我喜欢集成测试,这是检查Hibernate生成哪些幕后花絮的SQL查询的好方法。 但是集成测试需要运行的数据库服务器,这是您必须要做的第一选择。

1.使用类似生产的本地数据库服务器进行集成测试

对于生产环境,我始终喜欢使用增量DDL脚本,因为我始终可以知道在给定服务器上部署了什么版本,以及哪些是需要部署的较新脚本。 我一直依靠Flyway来为我管理模式更新,对此我非常满意。

在一个集成测试量很小的小型项目中,您也可以使用类似于生产的本地数据库服务器进行测试。 这是最安全的选择,因为它可以确保您在生产设置非常相似的环境下进行测试。

主要缺点是测试速度。 使用外部数据库意味着额外的时序成本,这很容易在大型项目中失控。 毕竟,谁喜欢每天运行60分钟的测试程序?

2.内存数据库集成测试

我选择使用内存数据库进行集成测试的原因是为了加快测试的运行时间。 这是影响测试运行时间的一个方面,还有许多其他方面可能会影响您,例如销毁和重新创建包含大量bean依赖项的Spring应用程序上下文。

您可以选择许多内存数据库: HSQLDB , H2 , Apache Derby ,仅举几例。

我一直在使用两种内存中模式生成策略,它们都有优点和缺点,下面将对它们进行解释。

2.1利用hibernate.hbm2ddl.auto =” update”

Hibernate在配置它时非常灵活。 幸运的是,我们可以使用“ hibernate.hbm2ddl.auto” SessionFactory属性来自定义DDL生成。

部署架构的最简单方法是使用“更新”选项。 这对于测试目的很有用。 在生产环境中我不会依赖它,对于增量环境而言,增量DDL脚本是一种更好的方法。

因此,选择“更新”选项是Integration Testing架构管理的一种选择。

这就是我在Hibernate Facts代码示例中使用它的方式。

让我们从JPA配置开始,您可以在META-INF / persistence.xml文件中找到:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
             xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="testPersistenceUnit" transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>

        <properties>
            <property name="hibernate.archive.autodetection"
                      value="class, hbm"/>
            <property name="hibernate.transaction.jta.platform"
                      value="org.hibernate.service.jta.platform.internal.BitronixJtaPlatform" />
            <property name="hibernate.dialect"
                      value="org.hibernate.dialect.HSQLDialect"/>
            <em><property name="hibernate.hbm2ddl.auto"
                      value="update"/></em>
            <property name="hibernate.show_sql"
                      value="true"/>
        </properties>
    </persistence-unit>
</persistence>

并且dataSource配置如下所示:

<bean id="dataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
        <constructor-arg>
            <bean class="bitronix.tm.resource.jdbc.PoolingDataSource" init-method="init"
                  destroy-method="close">
                <property name="className" value="bitronix.tm.resource.jdbc.lrc.LrcXADataSource"/>
                <property name="uniqueName" value="testDataSource"/>
                <property name="minPoolSize" value="0"/>
                <property name="maxPoolSize" value="5"/>
                <property name="allowLocalTransactions" value="true" />
                <property name="driverProperties">
                    <props>
                        <prop key="user">${jdbc.username}</prop>
                        <prop key="password">${jdbc.password}</prop>
                        <prop key="url">${jdbc.url}</prop>
                        <prop key="driverClassName">${jdbc.driverClassName}</prop>
                    </props>
                </property>
            </bean>
        </constructor-arg>
    </bean>

我认为Bitronix是我使用过的最可靠的工具之一。 在开发JEE应用程序时,我利用了使用中的Application Server提供的Transaction Manager。 对于基于Spring的项目,我必须雇用独立的事务管理器,在评估JOTM,Atomikos和Bitronix之后,我选择了Bitronix。 那是5年前,自从我部署并使用了几个应用程序以来。

即使应用程序当前仅使用一个数据源,我还是更喜欢使用XA Transactions。 我不必担心使用JTA会明显降低性能,因为当当前事务仅使用一个登记的数据源时,Bitronix使用1PC(单阶段提交) 。 由于最后一次资源提交的优化,它还可以添加一个非XA数据源。

使用JTA时,建议不要混合使用XA和本地事务,因为并非所有XA数据源都允许在本地事务中进行操作,因此我倾向于尽可能避免这种情况。

不幸的是,就像这种DDL生成方法一样简单,它有一个我不太喜欢的缺陷。 我不能禁用“ allowLocalTransactions”设置,因为Hibernate会创建DDL脚本并在XA事务之外对其进行更新。

另一个缺点是,您几乎无法控制Hibernate代表您部署的DDL脚本,在这种特定的情况下,我不希望牺牲灵活性而不是方便性。

如果您不使用JTA并且不需要决定在当前数据库服务器上部署哪种DDL模式的灵活性,那么hibernate.hbm2ddl.auto =“ update”可能是您的正确选择。

2.2灵活的架构部署

此方法包括两个步骤。 前者将使Hibernate生成DDL脚本,而后者将以自定义方式部署它们。

要生成DDL脚本,我必须使用以下Ant任务(即使正在通过Maven运行),这是因为在编写本文时,没有可以使用的Hibernate 4 Maven插件:

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-antrun-plugin</artifactId>
	<executions>
		<execution>
			<id>generate-test-sql-scripts</id>
			<phase>generate-test-resources</phase>
			<goals>
				<goal>run</goal>
			</goals>
			<configuration>
				<tasks>
					<property name="maven_test_classpath" refid="maven.test.classpath"/>
					<path id="hibernate_tools_path">
						<pathelement path="${maven_test_classpath}"/>
					</path>
					<property name="hibernate_tools_classpath" refid="hibernate_tools_path"/>
					<taskdef name="hibernatetool"
							 classname="org.hibernate.tool.ant.HibernateToolTask"/>
					<mkdir dir="${project.build.directory}/test-classes/hsqldb"/>
					<hibernatetool destdir="${project.build.directory}/test-classes/hsqldb">
						<classpath refid="hibernate_tools_path"/>
						<jpaconfiguration persistenceunit="testPersistenceUnit"
										  propertyfile="src/test/resources/META-INF/spring/jdbc.properties"/>
						<hbm2ddl drop="false" create="true" export="false"
								 outputfilename="create_db.sql"
								 delimiter=";" format="true"/>
						<hbm2ddl drop="true" create="false" export="false"
								 outputfilename="drop_db.sql"
								 delimiter=";" format="true"/>
					</hibernatetool>
				</tasks>
			</configuration>
		</execution>
	</executions>
	...
</plugin>

有了“创建”和“删除” DDl脚本后,我们现在必须在Spring上下文启动时部署它们,这是使用以下自定义Utility类完成的:

public class DatabaseScriptLifecycleHandler implements InitializingBean, DisposableBean {

    private final Resource[] initScripts;
    private final Resource[] destroyScripts;

    private JdbcTemplate jdbcTemplate;

    @Autowired
    private TransactionTemplate transactionTemplate;

    private String sqlScriptEncoding = "UTF-8";
    private String commentPrefix = "--";
    private boolean continueOnError;
    private boolean ignoreFailedDrops;

	public DatabaseScriptLifecycleHandler(DataSource dataSource,
                                          Resource[] initScripts,
                                          Resource[] destroyScripts) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.initScripts = initScripts;
        this.destroyScripts = destroyScripts;
    }

    public void afterPropertiesSet() throws Exception {
        initDatabase();
    }

    public void destroy() throws Exception {
        destroyDatabase();
    }

    public void initDatabase() {
        final ResourceDatabasePopulator resourceDatabasePopulator = createResourceDatabasePopulator();
        transactionTemplate.execute(new TransactionCallback<Void>() {
            @Override
            public Void doInTransaction(TransactionStatus status) {
                jdbcTemplate.execute(new ConnectionCallback<Void>() {
                    @Override
                    public Void doInConnection(Connection con) throws SQLException, DataAccessException {
                        resourceDatabasePopulator.setScripts(getInitScripts());
                        resourceDatabasePopulator.populate(con);
                        return null;
                    }
                });
                return null;
            }
        });
    }

    public void destroyDatabase() {
        final ResourceDatabasePopulator resourceDatabasePopulator = createResourceDatabasePopulator();
        transactionTemplate.execute(new TransactionCallback<Void>() {
            @Override
            public Void doInTransaction(TransactionStatus status) {
                jdbcTemplate.execute(new ConnectionCallback<Void>() {
                    @Override
                    public Void doInConnection(Connection con) throws SQLException, DataAccessException {
                        resourceDatabasePopulator.setScripts(getDestroyScripts());
                        resourceDatabasePopulator.populate(con);
                        return null;
                    }
                });
                return null;
            }
        });
    }

	protected ResourceDatabasePopulator createResourceDatabasePopulator() {
		ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator();
		resourceDatabasePopulator.setCommentPrefix(getCommentPrefix());
		resourceDatabasePopulator.setContinueOnError(isContinueOnError());
		resourceDatabasePopulator.setIgnoreFailedDrops(isIgnoreFailedDrops());
		resourceDatabasePopulator.setSqlScriptEncoding(getSqlScriptEncoding());
		return resourceDatabasePopulator;
	}
}

配置为:

<bean id="databaseScriptLifecycleHandler" class="vladmihalcea.util.DatabaseScriptLifecycleHandler"
	  depends-on="transactionManager">
	<constructor-arg name="dataSource" ref="dataSource"/>
	<constructor-arg name="initScripts">
		<array>
			<bean class="org.springframework.core.io.ClassPathResource">
				<constructor-arg value="hsqldb/create_db.sql"/>
			</bean>
		</array>
	</constructor-arg>
	<constructor-arg name="destroyScripts">
		<array>
			<bean class="org.springframework.core.io.ClassPathResource">
				<constructor-arg value="hsqldb/drop_db.sql"/>
			</bean>
		</array>
	</constructor-arg>
</bean>

这次我们可以摆脱任何本地交易,因此可以安全地设置:

<property name="allowLocalTransactions" value="false" />
  • 代码可在GitHub上获得 。

参考: Hibernate Facts:来自JCG合作伙伴 Vlad Mihalcea的集成测试策略,来自Vlad Mihalcea的Blog博客。

翻译自: https://www.javacodegeeks.com/2013/12/hibernate-facts-integration-testing-strategies.html