使用Java程式設計引導JPA

qianmoQ發表於2019-01-19

案例概述

大多數JPA驅動的應用程式大量使用“persistence.xml”檔案來獲取JPA實現,例如HibernateOpenJPA

我們的方法提供了一種集中式機制,用於配置一個或多個永續性單元 和相關的永續性上下文。

雖然這種方法本身並不是錯誤的,但它並不適用於需要單獨測試使用不同永續性單元的應用程式元件的用例。

從好的方面來說,只需使用普通的Java就可以在不使用“persistence.xml”檔案的情況下引導JPA實現。

在本文中,我們將看到如何使用Hibernate完成此任務。

實現PersistenceUnitInfo介面

在典型的“基於xml”的JPA配置中,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資料來源等。

使用Hibernate的EntityManagerFactoryBuilderImpl類建立實體管理器工廠

現在我們已經實現了自定義PersistenceUnitInfo實現,我們需要做的最後一件事就是獲得一個實體管理器工廠。

Hibernate使用EntityManagerFactoryBuilderImpl類(構建器模式的簡潔實現)使這個過程變得輕而易舉。

為了提供更高階別的抽象,讓我們建立一個包含EntityManagerFactoryBuilderImpl功能的類。

首先,讓我們展示使用Hibernate的EntityManagerFactoryBuilderImpl類和 HibernatePersistenceUnitInf類建立實體管理器工廠和實體管理器的方法 :

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物件。

我們這樣做只是為了讓事情更容易理解。在一種更實際、鬆耦合的設計中,我們將使用EntityManagerFactoryBuilderImpl的withDataSource()方法注入資料來源物件,而不是在類中建立它。

使用實體管理器執行CRUD操作

最後,讓我們看看如何使用JpaEntityManagerFactory例項獲取JPA實體管理器並執行CRUD操作。(請注意,為簡潔起見,我們省略了User類):

public static void main(String[] args) {
    EntityManager entityManager = getJpaEntityManager();
    User user = entityManager.find(User.class, 1);
     
    entityManager.getTransaction().begin();
    user.setName("John");
    user.setEmail("john@domain.com");
    entityManager.merge(user);
    entityManager.getTransaction().commit();
     
    entityManager.getTransaction().begin();
    entityManager.persist(new User("Monica", "monica@domain.com"));
    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;
}

案例結論

在本文中,我們展示瞭如何使用JPA的PersistenceUnitInfo介面和Hibernate的EntityManagerFactoryBuilderImpl類的自定義實現以程式設計方式引導JPA實體管理器,而不必依賴傳統的“persistence.xml”檔案。

相關文章