Статьи

Учебник: Hibernate, JPA — Часть 1

Это первая часть руководства по использованию Hibernate и JPA. Эта часть представляет собой введение в JPA и Hibernate. Вторая часть будет посвящена созданию приложения Spring MVC с использованием Spring ORM для уменьшения объема кода, необходимого для создания приложения CRUD.

Для этого вам нужно ознакомиться с Maven, JUnit, SQL и реляционными базами данных.

зависимости

Для начала нам понадобится пара основных зависимостей. По сути, есть три слоя:

  1. Самый нижний уровень — это драйверы JDBC, используемые Hibernate для подключения к базе данных. Я собираюсь использовать Derby, простую встроенную базу данных. Там нет сервера для установки или настройки, так что проще настроить, чем даже MySQL или PostgreSQL; это не подходит для производства.
  2. Средний слой — библиотеки Hibernate. Я собираюсь использовать версию 3.5.6. Это работает с Java 1.5, 4.x нет.
  3. Библиотеки 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'?>
 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
@ManyToMany
private 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
@Test
public 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 Алекса Коллинза в блоге Алекса Коллинза .