Для этого вам нужно ознакомиться с Maven, JUnit, SQL и реляционными базами данных.
зависимости
Для начала нам понадобится пара основных зависимостей. По сути, есть три слоя:
- Самый нижний уровень — это драйверы JDBC, используемые Hibernate для подключения к базе данных. Я собираюсь использовать Derby, простую встроенную базу данных. Там нет сервера для установки или настройки, так что проще настроить, чем даже MySQL или PostgreSQL; это не подходит для производства.
- Средний слой — библиотеки Hibernate. Я собираюсь использовать версию 3.5.6. Это работает с Java 1.5, 4.x нет.
- Библиотеки JPA.
Кроме того, нам понадобится JUnit для создания тестов и Tomcat, чтобы мы могли использовать его JNDI-именование для тестов. JNDI является предпочтительной системой для включения сведений о сервере в файл свойств по причинам, к которым мы придем.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<dependencies> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derby</artifactId> <version>10.4.1.3</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>3.6.9.Final</version> </dependency> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.0-api</artifactId> <version>1.0.0.Final</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>catalina</artifactId> <version>6.0.18</version> <scope>test</scope> </dependency> </dependencies> |
конфигурация
Ключевой файл конфигурации для JPA — это файл persistence.xml. Это живет в каталоге META-INF. В нем подробно описывается, какой драйвер персистентности использовать и к какому источнику данных JNDI подключаться. Также можно указать дополнительные свойства, в этом случае мы включим некоторые свойства Hibernate.
Я добавил несколько комментариев о дополнительных свойствах, чтобы вы знали, для чего они нужны. Вы можете настроить источник данных напрямую, но использование JNDI означает, что мы можем легко развернуть код в контейнере, как автономно, так и для запуска модульных тестов с минимальными изменениями кода.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<?xml version='1.0' encoding='UTF-8'?> xsi:schemaLocation='http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd' version='1.0'> <persistence-unit name='tutorialPU' transaction-type='RESOURCE_LOCAL'> <provider>org.hibernate.ejb.HibernatePersistence</provider> <!-- the JNDI data source --> <non-jta-data-source>java:comp/env/jdbc/tutorialDS</non-jta-data-source> <properties> <!-- if this is true, hibernate will print (to stdout) the SQL it executes, so you can check it to ensure it's not doing anything crazy --> <property name='hibernate.show_sql' value='true' /> <property name='hibernate.format_sql' value='true' /> <!-- since most database servers have slightly different versions of the SQL, Hibernate needs you to choose a dialect so it knows the subtleties of talking to that server --> <property name='hibernate.dialect' value='org.hibernate.dialect.DerbyDialect' /> <!-- this tell Hibernate to update the DDL when it starts, very useful for development, dangerous in production --> <property name='hibernate.hbm2ddl.auto' value='update' /> </properties> </persistence-unit></persistence> |
юридические лица
JPA говорит с точки зрения сущностей, а не записей базы данных. Сущность — это экземпляр класса, сопоставляемый с одной записью в таблице (классы сопоставляются с таблицами). Поля сущностей (которые должны использовать соглашение об именах JavaBean) отображаются в столбцы.
Аннотации могут быть использованы для добавления дополнительной информации в классе. Они помечают класс как объект и позволяют указывать метаинформацию о таблице и столбцах, например имена, размеры и ограничения.
В нашем случае мы начнем с самой простой сущности.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package tutorial;import javax.persistence.*;import java.util.HashSet;import java.util.Set;@Entity@Table(name = 'usr') // @Table is optional, but 'user' is a keyword in many SQL variants public class User { @Id // @Id indicates that this it a unique primary key @GeneratedValue // @GeneratedValue indicates that value is automatically generated by the server private Long id; @Column(length = 32, unique = true) // the optional @Column allows us makes sure that the name is limited to a suitable size and is unique private String name; // note that no setter for ID is provided, Hibernate will generate the ID for us public long getId() { return id; } public void setName(String name) { this.name = name; } public String getName() { return name; }} |
JPA может использовать мета-информацию для создания DDL при запуске. Это полезно для разработки, поскольку позволяет быстро приступить к работе, не вдаваясь в SQL, необходимый для создания таблиц. Хотите добавить колонку? Просто добавьте колонку, скомпилируйте и запустите. К сожалению, удобство также увеличивает риск (например, что делает сервер базы данных, когда в таблице миллионы записей и вы добавляете новый столбец?) И потеря контроля.
Существует компромисс: после создания сущностей в Hibernate вы можете экспортировать DDL и изменить конфигурацию Hibernate, чтобы остановить обновление DDL.
Прецедент
Есть только две части, сначала мы создадим абстрактный контрольный пример в качестве корня для всех наших тестов. Это зарегистрирует источник данных в JNDI, и мы расширим его другими тестами, чтобы они получили доступ к базе данных.
|
01
02
03
04
05
06
07
08
09
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
|
package tutorial;import org.apache.derby.jdbc.EmbeddedDataSource;import org.apache.naming.java.javaURLContextFactory;import org.junit.AfterClass;import org.junit.BeforeClass;import javax.naming.Context;import javax.naming.InitialContext;public abstract class AbstractTest { @BeforeClass public static void setUpClass() throws Exception { System.setProperty(Context.INITIAL_CONTEXT_FACTORY, javaURLContextFactory.class.getName()); System.setProperty(Context.URL_PKG_PREFIXES, 'org.apache.naming'); InitialContext ic = new InitialContext(); ic.createSubcontext('java:'); ic.createSubcontext('java:comp'); ic.createSubcontext('java:comp/env'); ic.createSubcontext('java:comp/env/jdbc'); EmbeddedDataSource ds = new EmbeddedDataSource(); ds.setDatabaseName('tutorialDB'); // tell Derby to create the database if it does not already exist ds.setCreateDatabase('create'); ic.bind('java:comp/env/jdbc/tutorialDS', ds); } @AfterClass public static void tearDownClass() throws Exception { InitialContext ic = new InitialContext(); ic.unbind('java:comp/env/jdbc/tutorialDS'); }} |
Последний кусок — это контрольный пример. Менеджер сущностей обеспечивает доступ к данным. Операция persist (которая в этом случае приведет к одной вставке) должна быть выполнена в транзакции. На самом деле Hibernate не будет выполнять какую-либо работу до фиксации. Вы можете увидеть это, добавив Thread.sleep непосредственно перед фиксацией.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@Test public void testNewUser() { EntityManager entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager(); entityManager.getTransaction().begin(); User user = new User(); user.setName(Long.toString(new Date().getTime())); entityManager.persist(user); entityManager.getTransaction().commit(); // see that the ID of the user was set by Hibernate System.out.println('user=' + user + ', user.id=' + user.getId()); User foundUser = entityManager.find(User.class, user.getId()); // note that foundUser is the same instance as user and is a concrete class (not a proxy) System.out.println('foundUser=' + foundUser); assertEquals(user.getName(), foundUser.getName()); entityManager.close(); } |
Обработка исключений
Необходимость начать и совершить это многословно. Кроме того, последний пример является неполным, поскольку он пропускает любой откат в случае возникновения исключения.
Исключением является код котельной плиты. Как будто это эквивалент JDBC, это не красиво. Вот пример:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@Test(expected = Exception.class) public void testNewUserWithTxn() throws Exception { EntityManager entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager(); entityManager.getTransaction().begin(); try { User user = new User(); user.setName(Long.toString(new Date().getTime())); entityManager.persist(user); if (true) { throw new Exception(); } entityManager.getTransaction().commit(); } catch (Exception e) { entityManager.getTransaction().rollback(); throw e; } entityManager.close(); } |
Я оставлю управление исключениями на данный момент, так как есть лучшие способы сделать это. Позже мы рассмотрим, как @Inject и Spring Data @Transactional в JSR-330 могут уменьшить котельную пластину.
Отношения сущностей
Поскольку мы используем реляционные базы данных, мы почти наверняка захотим создать связь между сущностями. Мы создадим объект роли и установим связь между многими пользователями и ролью. Чтобы создать объект роли, просто скопируйте объект User, назовите его Role и удалите строку @Table. Нам не нужно создавать сущность UserRole. Но мы хотим добавить и удалить роли от пользователя.
Добавьте следующее поле и метод в пользовательскую таблицу:
|
01
02
03
04
05
06
07
08
09
10
|
@ManyToManyprivate Set<Role> roles = new HashSet<Role>();public boolean addRole(Role role) { return roles.add(role);}public Set<Role> getRoles() { return roles;} |
Аннотация @ManyToMany сообщает JPA, что это отношение многие ко многим. Мы можем проверить это с помощью нового теста. Этот тест создает пользователя и роль в одной транзакции, а затем обновляет пользователя во второй, используя слияние. Слияния используются для обновления объекта в базе данных.
|
01
02
03
04
05
06
07
08
09
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
|
@Test public void testNewUserAndAddRole() { EntityManager entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager(); entityManager.getTransaction().begin(); User user = new User(); user.setName(Long.toString(new Date().getTime())); Role role = new Role(); role.setName(Long.toString(new Date().getTime())); entityManager.persist(user); entityManager.persist(role); entityManager.getTransaction().commit(); assertEquals(0, user.getRoles().size()); entityManager.getTransaction().begin(); user.addRole(role); entityManager.merge(user); entityManager.getTransaction().commit(); assertEquals(1, user.getRoles().size()); entityManager.close(); } |
Запросы
JPA позволяет вам использовать язык запросов с сильным сходством с SQL, который называется JPQL. Запросы могут быть написаны напрямую, но именованные запросы легче контролировать, поддерживать и демонстрировать лучшую производительность, так как Hibernate может подготовить оператор. Они указываются с помощью аннотации @NamedQuery. Добавьте эту строку в класс User после аннотации @Table:
|
1
|
@NamedQuery(name='User.findByName', query = 'select u from User u where u.name = :name') |
Вы можете проверить это следующим образом:
|
01
02
03
04
05
06
07
08
09
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
|
@Testpublic void testFindUser() throws Exception { EntityManager entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager(); entityManager.getTransaction().begin(); User user = new User(); String name = Long.toString(new Date().getTime()); user.setName(name); Role role = new Role(); role.setName(name); user.addRole(role); entityManager.persist(role); entityManager.persist(user); entityManager.getTransaction().commit(); entityManager.close(); entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager(); User foundUser = entityManager.createNamedQuery('User.findByName', User.class).setParameter('name', name) .getSingleResult(); System.out.println(foundUser); assertEquals(name, foundUser.getName()); assertEquals(1, foundUser.getRoles().size()); System.out.println(foundUser.getRoles().getClass()); entityManager.close();} |
В этом примере я закрыл и снова открыл менеджер сущностей. Это заставляет Hibernate запрашивать пользователя из базы данных. Заметили что-нибудь интересное о выходе? SQL для получения ролей появляется после toString найденного пользователя. Hibernate создает прокси-объект для ролей (в данном случае org.hibernate.collection.PersistentSet) и заполняет его только при первом доступе к объекту. Это может привести к нелогичному поведению и имеет свой собственный набор ошибок.
Попробуйте этот вариант вышеприведенного теста, в котором мы закрываем менеджер сущностей перед первым запросом ролей:
|
01
02
03
04
05
06
07
08
09
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
|
@Test(expected = LazyInitializationException.class)public void testFindUser1() throws Exception { EntityManager entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager(); entityManager.getTransaction().begin(); User user = new User(); String name = Long.toString(new Date().getTime()); user.setName(name); Role role = new Role(); role.setName(name); user.addRole(role); entityManager.persist(role); entityManager.persist(user); entityManager.getTransaction().commit(); entityManager.close(); entityManager = Persistence.createEntityManagerFactory('tutorialPU').createEntityManager(); User foundUser = entityManager.createNamedQuery('User.findByName', User.class).setParameter('name', name) .getSingleResult(); entityManager.close(); assertEquals(1, foundUser.getRoles().size());} |
Исключение LazyInitializationException будет вызываться при вызове getRoles (). Это не ошибка. Как только менеджер сущностей закрыт, любая сущность может стать непригодной для использования.
Конец
Это основы для работы с Hibernate JPA. В следующей части этого руководства я расскажу о проверке и рассмотрим некоторые другие детали более подробно.
Ссылка: Учебное пособие: Hibernate, JPA — часть 1 от нашего партнера по JCG Алекса Коллинза в блоге Алекса Коллинза .