Bootstrapping JPA programaticamente em Java

Bootstrapping JPA programaticamente em Java

1. Visão geral

A maioria dos aplicativos orientados a JPA faz uso intenso do arquivo“persistence.xml” para obter uma implementação JPA, comoHibernate ouOpenJPA.

Nossa abordagem aquiprovides a centralized mechanism for configuring one or more persistence units lixa ospersistence contexts associados.

E, embora essa abordagem não seja inerentemente errada, não é adequada para casos de uso em que é necessário testar isoladamente os componentes do aplicativo que usam diferentes unidades de persistência.

Pelo lado bom,it’s possible to bootstrap a JPA implementation without resorting to the “persistence.xml” file at all, by just using plain Java.

Neste tutorial, veremos como fazer isso com o Hibernate.

2. Implementando a interfacePersistenceUnitInfo

Em uma configuração típica de JPA “baseada em xml”, a implementação de JPA automaticamente cuida da implementação dePersistenceUnitInfo interface.

Using all the data gathered by parsing the “persistence.xml” file, to provedor de persistência usa essa implementação para criar uma fábrica de gerenciador de entidade. Desta fábrica, podemos obter um gerente de entidade.

Since we won’t rely on the “persistence.xml” file, the first thing that we need to do is to provide our own PersistenceUnitInfo implementation. Usaremos Hibernate para nosso provedor de persistência:

public class HibernatePersistenceUnitInfo implements PersistenceUnitInfo {

    public static String JPA_VERSION = "2.1";
    private String persistenceUnitName;
    private PersistenceUnitTransactionType transactionType
      = PersistenceUnitTransactionType.RESOURCE_LOCAL;
    private List managedClassNames;
    private List mappingFileNames = new ArrayList<>();
    private Properties properties;
    private DataSource jtaDataSource;
    private DataSource nonjtaDataSource;
    private List transformers = new ArrayList<>();

    public HibernatePersistenceUnitInfo(
      String persistenceUnitName, List managedClassNames, Properties properties) {
        this.persistenceUnitName = persistenceUnitName;
        this.managedClassNames = managedClassNames;
        this.properties = properties;
    }

    // standard setters / getters
}

Em poucas palavras, a classeHibernatePersistenceUnitInfo é apenas um contêiner de dados simples, que armazena os parâmetros de configuração vinculados a uma unidade de persistência específica. Isso inclui o nome da unidade de persistência, os nomes das classes de entidade gerenciada, o tipo de transação, as fontes de dados JTA e não JTA e assim por diante.

3. Criando um Entity Manager Factory com a classeEntityManagerFactoryBuilderImpl do Hibernate

Agora que temos uma implementaçãoPersistenceUnitInfo personalizada em vigor, a última coisa que precisamos fazer é obter uma fábrica de gerenciador de entidades.

Hibernate makes this process a breeze, with its EntityManagerFactoryBuilderImpl class (a neat implementation of the builder pattern).

Para fornecer um nível mais alto de abstração, vamos criar uma classe que envolve a funcionalidade deEntityManagerFactoryBuilderImpl.

Primeiro, vamos mostrar os métodos que cuidam decreating an entity manager factory and an entity manager, usando a classeHibernate’s EntityManagerFactoryBuilderImpl e nossoHibernatePersistenceUnitInfassim classe:

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 configuration = new HashMap<>();
        return new EntityManagerFactoryBuilderImpl(
          new PersistenceUnitInfoDescriptor(persistenceUnitInfo), configuration)
          .build();
    }

    protected HibernatePersistenceUnitInfo getPersistenceUnitInfo(String name) {
        return new HibernatePersistenceUnitInfo(name, getEntityClassNames(), getProperties());
    }

    // additional methods
}

A seguir, vamos dar uma olhada emmethods that provide the parameters required by EntityManagerFactoryBuilderImpl and HibernatePersistenceUnitInfo.

Esses parâmetros incluem as classes de entidade gerenciadas, os nomes das classes de entidade, as propriedades de configuração do Hibernate e um objetoMysqlDataSource:

public class JpaEntityManagerFactory {
    //...

    protected List 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;
    }
}

Para simplificar, codificamos os parâmetros de conexão do banco de dados dentro da classeJpaEntityManagerFactory. Na produção, porém, devemos armazená-los em um arquivo de propriedades separado.

Além disso, o métodogetMysqlDataSource() retorna um objetoMysqlDataSource totalmente inicializado.

Fizemos isso apenas para manter as coisas fáceis de seguir. Em um design mais realista e fracamente acoplado, teríamosinject a DataSource object using EntityManagerFactoryBuilderImpl's withDataSource() method, rather than creating it within the class.

4. Execução de operações CRUD com um gerente de entidade

Finalmente, vamos ver como usar uma instânciaJpaEntityManagerFactory para obter um gerenciador de entidade JPA e realizar operações CRUD. (Observe que omitimos a classeUser por questão de brevidade):

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;
}

5. Conclusão

Neste artigo, mostramos como inicializar programaticamente um gerenciador de entidade JPA usando uma implementação customizada dePersistenceUnitInfo interface and Hibernate’s EntityManagerFactoryBuilderImpl class de JPA, sem ter que depender do “persistence.xml” file. tradicional

Como de costume, todos os exemplos de código mostrados neste artigo estão disponíveisover on GitHub.