«1. Обзор

Большинство приложений, управляемых JPA, интенсивно используют файл «persistence.xml» для получения реализации JPA, такой как Hibernate или OpenJPA.

Наш подход здесь предоставляет централизованный механизм для настройки одного или нескольких модулей сохраняемости и связанных с ними контекстов сохраняемости.

И хотя этот подход по своей сути не является неправильным, он не подходит для случаев использования, когда необходимо изолированно тестировать компоненты приложения, использующие разные единицы сохраняемости.

С другой стороны, можно запустить реализацию JPA, вообще не прибегая к файлу «persistence.xml», просто используя обычную Java.

В этом уроке мы увидим, как это сделать с помощью Hibernate.

2. Реализация интерфейса PersistenceUnitInfo

В типичной конфигурации JPA, основанной на XML, реализация JPA автоматически заботится о реализации интерфейса PersistenceUnitInfo.

Используя все данные, собранные при анализе файла «persistence.xml», поставщик сохраняемости использует эту реализацию для создания фабрики диспетчера сущностей. Из этой фабрики мы можем получить менеджера сущностей.

Поскольку мы не будем полагаться на файл «persistence.xml», первое, что нам нужно сделать, это предоставить нашу собственную реализацию PersistenceUnitInfo. Мы будем использовать Hibernate в качестве нашего поставщика сохраняемости:

public class HibernatePersistenceUnitInfo implements PersistenceUnitInfo {
    
    public static String JPA_VERSION = "2.1";
    private String persistenceUnitName;
    private PersistenceUnitTransactionType transactionType
      = PersistenceUnitTransactionType.RESOURCE_LOCAL;
    private List<String> managedClassNames;
    private List<String> mappingFileNames = new ArrayList<>();
    private Properties properties;
    private DataSource jtaDataSource;
    private DataSource nonjtaDataSource;
    private List<ClassTransformer> transformers = new ArrayList<>();
    
    public HibernatePersistenceUnitInfo(
      String persistenceUnitName, List<String> managedClassNames, Properties properties) {
        this.persistenceUnitName = persistenceUnitName;
        this.managedClassNames = managedClassNames;
        this.properties = properties;
    }

    // standard setters / getters   
}

В двух словах, класс HibernatePersistenceUnitInfo — это просто контейнер данных, в котором хранятся параметры конфигурации, привязанные к определенной единице сохраняемости. Сюда входят имя единицы сохраняемости, имена классов управляемых объектов, тип транзакции, источники данных JTA и не-JTA и т. д.

3. Создание фабрики менеджера сущностей с помощью класса EntityManagerFactoryBuilderImpl Hibernate

Теперь, когда у нас есть собственная реализация PersistenceUnitInfo, последнее, что нам нужно сделать, это получить фабрику менеджера сущностей.

Hibernate упрощает этот процесс благодаря классу EntityManagerFactoryBuilderImpl (аккуратная реализация шаблона компоновщика).

Чтобы обеспечить более высокий уровень абстракции, давайте создадим класс, обертывающий функциональность EntityManagerFactoryBuilderImpl.

Во-первых, давайте продемонстрируем методы, которые заботятся о создании фабрики диспетчера сущностей и диспетчера сущностей, используя класс EntityManagerFactoryBuilderImpl Hibernate и наш класс HibernatePersistenceUnitInfo:

public class JpaEntityManagerFactory {
    private String DB_URL = "jdbc:mysql://databaseurl";
    private String DB_USER_NAME = "username";
    private String DB_PASSWORD = "password";
    private Class[] entityClasses;
    
    public JpaEntityManagerFactory(Class[] entityClasses) {
        this.entityClasses = entityClasses;
    }
    
    public EntityManager getEntityManager() {
        return getEntityManagerFactory().createEntityManager();
    }
    
    protected EntityManagerFactory getEntityManagerFactory() {
        PersistenceUnitInfo persistenceUnitInfo = getPersistenceUnitInfo(
          getClass().getSimpleName());
        Map<String, Object> configuration = new HashMap<>();
        return new EntityManagerFactoryBuilderImpl(
          new PersistenceUnitInfoDescriptor(persistenceUnitInfo), configuration)
          .build();
    }
    
    protected HibernatePersistenceUnitInfo getPersistenceUnitInfo(String name) {
        return new HibernatePersistenceUnitInfo(name, getEntityClassNames(), getProperties());
    }

    // additional methods
}

Далее давайте рассмотрим методы, которые обеспечивают параметры, необходимые для EntityManagerFactoryBuilderImpl и HibernatePersistenceUnitInfo.

Эти параметры включают классы управляемых сущностей, имена классов сущностей, свойства конфигурации Hibernate и объект MysqlDataSource:

public class JpaEntityManagerFactory {
    //...
    
    protected List<String> getEntityClassNames() {
        return Arrays.asList(getEntities())
          .stream()
          .map(Class::getName)
          .collect(Collectors.toList());
    }
    
    protected Properties getProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
        properties.put("hibernate.id.new_generator_mappings", false);
        properties.put("hibernate.connection.datasource", getMysqlDataSource());
        return properties;
    }
    
    protected Class[] getEntities() {
        return entityClasses;
    }
    
    protected DataSource getMysqlDataSource() {
        MysqlDataSource mysqlDataSource = new MysqlDataSource();
        mysqlDataSource.setURL(DB_URL);
        mysqlDataSource.setUser(DB_USER_NAME);
        mysqlDataSource.setPassword(DB_PASSWORD);
        return mysqlDataSource;
    }
}

Для простоты мы жестко закодировали параметры подключения к базе данных в классе JpaEntityManagerFactory. . Однако в рабочей среде мы должны хранить их в отдельном файле свойств.

Кроме того, метод getMysqlDataSource() возвращает полностью инициализированный объект MysqlDataSource.

Мы сделали это только для того, чтобы вам было легко следить за вещами. В более реалистичном, слабосвязанном дизайне мы бы внедрили объект DataSource, используя метод EntityManagerFactoryBuilderImpl withDataSource(), вместо того, чтобы создавать его внутри класса.

4. Выполнение операций CRUD с помощью диспетчера сущностей

Наконец, давайте посмотрим, как использовать экземпляр JpaEntityManagerFactory для получения диспетчера сущностей JPA и выполнения операций CRUD. (Обратите внимание, что для краткости мы опустили класс User). Класс EntityManagerFactoryBuilderImpl Hibernate без необходимости полагаться на традиционный файл «persistence.xml».

public static void main(String[] args) {
    EntityManager entityManager = getJpaEntityManager();
    User user = entityManager.find(User.class, 1);
    
    entityManager.getTransaction().begin();
    user.setName("John");
    user.setEmail("[email protected]");
    entityManager.merge(user);
    entityManager.getTransaction().commit();
    
    entityManager.getTransaction().begin();
    entityManager.persist(new User("Monica", "[email protected]"));
    entityManager.getTransaction().commit();
 
    // additional CRUD operations
}

private static class EntityManagerHolder {
    private static final EntityManager ENTITY_MANAGER = new JpaEntityManagerFactory(
      new Class[]{User.class})
      .getEntityManager();
}

public static EntityManager getJpaEntityManager() {
    return EntityManagerHolder.ENTITY_MANAGER;
}

Как обычно, все примеры кода, показанные в этой статье, доступны на GitHub.

«

As usual, all the code samples shown in this article are available over on GitHub.