如何在没有 persistence.xml 配置文件的情况下以编程方式引导 JPA

上一篇我们知道了如何用编程的方式创建一个EntityManagerFactory;今天我们用另外一种更加通用方式来创建EntityManagerFactory。这种方式是基于标准的java持久化API。

在上一节,我们用到了一个接口,这个类就是PersistenceUnitInfo,JPA 规范 PersistenceUnitInfo 接口封装了引导 EntityManagerFactory 所需的一切。通常,此接口由 JPA 提供程序实现,以存储在解析 persistence.xml 配置文件后检索到的信息。

因为我们将不再使用persistence.xml 配置文件,所以我们必须自己实现这个接口。出于此测试的目的,请考虑以下实现:

public class PersistenceUnitInfoImpl
        implements PersistenceUnitInfo {
 
    public static final String JPA_VERSION = "2.1";
 
    private final String persistenceUnitName;
 
    private PersistenceUnitTransactionType transactionType =
        PersistenceUnitTransactionType.RESOURCE_LOCAL;
 
    private final List<String> managedClassNames;
 
    private final List<String> mappingFileNames = new ArrayList<>();
 
    private final Properties properties;
 
    private DataSource jtaDataSource;
 
    private DataSource nonJtaDataSource;
 
    public PersistenceUnitInfoImpl(
            String persistenceUnitName,
            List<String> managedClassNames,
            Properties properties) {
        this.persistenceUnitName = persistenceUnitName;
        this.managedClassNames = managedClassNames;
        this.properties = properties;
    }
 
    @Override
    public String getPersistenceUnitName() {
        return persistenceUnitName;
    }
 
    @Override
    public String getPersistenceProviderClassName() {
        return HibernatePersistenceProvider.class.getName();
    }
 
    @Override
    public PersistenceUnitTransactionType getTransactionType() {
        return transactionType;
    }
 
    @Override
    public DataSource getJtaDataSource() {
        return jtaDataSource;
    }
 
    public PersistenceUnitInfoImpl setJtaDataSource(
            DataSource jtaDataSource) {
        this.jtaDataSource = jtaDataSource;
        this.nonJtaDataSource = null;
        transactionType = PersistenceUnitTransactionType.JTA;
        return this;
    }
 
    @Override
    public DataSource getNonJtaDataSource() {
        return nonJtaDataSource;
    }
 
    public PersistenceUnitInfoImpl setNonJtaDataSource(
            DataSource nonJtaDataSource) {
        this.nonJtaDataSource = nonJtaDataSource;
        this.jtaDataSource = null;
        transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL;
        return this;
    }
 
    @Override
    public List<String> getMappingFileNames() {
        return mappingFileNames;
    }
 
    @Override
    public List<URL> getJarFileUrls() {
        return Collections.emptyList();
    }
 
    @Override
    public URL getPersistenceUnitRootUrl() {
        return null;
    }
 
    @Override
    public List<String> getManagedClassNames() {
        return managedClassNames;
    }
 
    @Override
    public boolean excludeUnlistedClasses() {
        return false;
    }
 
    @Override
    public SharedCacheMode getSharedCacheMode() {
        return SharedCacheMode.UNSPECIFIED;
    }
 
    @Override
    public ValidationMode getValidationMode() {
        return ValidationMode.AUTO;
    }
 
    public Properties getProperties() {
        return properties;
    }
 
    @Override
    public String getPersistenceXMLSchemaVersion() {
        return JPA_VERSION;
    }
 
    @Override
    public ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }
 
    @Override
    public void addTransformer(ClassTransformer transformer) {
 
    }
 
    @Override
    public ClassLoader getNewTempClassLoader() {
        return null;
    }
}

为了更容易地重用引导逻辑,我们可以定义一个 AbstractJPAProgrammaticBootstrapTest 基类,它将由我们所有想要在没有外部 persistence.xml 配置文件的情况下引导的单元测试扩展。

AbstractJPAProgrammaticBootstrapTest 在开始新测试时创建一个 EntityManagerFactory,并在执行单元测试后将其关闭。这样,所有测试都是独立运行的,每个测试类也可以是独立的。

private EntityManagerFactory emf;
 
public EntityManagerFactory entityManagerFactory() {
    return emf;
}
 
@Before
public void init() {
    PersistenceUnitInfo persistenceUnitInfo = persistenceUnitInfo(
        getClass().getSimpleName()
    );
 
    Map<String, Object> configuration = new HashMap<>();
 
    Integrator integrator = integrator();
    if (integrator != null) {
        configuration.put(
            "hibernate.integrator_provider",
            (IntegratorProvider) () ->
                Collections.singletonList(integrator)
        );
    }
 
    emf = new HibernatePersistenceProvider()
    .createContainerEntityManagerFactory(
        persistenceUnitInfo,
        configuration
    );
}
 
@After
public void destroy() {
    emf.close();
}

JPA 标准定义了 PersistenceProvider 接口,定义了用于实例化新 EntityManagerFactory 的契约。我们将使用 HibernatePersistenceProvider,它是此接口的特定于 Hibernate 的实现。如果您想使用不同的 JPA 提供程序,则必须检查 PersistenceProvider 实现的提供程序 API 并改用它。

现在,让我们看看 persistenceUnitInfo 长什么样:

protected PersistenceUnitInfoImpl persistenceUnitInfo(String name) {
    PersistenceUnitInfoImpl persistenceUnitInfo = new PersistenceUnitInfoImpl(
        name, entityClassNames(), properties()
    );
 
    String[] resources = resources();
    if (resources != null) {
        persistenceUnitInfo.getMappingFileNames().addAll(
            Arrays.asList(resources)
        );
    }
 
    return persistenceUnitInfo;
}

实体类和关联的 XML 配置由以下方法定义:

protected abstract Class<?>[] entities();
 
protected String[] resources() {
    return null;
}
 
protected List<String> entityClassNames() {
    return Arrays.asList(entities())
    .stream()
    .map(Class::getName)
    .collect(Collectors.toList());
}

因此,我们可以简单地实现实体或扩展资源方法以编程方式提供 JPA 映射信息。

properties 方法定义了所有测试所需的一些基本属性,例如自动生成模式或提供 JDBC 数据源以连接到底层数据库。

protected Properties properties() {
    Properties properties = new Properties();
     
    properties.put(
        "hibernate.dialect",
        dataSourceProvider().hibernateDialect()
    );
     
    properties.put(
        "hibernate.hbm2ddl.auto",
        "create-drop"
    );
     
    DataSource dataSource = newDataSource();
     
    if (dataSource != null) {
        properties.put(
            "hibernate.connection.datasource",
            dataSource
        );
    }
     
    properties.put(
        "hibernate.generate_statistics",
        Boolean.TRUE.toString()
    );
 
    return properties;
}

当然,我们可以扩展属性基类的方法来提供额外的属性。

newDataSource 方法定义如下:

protected DataSource newDataSource() {
   return proxyDataSource()
        ? dataSourceProxyType().dataSource(
            dataSourceProvider().dataSource())
        : dataSourceProvider().dataSource();
}
 
protected DataSourceProxyType dataSourceProxyType() {
    return DataSourceProxyType.DATA_SOURCE_PROXY;
}
 
protected boolean proxyDataSource() {
    return true;
}
 
protected DataSourceProvider dataSourceProvider() {
    return database().dataSourceProvider();
}
 
protected Database database() {
    return Database.HSQLDB;
}

dataSourceProxyType 允许我们代理底层的 JDBC 数据源,这样我们就可以使用 datasource-proxy 开源项目来记录 SQL 语句及其绑定参数值。

但是,您不仅限于标准 JPA 引导,因为 Hibernate 允许您通过 Integrator 机制集成您自己的引导逻辑。

默认情况下,我们不提供任何 Integrator:

protected Integrator integrator() {
    return null;
}

但是如果我们提供一个集成器,这个集成器将通过 hibernate.integrator_provider 配置属性传递给 Hibernate。

现在我们有了 AbstractJPAProgrammaticBootstrapTest,具体测试如下所示:

public class BootstrapTest
    extends AbstractJPAProgrammaticBootstrapTest {
 
    @Override
    protected Class<?>[] entities() {
        return new Class[] {
            Post.class
        };
    }
     
    @Test
    public void test() {
        doInJPA(entityManager -> {
            for (long id = 1; id <= 3; id++) {
                Post post = new Post();
                post.setId(id);
                post.setTitle(
                    String.format(
                        "High-Performance Java Persistence, Part %d", id
                    )
                );
                entityManager.persist(post);
            }
        });
    }
 
    @Entity(name = "Post")
    @Table(name = "post")
    public static class Post {
 
        @Id
        private Long id;
 
        private String title;
 
        //Getters and setters omitted for brevity
    }
}

我们只需扩展 AbstractJPAProgrammaticBootstrapTest 基类并定义我们要使用的实体。请注意,实体仅与此测试相关联,以便我们确保实体映射更改不会波及其他测试。

至于这个doInJPA工具方法的具体实现,这个以后再讨论吧。欢迎大家留言交流。

版权声明:著作权归作者所有。

thumb_up 0 | star_outline 0 | textsms 0