« Hibernate: Пользовательские типы в hibernate. Разбираемся с компонентами | Пишем и тестируем код, работающий с БД, вместе с DBUnit и LiquiBase. Часть 2 » |
Hibernate: Пользовательские типы в hibernate. Разбираемся с UDT
Я продолжаю рассказывать об поддержке hibernate пользовательских типов данных и от рассмотрения компонентов, я перехожу, собственно, к созданию собственных типов данных, корректно интегрирующихся в среду hibernate.Существует несколько интерфейсов являющихся базовыми точками расширения hibernate-функциональности: UserType, CompositeUserType, UserCollectionType, EnhancedUserType, UserVersionType, ParametrizedType. Не все эти интерфейсы часто используются в практике, так я сосредоточусь на описании возможностей только UserType, CompositeUserType и ParametrizedType.
Тем кто дружит с ibatis: Надо сказать, что по сравнению с тем как реализована система пользовательских типов данных в ibatis, hibernate кажется очень, очень громоздким и избыточным. Фактически, казалось бы достаточно создать два метода выполняющих пребразование из стандартного sql-типа данных в java-класс и обратно. Однако, нет: в самом простом случае для интерфейса UserType нам нужно реализовать 11 методов. Все дело в том, что система типов в hibernate более гибкая и универсальная: там мы можем работать с пользовательскими типами данных, которые ну уровне БД отображаются не одним sql-полем, а несколькими. Кроме того, есть возможность создать такой свой тип данных, что можно использовать его при написании hql-запросов.
Для примера я создал копию показанного в прошлой части статьи класса AddressComponent, только назвал его AddressType и теперь буду учить hibernate работать с этим новым типом данных.
Сначала я создаю класс, которых инкапсулирует в себе все поля образующие адрес. Обязательным в составе этого класса является реализация методов hashCode и equals.
package test.db2.model;
import java.io.Serializable;
public class AddressType implements Serializable {
protected String country;
protected String city;
protected String home;
protected String phone;
public AddressType() {
}
public AddressType(String country, String city, String home, String phone) {
this.country = country;
this.city = city;
this.home = home;
this.phone = phone;
}
// обязательно нужно реализовать методы hashCode и equals
public int hashCode() {
int s = 0;
if (country != null) s += country.hashCode();
if (city != null) s = s * 7 + city.hashCode();
if (home != null) s = s * 7 + home.hashCode();
if (phone != null) s = s * 7 + phone.hashCode();
return s;
}
public boolean equals(Object obj) {
if (obj == null) return false;
if (!(obj instanceof AddressType)) return false;
if (this == obj) return true;
AddressType at = (AddressType) obj;
return (country == null ? at.country == null : country.equals(at.country)) &&
(city == null ? at.city == null : city.equals(at.city)) &&
(home == null ? at.home == null : home.equals(at.home)) &&
(phone == null ? at.phone == null : phone.equals(at.phone));
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getHome() {
return home;
}
public void setHome(String home) {
this.home = home;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
package test.db2.model;
import org.hibernate.usertype.UserType;
import org.hibernate.HibernateException;
import org.hibernate.Hibernate;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.io.Serializable;
public class AddressTypeDescriptor implements UserType {
// т.к. мой старый новый тип данных адреса состоит из четырех текстовых полей,
// то здесь я и задам массив с указанием их тиво данных
int [] sqlTypes = new int [] {
Hibernate.STRING.sqlType(), Hibernate.STRING.sqlType(),
Hibernate.STRING.sqlType(), Hibernate.STRING.sqlType()
};
/**
* Возвращаем массив с указанием того какие типы данных образуют данное "высокоуровневое свойство"
* @return
*/
public int[] sqlTypes() {
return sqlTypes;
}
/**
* Теперь нужно сообщить о том, какой тип данных используется для хранения адреса
* @return
*/
public Class returnedClass() {
return AddressType.class;
}
/**
* Сравнение двух объектов заданного типа данных (после простейших проверок
* эту задачу можно делегировать методу equals в составе AddressType)
* @param x
* @param y
* @return
* @throws HibernateException
*/
public boolean equals(Object x, Object y) throws HibernateException {
if (x == null || y == null) return false;
return x.equals(y);
}
/**
* Просто перевызываем метод hashCode для целевого объекта x
* @param x
* @return
* @throws HibernateException
*/
public int hashCode(Object x) throws HibernateException {
if (x == null) return 0;
return x.hashCode();
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException {
AddressType at = new AddressType();
at.setCountry(rs.getString(names[0]));
at.setCity(rs.getString(names[1]));
at.setHome(rs.getString(names[2]));
at.setPhone(rs.getString(names[3]));
return at;
}
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException {
AddressType at = (AddressType) value;
if (at != null){
st.setString(index+0, at.getCountry());
st.setString(index+1, at.getCity());
st.setString(index+2, at.getHome());
st.setString(index+3, at.getPhone());
}
else{
st.setNull(index+0, sqlTypes[0]);
st.setNull(index+1, sqlTypes[1]);
st.setNull(index+2, sqlTypes[2]);
st.setNull(index+3, sqlTypes[3]);
}
}
/**
* Здесь нужно создать копию объекта (полную, или глубокую копию)
* @param value
* @return
* @throws HibernateException
*/
public Object deepCopy(Object value) throws HibernateException {
if (value == null) return null;
AddressType at = (AddressType) value;
return new AddressType (at.getCountry(), at.getCity(), at.getHome(), at.getPhone());
}
/**
* Да, мы обладаем способностью к изменению
* @return
*/
public boolean isMutable() {
return true;
}
/**
* Этот метод используется когда нужно объект поместить внутрь cache второго уровня
* @param value
* @return
* @throws HibernateException
*/
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
}
/**
* А здесь обратный процесс, когда объект восстанавливается из сериализованного представления себя
* @param cached
* @param owner
* @return
* @throws HibernateException
*/
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return cached;
}
/**
* Метод вызывается когда необходимо выполнить свлияние двух объектов
* @param original
* @param target
* @param owner
* @return
* @throws HibernateException
*/
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return deepCopy(original);
}
}
- sqlTypes этот метод должен вернуть массив целых чисел - кодов. Т.е. мы должны сообщить hibernate о том, какие типы данных полей необходимы для хранения полей класса пользовательского типа данных. Т.к. мой класс AddressType содержит четырые строковые поля, то я и возвращаю массив из четырех кодов Hibernate.STRING.sqlType().
- returnedClass код этого метода прозрачен - мы должны вернуть hibernate информацию о том какой тип данных представляется в java.
- equals метод служит для сравнения двух объектов на равенство (например, при выполнении проверки на "чистоту" при закрытии сессии)
- hashCode - без комментариев.
- метод nullSafeGet служит для того, чтобы выполнить чтение из базы данных содержимого моего типа UserAddress. В качестве параметров методу передается объект ResultSet, однако для того чтобы мы могли читать данные из resultset-а, нам нужно знать либо имена полей, либо их порядковые номера. Таким образом hibernate передает внутрь метода nullSafeGet еще и массив String[] names с именами полей.
- метод nullSafeSet выполняет парное действие к nullSafeGet и служит для записи содержимого объекта внутрь БД.
- deepCopy метод должен выполнить глубокое клонирование некоторого объекта.
- результатом вызова метода isMutable будет boolean, говорящий о том является ли данный тип данных (AddressType) изменяемым после своего создания или нет.
- методы disassemble и disassemble служат для выполнения корректной сериализации объекта при передаче его между hibernate session и кэшем второго уровня.
- метод replace вызывается при выполнении операции merge записи с существующей в БД.
Изменения в классе User тривиальны: я избавился от ненужных полей в составе класса и заменил AddressComponent на AddressType:
@Entity
public class User {
@Id
@GeneratedValue
protected Integer id;
protected String fio;
protected AddressType homeAddress;
public User() {
}
public User(String fio, AddressType homeAddress) {
this.fio = fio;
this.homeAddress = homeAddress;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFio() {
return fio;
}
public void setFio(String fio) {
this.fio = fio;
}
public AddressType getHomeAddress() {
return homeAddress;
}
public void setHomeAddress(AddressType homeAddress) {
this.homeAddress = homeAddress;
}
}
<hibernate-mapping package="test.db2.model">
<class name="User">
<id name="id" type="int">
<generator class="native" />
</id>
<property name="fio" type="string" />
<property name="homeAddress" type="test.db2.model.AddressTypeDescriptor" >
<column name="country" />
<column name="city" />
<column name="home" />
<column name="phone" />
</property>
</class>
</hibernate-mapping>
@org.hibernate.annotations.Type(type = "test.db2.model.AddressTypeDescriptor")
@Columns(columns = {
@Column(name = "home_country"),
@Column(name = "home_city"),
@Column(name = "home_home"),
@Column(name = "home_phone")
}
)
protected AddressType homeAddress;
Показанный выше пример создал класс дескриптора нового типа данных как реализующий интерфейс UserType. Теперь попробуем расширить класс AddressTypeDescriptor и заставим его реализовать интерфейс CompositeUserType. Если посмотреть на то, какие методы образуют данный интерфейс, то можете увидеть что многие методы перекочевали из UserType, часть методов при этом немного изменила сигнатуры, чтобы соответствовать новым требованиям. Самый главный вопрос: зачем нужен CompositeUserType и чем он отличается от UserType? Все дело в том, что когда мы создаем новый тип данных, то фактически приводим к созданию новых свойств не однозначно связанных с полями исходной таблицы. Это не составило бы больших проблем, если бы мы ограничились простыми действиями, вроде "создать запись, сохранить, удалить". Однако, когда мы говорим о поиске данных, то возникает проблема: я хочу сформирулировать запрос "найти всех пользователей, которые живут в РБ". Для нас очевидно, что так сконструировать запрос, чтобы он сравнивал значение поля "home_country" со значением "РБ". Однако, чтобы сделать так hibernate должен знать об внутреннем устройстве пользовательского типа данных немного больше, чем то, какие типы полей его образуют, давайте дадим ему это:
package test.db2.model;
import org.hibernate.usertype.UserType;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.HibernateException;
import org.hibernate.Hibernate;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.type.Type;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.io.Serializable;
public class AddressTypeDescriptor implements CompositeUserType {
/**
* возвращаем информацию об том какие имена полей образуют новый тип данных
*
* @return
*/
public String[] getPropertyNames() {
return new String[]{"country", "city", "home", "phone"};
}
/**
* возвращаем информацию о том, какие типы полей образуют эти свойства
*
* @return
*/
public Type[] getPropertyTypes() {
return new Type[]{
Hibernate.STRING, Hibernate.STRING,
Hibernate.STRING, Hibernate.STRING
};
}
/**
* теперь надо на основании порядкового номера свойства вернуть его значение
*
* @param component
* @param property
* @return
* @throws HibernateException
*/
public Object getPropertyValue(Object component, int property) throws HibernateException {
AddressType at = (AddressType) component;
switch (property) {
case 0:
return at.getCountry();
case 1:
return at.getCity();
case 2:
return at.getHome();
case 3:
return at.getPhone();
default:
throw new IllegalArgumentException("invalid property numer '" + property + "'");
}
}
/**
* теперь выполняем парную операцию, устанавливая новое значение для сложного свойства
*
* @param component
* @param property
* @param value
* @throws HibernateException
*/
public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
AddressType at = (AddressType) component;
switch (property) {
case 0:
at.setCountry((String) value);
break;
case 1:
at.setCity((String) value);
break;
case 2:
at.setHome((String) value);
break;
case 3:
at.setPhone((String) value);
break;
default:
throw new IllegalArgumentException("invalid property numer '" + property + "'");
}
}
/**
* указываем сведения об том типе данных, который обслуживает данный класс
*
* @return
*/
public Class returnedClass() {
return AddressType.class;
}
/**
* Сравнение двух объектов заданного типа данных (после простейших проверок
* эту задачу можно делегировать методу equals в составе AddressType)
*
* @param x
* @param y
* @return
* @throws HibernateException
*/
public boolean equals(Object x, Object y) throws HibernateException {
if (x == null || y == null) return false;
return x.equals(y);
}
/**
* Просто перевызываем метод hashCode для целевого объекта x
*
* @param x
* @return
* @throws HibernateException
*/
public int hashCode(Object x) throws HibernateException {
if (x == null) return 0;
return x.hashCode();
}
/**
* нужно выполнить чтение данных образующих свойство из ResultSet-а
*
* @param rs
* @param names
* @param session
* @param owner
* @return
* @throws HibernateException
* @throws SQLException
*/
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)
throws HibernateException, SQLException {
AddressType at = new AddressType();
at.setCountry(rs.getString(names[0]));
at.setCity(rs.getString(names[1]));
at.setHome(rs.getString(names[2]));
at.setPhone(rs.getString(names[3]));
return at;
}
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)
throws HibernateException, SQLException {
AddressType at = (AddressType) value;
if (at != null) {
st.setString(index + 0, at.getCountry());
st.setString(index + 1, at.getCity());
st.setString(index + 2, at.getHome());
st.setString(index + 3, at.getPhone());
} else {
st.setNull(index + 0, Hibernate.STRING.sqlType());
st.setNull(index + 1, Hibernate.STRING.sqlType());
st.setNull(index + 2, Hibernate.STRING.sqlType());
st.setNull(index + 3, Hibernate.STRING.sqlType());
}
}
/**
* Здесь нужно создать копию объекта (полную, или глубокую копию)
* @param value
* @return
* @throws HibernateException
*/
public Object deepCopy(Object value) throws HibernateException {
if (value == null) return null;
AddressType at = (AddressType) value;
return new AddressType (at.getCountry(), at.getCity(), at.getHome(), at.getPhone());
}
/**
* Да, мы обладаем способностью к изменению
* @return
*/
public boolean isMutable() {
return true;
}
/**
* Этот метод используется когда нужно объект поместить внутрь cache второго уровня
* @param value
* @param session
* @return
* @throws HibernateException
*/
public Serializable disassemble(Object value, SessionImplementor session) throws HibernateException {
return (Serializable)value;
}
/**
* А здесь обратный процесс, когда объект восстанавливается из сериализованного представления себя
* @param cached
* @param session
* @param owner
* @return
* @throws HibernateException
*/
public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException {
return cached;
}
/**
* Метод вызывается когда необходимо выполнить свлияние двух объектов
* @param original
* @param target
* @param session
* @param owner
* @return
* @throws HibernateException
*/
public Object replace(Object original, Object target, SessionImplementor session, Object owner) throws HibernateException {
return deepCopy(original);
}
}
Теперь попробуем поиграться с новым типом данных для создания hql-запроса (равно, как и criteria-основанного запроса) для поиска данных в БД. Для этого мне пришлось внести очередные правки в класс User (также помимо добавления поля homeAddress хранящего сведения об домашнем адресе пользователя в виде нового типа данных), я вернул в состав User-а информацию об его рабочем адресе (но только в форме рассмотренного в прошлой статье component-а):
package test.db2.model;
import org.hibernate.annotations.AccessType;
import org.hibernate.annotations.Columns;
import javax.persistence.*;
import java.io.Serializable;
@Entity
public class User {
@Id
@GeneratedValue
protected Integer id;
protected String fio;
@org.hibernate.annotations.Type(type = "test.db2.model.AddressTypeDescriptor")
@Columns(columns = {
@Column(name = "home_country"),
@Column(name = "home_city"),
@Column(name = "home_home"),
@Column(name = "home_phone")
})
protected AddressType homeAddress;
@AttributeOverrides({
@AttributeOverride(name = "country.name", column = @Column(name="work_country_name")),
@AttributeOverride(name = "country.anthem", column = @Column(name="work_country_anthem")),
@AttributeOverride(name = "city", column = @Column(name="work_city")),
@AttributeOverride(name = "home", column = @Column(name="work_home")),
@AttributeOverride(name = "phone", column = @Column(name="work_phone"))
})
@Embedded
protected AddressComponent workAddress;
@ManyToOne (cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
@JoinColumn (name = "fk_department_id")
protected Department department;
public User() {
}
public User(String fio, AddressType homeAddress) {
this.fio = fio;
this.homeAddress = homeAddress;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFio() {
return fio;
}
public void setFio(String fio) {
this.fio = fio;
}
public AddressType getHomeAddress() {
return homeAddress;
}
public void setHomeAddress(AddressType homeAddress) {
this.homeAddress = homeAddress;
}
public AddressComponent getWorkAddress() {
return workAddress;
}
public void setWorkAddress(AddressComponent workAddress) {
this.workAddress = workAddress;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
}
Session session = getSession();
session.beginTransaction();
User vasyano = new User("Vasyano Tapkin", new AddressType("Республика Беларусь", "минск", "13", "1234567890"));
User petyano = new User("Petyano Gromov", new AddressType("belarus", null, null, null));
User lenka = new User("Lenka Umkina", null);
lenka.setWorkAddress(new AddressComponent(new Country("belarus", "bla-bla-bla"), "minsk", "13", "1234567890"));
session.saveOrUpdate(vasyano);
session.saveOrUpdate(petyano);
session.saveOrUpdate(lenka);
session.getTransaction().commit();
Query hql_query = session.createQuery("from User where homeAddress.country = :country");
hql_query.setString("country", "belarus");
List hql_list_in_rb = hql_query.list();
Второй пример запроса так же работает с hql и домашним адресом клиента. Однако, в этом случае я хочу выполнить поиск не на основании значения отдельных свойств домашнего адреса, а сравнить весь домашний адрес:
AddressType addr_of_lenka = new AddressType("Республика Беларусь", "минск", "13", "1234567890");
Query hql_query2 = session.createQuery("from User where homeAddress = :address");
hql_query2.setParameter("address", addr_of_lenka, Hibernate.custom(AddressTypeDescriptor.class));
List hql2_list_list_by_spec_addr = hql_query2.list();
Небольшое примечание: при выполнении такого запроса возникают проблемы с mysql т.к. посылаемый на сервер запрос выглядит следующим образом:
23:49:50,437 DEBUG SQL:401 - /* from User where homeAddress = :address */
select user0_.id as id0_, user0_.fk_department_id as fk13_0_, user0_.fio as fio0_, user0_.friendly as friendly0_,
user0_.home_country as home4_0_, user0_.home_city as home5_0_, user0_.home_home as home6_0_, user0_.home_phone as home7_0_,
user0_.work_city as work8_0_, user0_.work_country_anthem as work9_0_, user0_.work_country_name as work10_0_,
user0_.work_home as work11_0_, user0_.work_phone as work12_0_ from User user0_
where (user0_.home_country, user0_.home_city, user0_.home_home, user0_.home_phone)=(?, ?, ?, ?)
jdbc:mysql://localhost/demoarticles?useUnicode=true&characterSet=UTF-8&connectionCollation=utf8_general_ci
Третий пример запроса повторяет функциональность первого вида запроса, но создан с помощью Criteria-api:
Criteria criteria_addtype = DetachedCriteria.forClass(User.class).add(
Restrictions.eq("homeAddress.country", "belarus")
).getExecutableCriteria(session);
List hql3_list_by_criteriapi_addrtype = criteria_addtype.list();
Query hql_query4 = session.createQuery("from User where workAddress.country.name = :countryName");
hql_query4.setString("countryName", "belarus");
List hql4_list_list_by_spec_addr = hql_query4.list();
Не секрет, что при работе с hibernate, мы сталкиваемся с проблемой несовпадения модели данных в БД и в java. Классическим примером является работа с логическими типами данных: если в java мы объвляем поля класса как boolean - и это удобно, то попытка создать логическое поле в БД может натолкнуться на ряд проблем.
Прежде всего, не все БД поддерживают логический тип данных и в этом случае его приходится эмулировать с помощью, например, целого числа (если нету bit-а, то сойдет и tinyint), для которого 0 играет роль "false", а 1 - "true". Возможен и вариант, когда роль логического типа играет строка из одного символа char(1), значением которой являются такие величины как 'Y/N' или 'T/F' или что там еще вы придумаете.
Показанный мною пример работы с CustomType позволяет вам создать любой из приведенных выше примеров. Однако, предположим, что вы решили создать универсальный тип данных. Т.е. в основе представления логического типа данных в БД используется строка char(1), а вот конкретные значения играющие роль true & false различаются. И для того чтобы не создавать несколько классов с практически идентичной функциональностью (отличия только в двух буквах), то вы хотели бы иметь возможность создать универсальный (гибкий, шаблонный) тип данных "универсальный_boolean_построенный_на_базе_строки_из_одного_символа". Но при этом при объявлении в составе класса некоторого поля использующего этот "тип_данных_с_длинным_названием" вы можете передать ему два параметра: две конкретные строки используемые для представления значения true & false. И именно для этого используется ParametrizedType.
В состав интерфейса входит только один метод, используемый hibernate-ом для передачи параметров влияющих на работу класса:
public void setParameterValues(Properties parameters) {}
package test.db2.model;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
import org.hibernate.HibernateException;
import org.hibernate.Hibernate;
import java.util.Properties;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.io.Serializable;
public class SuperBooleanTypeDescriptor implements UserType, ParameterizedType{
/**
* две переменные хранят значения, играющие роль true|false отметок
*/
protected String trueMarker;
protected String falseMarker;
/**
* принимаем и сохраняем значения параметров
* @param parameters
*/
public void setParameterValues(Properties parameters) {
trueMarker = parameters.getProperty("trueMarker");
falseMarker = parameters.getProperty("falseMarker");
if (trueMarker == null || falseMarker == null)
throw new IllegalArgumentException ("SuperBooleanTypeDescriptor requires 2 parameters: trueMarker & falseMarker");
}
public int[] sqlTypes() {
// указывем какой тип данных на уровне БД используется для хранения логического значения
return new int[]{Hibernate.CHARACTER.sqlType()};
}
public Class returnedClass() {
return Boolean.class;
}
public boolean equals(Object x, Object y) throws HibernateException {
if (x == null || y == null) return false;
return x.equals(y);
}
public int hashCode(Object x) throws HibernateException {
if (x == null) return 0;
return x.hashCode();
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
String s = rs.getString(names[0]);
if (trueMarker.equals(s)) return true;
if (falseMarker.equals(s)) return false;
return null;
}
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
if (value == null)
st.setString(index, null);
else
st.setString(index, ((Boolean)value)?trueMarker:falseMarker);
}
public Object deepCopy(Object value) throws HibernateException {
if (value == null) return null;
return new Boolean (((Boolean)value));
}
public boolean isMutable() {
return true;
}
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable)value;
}
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return cached;
}
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return deepCopy(original);
}
}
Теперь пример кода, который использует созданный шагом ранее тип данных (для этого в составе класса User я создал новое свойство friendly типа boolean и промаркировал его соответсвующим образом):
@Entity
public class User {
@Id
@GeneratedValue
protected Integer id;
@Type(type = "test.db2.model.SuperBooleanTypeDescriptor",
parameters = {
@Parameter(name = "trueMarker", value = "t"),
@Parameter(name = "falseMarker", value = "f")
}
)
protected Boolean friendly;
@TypeDefs ({
@TypeDef (
name = "boolean_t_f" , typeClass = SuperBooleanTypeDescriptor.class,
parameters = {
@Parameter(name = "trueMarker", value = "t"),
@Parameter(name = "falseMarker", value = "f")
}
)
})
package test.db2.model;
import org.hibernate.annotations.TypeDefs;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.Parameter;
<mapping class="test.db2.model.User" />
// ***** то что надо *****
<mapping package="test.db2.model"/>
<mapping class="test.db2.model.Furniture" />
<mapping class="test.db2.model.Department" />
@Entity
public class User {
@Id
@GeneratedValue
protected Integer id;
@Type(type = "boolean_t_f")
protected Boolean friendly;
protected String fio;
<hibernate-mapping package="test.db2.model">
<typedef class="test.db2.model.SuperBooleanTypeDescriptor" name="boolean_Y_N">
<param name="trueMarker">Y</param>
<param name="falseMarker">N</param>
</typedef>
<class name="User">
<id name="id" type="int">
<generator class="native"/>
</id>
<property name="fio" type="string"/>
<property name="homeAddress" type="test.db2.model.AddressTypeDescriptor">
<column name="country"/>
<column name="city"/>
<column name="home"/>
<column name="phone"/>
</property>
<component name="workAddress" class="AddressComponent">
<parent name="homeOwner"/>
<component name="country" class="Country">
<property name="name" column="work_country_name"/>
<property name="anthem" column="work_country_anthem"/>
</component>
<property name="city" column="work_city"/>
<property name="home" column="work_home"/>
<property name="phone" column="work_phone"/>
</component>
<property name="friendly">
<type name="test.db2.model.SuperBooleanTypeDescriptor">
<param name="trueMarker">Y</param>
<param name="falseMarker">N</param>
</type>
</property>
<!-- или так -->
<!--
<property name="friendly" type="boolean_Y_N" />
-->
</class>
</hibernate-mapping>
session.createQuery("from User where friendly = :f").setBoolean("f", false).list()
« Hibernate: Пользовательские типы в hibernate. Разбираемся с компонентами | Пишем и тестируем код, работающий с БД, вместе с DBUnit и LiquiBase. Часть 2 » |