Как и в 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.