Как и в JPA, в голом Hibernate требуется описывать как сущности отображаются на таблицы базы данных. Hibernate поддерживает три варианта описания отображения: с использованием аннотаций, описание в xml файле, динамическое отображение.
Annotation mapping
Раньше Hibernate использовал собственный набор аннотаций для описания отображения сущностей на таблицы. В последних версиях эти аннотации признали устаревшими и Hibernate использует JPA аннотации для описания отображений сущностей и связей между ними.
Главное различие между JPA и Hibernate в том, что JPA может самостоятельно находить классы сущностей, сканируя аннотации, в то время как Hibernate требует перечисления всех проаннотированных классов в конфигурации:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<hibernate-configuration>
<session-factory>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
<property name="hibernate.connection.url">jdbc:h2:mem:test</property>
<mapping class="ru.easyjava.data.hibernate.entity.AbstractIdentifiableObject"/>
<mapping class="ru.easyjava.data.hibernate.entity.Address"/>
<mapping class="ru.easyjava.data.hibernate.entity.Company"/>
<mapping class="ru.easyjava.data.hibernate.entity.Passport"/>
<mapping class="ru.easyjava.data.hibernate.entity.Person"/>
</session-factory>
</hibernate-configuration>
|
Кроме того, Hibernate поддерживает дополнительные аннотации для отображения, которые расширяют возможности ORM. Я расскажу о их использовании подробнее позже, в статьях, посвящённых этому дополнительному функционалу.
XML mapping
XML mapping исторически появился в Hibernate первым и долгое время оставлялся единственным механизмом описания отображения сущностей на таблицы. В настоящий момент рекомендуется использовать аннотации, которые активно развиваются и имеют лучший функционал. Но у XML есть и свои плюсы: код сущностей остаётся нетронутым и описание отображения отделено от кода. Соответственно, это единственная возможность применить ORM в том случае, когда код сущностей недоступен или его нельзя изменять.
Традиционно каждая сущность описывается в своём отдельном файле с именем сущность.hbm.xml. Разумеется, имя файла и его расширение могут быть любыми, но желательно придерживаться этого соглашения.
В простом случае сущность описывается перечислением полей и указанием id поля:
1
2
3
4
5
6
7
8
9
10
11
|
<hibernate-mapping package="ru.easyjava.data.hibernate.entity">
<class name="Passport" table="PASSPORTS">
<id name="id" type="int" column="id">
<generator class="native"/>
</id>
<property name="series" type="string" column="SERIES"/>
<property name="no" type="string" column="NO"/>
<property name="issueDate" type="timestamp" column="ISSUE_DATE"/>
<one-to-one name="owner" class="ru.easyjava.data.hibernate.entity.Person" cascade="save-update"/>
</class>
</hibernate-mapping>
|
В примере выше описывают поля сущности, названия таблицы и столбцов в ней и, кроме того, отношение один-к-одному с сущностью Person. К сожалению в XML mapping нет аналога @MappedSuperclass, поэтому пришлось включать id поле в каждый класс. Это хороший пример превосходства функционала аннотаций над XML 🙂
Впрочем, XML не так уж и беден. По крайней мере стандартные отношения между классами в нём описываются весьма полноценно. Например один-ко-многим:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<hibernate-mapping package="ru.easyjava.data.hibernate.entity">
<class name="Address" table="ADDRESSES">
<id name="id" type="int" column="id">
<generator class="native"/>
</id>
<property name="city" type="string" column="CITY"/>
<property name="street" type="string" column="STREET"/>
<property name="building" type="string" column="BUILDING"/>
<set name="tenants" table="PERSONS" inverse="true" lazy="false">
<key>
<column name="PRIMARY_ADDRESS"/>
</key>
<one-to-many class="ru.easyjava.data.hibernate.entity.Person"/>
</set>
</class>
</hibernate-mapping>
|
Или даже многие-ко-многим:
1
2
3
4
5
6
7
8
9
10
11
12
|
<hibernate-mapping package="ru.easyjava.data.hibernate.entity">
<class name="Company" table="COMPANIES">
<id name="id" type="int" column="id">
<generator class="native"/>
</id>
<property name="name" type="string" column="NAME"/>
<set name="workers" table="WORKERS" inverse="true" lazy="true">
<key column="COMPANY_ID"/>
<many-to-many column="PERSON_ID" class="ru.easyjava.data.hibernate.entity.Person"/>
</set>
</class>
</hibernate-mapping>
|
Или даже всё сразу:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<hibernate-mapping package="ru.easyjava.data.hibernate.entity">
<class name="Person" abstract="true" table="PERSONS">
<id name="id" type="int" column="id">
<generator class="native"/>
</id>
<property name="firstName" type="string" column="FIRST_NAME"/>
<property name="lastName" type="string" column="LAST_NAME"/>
<property name="dob" type="timestamp" column="DOB"/>
<one-to-one name="passport" class="ru.easyjava.data.hibernate.entity.Passport" cascade="save-update"/>
<many-to-one name="primaryAddress" class="ru.easyjava.data.hibernate.entity.Address">
<column name="PRIMARY_ADDRESS"/>
</many-to-one>
<set name="workingPlaces" table="WORKERS" inverse="false" lazy="true" cascade="all">
<key column="PERSON_ID"/>
<many-to-many column="COMPANY_ID" class="ru.easyjava.data.hibernate.entity.Company"/>
</set>
</class>
</hibernate-mapping>
|
Все файлы отображений так же надо перечислить в конфигурации Hibernate:
1
2
3
4
5
6
7
8
9
10
11
12
|
<hibernate-configuration>
<session-factory>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
<property name="hibernate.connection.url">jdbc:h2:mem:test</property>
<mapping resource="Passport.hbm.xml"/>
<mapping resource="Address.hbm.xml"/>
<mapping resource="Company.hbm.xml"/>
<mapping resource="Person.hbm.xml"/>
</session-factory>
</hibernate-configuration>
|
Код, который работал с сущностями в примере с аннотациями, работает без изменений с теми же самыми сущностями, но описанными в XML.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
@Before
public void setUp() throws Exception {
Passport p = new Passport();
p.setSeries("AS");
p.setNo("123456");
p.setIssueDate(new Date());
Address a = new Address();
a.setCity("Kickapoo");
a.setStreet("Main street");
a.setBuilding("1");
Person person = new Person();
person.setFirstName("Test");
person.setLastName("Testoff");
person.setDob(new Date());
person.setPrimaryAddress(a);
person.setPassport(p);
Company c = new Company();
c.setName("Acme Ltd");
p.setOwner(person);
person.setWorkingPlaces(Collections.singleton(c));
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.configure()
.build();
try {
sessionFactory = new MetadataSources(registry)
.buildMetadata()
.buildSessionFactory();
} catch (Exception e) {
StandardServiceRegistryBuilder.destroy(registry);
throw e;
}
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(person);
session.save(a);
session.getTransaction().commit();
session.close();
}
@Test
public void testGreeter() {
Session session = sessionFactory.openSession();
session.beginTransaction();
session.createQuery("from Person ")
.list()
.forEach(System.out::println);
session.createQuery("from Passport ")
.list()
.forEach(System.out::println);
session.createQuery("from Address ")
.list()
.forEach(System.out::println);
session.createQuery("from Company ")
.list()
.forEach(System.out::println);
session.getTransaction().commit();
session.close();
}
|
Dynamic mapping
Последнее нововведение в Hibernate, это динамическое отображение. И это не отказ от необходимости перечислять сущности в конфигурации, вовсе нет. Это всего лишь возможность не определять конкретные структуры данных со стороны Java, заменяя их на Map. То есть, если есть база данных с таблицей, допустим, persons, можно не определять класс для данных, хранящихся в этой таблице, а загружать её в Map. При этом частичное отображение описывать всё равно нужно.
В первую очередь необходимо описать таблицу и её столбцы:
1
2
3
4
5
6
7
8
9
10
|
<hibernate-mapping>
<class entity-name="Passport">
<id name="id" type="long" column="ID">
<generator class="native"/>
</id>
<property name="series" column="SERIES" type="string"/>
<property name="no" column="NO" type="string"/>
<property name="issueDate" column="ISSUE_DATE" type="timestamp"/>
</class>
</hibernate-mapping>
|
во вторую очередь, добавить описание таблицы в конфигурацию Hibernate и включить поддержку dynamic mapping:
1
2
3
4
5
6
7
8
9
10
|
<hibernate-configuration>
<session-factory>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
<property name="hibernate.connection.url">jdbc:h2:mem:test</property>
<property name="default_entity_mode">dynamic-map</property>
<mapping resource="Passport.hbm.xml"/>
</session-factory>
</hibernate-configuration>
|
И теперь можно создавать записи в базе из Map. Обратите внимание, что при сохранении указывается имя сущности из её описания:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Session session = sessionFactory.openSession();
session.beginTransaction();
Map<String,Object> passport = new HashMap<>();
passport.put("id", 1L);
passport.put("series", "AS");
passport.put("no", "123456");
passport.put("issueDate", new Date());
session.save("Passport", passport);
session.getTransaction().commit();
session.close();
|
Сохранённую сущность можно и прочитать обратно в Map:
1
2
3
|
session.createQuery("from Passport ")
.list()
.forEach(System.out::println);
|
1
|
{no=123456, series=AS, id=1, issueDate=2016-06-07 14:26:35.428, $type$=Passport}
|
Код примера доступен на github.