Статьи

Аннотирование пользовательских типов в Hibernate

У Hibernate есть много приятных функций, и они довольно хорошо документированы, но недавняя необходимость добавить простой пользовательский тип в существующее отображение заставила меня задуматься о том, как это сделать. Я хотел бы сделать это с аннотациями, а не путем изменения конфигурации Hibernate (такой подход является  хорошо документированы). Вот как это делается.

Нужны два новых класса. Вы можете сделать это с одним (и примеры Hibernate делают это таким образом), но у них действительно разные функции, поэтому я написал их отдельно.

Первый — это класс, который вы хотите использовать для столбца. В моем случае мне нужна дата без миллисекунд, которая является тонкой оболочкой над java.util.Date. Вот мой класс:

/** * Oracle stores dates in DATE columns down to the second; Java stores them to the millisecond. * This occasionally can confuse Hibernate as to what data are stale.  This class slices off * any milliseconds which might be present in its representation. */public class DateNoMs extends java.util.Date {    private static final long serialVersionUID = 1L;    /** @see java.util.Date() */    public DateNoMs() {        super();        long t = getTime();        setTime(t - t%1000);    }    /** @see java.util.Date(long) */    public DateNoMs(long time) {        super(time - time%1000);    }        /**     * @param value     */    public DateNoMs(Date value) {        long t = value.getTime();        setTime(t - t%1000);    }    /** @see java.util.Date#setTime(long)     */    @Override    public void setTime(long time) {        super.setTime(time - time%1000);    }} 

Прямо, верно? Теперь в моем классе у меня есть отображение поля:

    @Column(name = "PAYMENT_DATE")    private DateNoMs m_paymentDate;

Конечно, это не сработает — Hibernate будет затягивать отображение, потому что не знает, как сопоставить столбец JDBC DATE с DateNoMs — как и следовало ожидать. На данный момент нам нужны две вещи: во-первых, объект, который Hibernate может использовать для преобразования JDBC DATE в DateNoMs, и аннотация, указывающая на эту «фабрику». Фабричный класс создается путем реализации (в простейшем случае) org.hibernate.usertype.UserType. Документация в этом интерфейсе довольно тонкая, но в дистрибутиве Hibernate есть хорошие примеры. Вот моя реализация. Мне очень помогает тот факт, что мой класс (DateNoMs) очень близок к java.util.Date, а java.sql.Date расширяет java.util.Date.

/** * Map "things" (currently Oracle Date columns) to the DateNoMs. */public class DateNoMsType implements UserType {    /** @see org.hibernate.usertype.UserType#assemble(java.io.Serializable, Object)     */    public Object assemble(Serializable cached, @SuppressWarnings("unused") Object owner) {        return cached;    }    /** @see org.hibernate.usertype.UserType#deepCopy(Object)     */    public Object deepCopy(Object value) {        if (value==null)            return null;                if (! (value instanceof java.util.Date))            throw new UnsupportedOperationException("can't convert "+value.getClass());        return new DateNoMs((java.util.Date)value);    }    /** @see org.hibernate.usertype.UserType#disassemble(Object)     */    public Serializable disassemble(Object value) throws HibernateException {        if (! (value instanceof java.util.Date))            throw new UnsupportedOperationException("can't convert "+value.getClass());        return new DateNoMs((java.util.Date)value);    }    /** @see org.hibernate.usertype.UserType#equals(Object, Object)     */    public boolean equals(Object x, Object y) throws HibernateException {        return x.equals(y);    }    /** @see org.hibernate.usertype.UserType#hashCode(Object)     */    public int hashCode(Object value) throws HibernateException {        return value.hashCode();    }    /** @see org.hibernate.usertype.UserType#isMutable()     */    public boolean isMutable() {        return true;    }    /** @see org.hibernate.usertype.UserType#nullSafeGet(java.sql.ResultSet, String[], Object)     */    public Object nullSafeGet(ResultSet rs, String[] names, @SuppressWarnings("unused") Object owner)            throws HibernateException, SQLException {        // assume that we only map to one column, so there's only one column name        java.sql.Date value = rs.getDate( names[0] );        if (value==null)            return null;                return new DateNoMs(value.getTime());    }    /** @see org.hibernate.usertype.UserType#nullSafeSet(java.sql.PreparedStatement, Object, int)     */    public void nullSafeSet(PreparedStatement stmt, Object value, int index)            throws HibernateException, SQLException {        if (value==null) {            stmt.setNull(index, Types.DATE);            return;        }        if (! (value instanceof java.util.Date))            throw new UnsupportedOperationException("can't convert "+value.getClass());        stmt.setDate( index, new java.sql.Date( ((java.util.Date)value).getTime()) );    }    /** @see org.hibernate.usertype.UserType#replace(Object, Object, Object)     */    public Object replace(Object original,             @SuppressWarnings("unused") Object target, @SuppressWarnings("unused") Object owner)  {        return original;    }    /** @see org.hibernate.usertype.UserType#returnedClass()     */    @SuppressWarnings("unchecked")    public Class returnedClass() {        return DateNoMs.class;    }    /** @see org.hibernate.usertype.UserType#sqlTypes()     */    public int[] sqlTypes() {        return new int[] {Types.DATE};    }}    

Ядром этого класса являются два метода, которые получают и устанавливают значения, связанные с моим новым типом: nullSafeSet и nullSafeGet. Важно отметить, что nullSafeGet поставляется со списком всех имен столбцов, сопоставленных с пользовательским типом данных в текущем запросе. В моем случае есть только один, но в сложных случаях вы можете сопоставить несколько столбцов одному объекту (примеры есть в документации по Hibernate).

Последним фрагментом головоломки является аннотация, которая сообщает Hibernate использовать новый класс «Тип» для генерации объектов вашего пользовательского типа путем добавления новой аннотации @Type в столбец:

    @Type(type="com.gorillalogic.type.DateNoMsType")    @Column(name = "PAYMENT_DATE")    private DateNoMs m_paymentDate;     

Аннотации @Type необходим полный путь к классу, который реализует интерфейс userType; это фабрика для создания целевого типа сопоставленного столбца.

Если вы собираетесь использовать новый тип во многих местах, вы можете сократить аннотацию @Type, выполнив typedef; Вы можете поместить это в package-info.java в любой пакет, который вам нравится (я помещаю мой в тот же пакет, что и класс UserType). Вот строка для типа, определенного выше:

@TypeDefs(  {    @TypeDef(name = "dateNoMs", typeClass = com.gorillalogic.type.DateNoMsType.class  }) package com.gorillalogic.type;    

Теперь аннотация моей колонки может выглядеть так:

    @Type(type="dateNoMsType")    @Column(name = "PAYMENT_DATE")    private DateNoMs m_paymentDate;    

 

Этого должно быть достаточно, чтобы вы начали.

С http://execdesign.blogspot.com