如何对 seed/clean 数据库和 运行 测试使用不同的数据源?
How to use different datasources to seed/clean database and running the tests?
我们正在尝试为我们的项目设置 Arquillian 运行 自动测试。我们想利用 arquillian 持久性扩展来编写使用持久层的测试。所以我们想使用 @UsingDataSet
and/or @CreateSchema@
注释来为数据库做种。
我们所有的应用程序组件都有自己的数据库用户,这些用户只能访问组件需要的那些 tables/attributes。 None 个组件有权执行删除或 DDL 语句。所以我们需要在数据库 user/datasource seed/clean 模式 before/after 测试和执行测试之间切换,如下所示:
- 种子数据库,使用数据源 A 删除和重新创建序列
- 运行 使用数据源 B 的测试
- 使用数据源 A 清理数据库
很明显,如果我们将所需的 delete/DDL-rights 授予组件数据库用户以进行 arquillian 测试,则根据定义测试结果将不可靠。
那么我们如何使用 arquillian.xml 中定义的不同数据源到 seed/clean 数据库和 运行 测试?
目前还不支持(我们正在努力),但是在版本 2.0.0(目前处于 alpha 阶段,在 maven central 上)有一个解决方法,使用编程方式而不是声明方式(使用注释)。您可以在此处查看示例 https://github.com/arquillian/arquillian-extension-persistence/blob/2.0.0/arquillian-ape-sql/container/int-tests/src/test/java/org/arquillian/integration/ape/dsl/ApeDslIncontainerTest.java
我访问过的 arquillian 培训课程的讲师提到,为 EJB 的 seeding/cleaning 和 PersistenceContext
定义不同的数据源应该没有问题。所以我坐下来测试一下。
TL;DR: It's possible to just use two different datasources.
这是我的测试设置和测试结果。
本地测试设置
数据库
作为数据库,我安装了一个 Oracle XE
,因为我们在公司使用 Oracle 数据库。由于组件的数据库用户没有自己的架构,而是访问架构所有者的 table,我创建了三个数据库用户:
- 用户“bish”是模式“bish”的模式所有者,其中包含 empty table 我在测试中使用的“Emp”
- 用户“readinguser”获得了 table“bish.Emp”
的“SELECT、INSERT、UPDATE”权限
- 用户“writinguser”获得了 table“bish.Emp”
的“SELECT、INSERT、UPDATE、DELETE”权限
应用服务器
作为应用程序服务器,我使用了一个 Wildfly 10.x
并定义了两个数据源,一个用于我的两个测试用户
<datasource jndi-name="java:/ReadingDS" pool-name="ReadingDS" enabled="true">
<connection-url>jdbc:oracle:thin:@localhost:1521:xe</connection-url>
<driver>oracle</driver>
<pool>
<min-pool-size>1</min-pool-size>
<max-pool-size>5</max-pool-size>
<prefill>true</prefill>
</pool>
<security>
<user-name>readinguser</user-name>
<password>oracle</password>
</security>
</datasource>
<datasource jndi-name="java:/WritingDS" pool-name="WritingDS" enabled="true">
<connection-url>jdbc:oracle:thin:@localhost:1521:xe</connection-url>
<driver>oracle</driver>
<pool>
<min-pool-size>1</min-pool-size>
<max-pool-size>5</max-pool-size>
<prefill>true</prefill>
</pool>
<security>
<user-name>writingguser</user-name>
<password>oracle</password>
</security>
</datasource>
测试应用程序
然后我写了一个带有实体的小应用程序,还有EJB,persistence.xml,arquillian.xml,数据集和测试class
实体(仅显示 table 具有显式模式命名的定义)
@Entity
@Table(name = "Emp", schema = "bish")
public class Emp implements Serializable {
// Straight forward entity...
}
具有两种方法的 EJB select创建和删除所有条目
@Stateless
@Remote(IEmpService.class)
@LocalBean
public class EmpService implements IEmpService {
@PersistenceContext
private EntityManager em;
public void removeAllEmps() {
em.createQuery("DELETE FROM Emp").executeUpdate();
}
public List<Emp> getAllEmps() {
return em.createQuery("FROM Emp", Emp.class).getResultList();
}
}
persistence.xml 内的持久性单元使用 EJB 内的“ReadingDS”
<persistence-unit name="ReadingUnit" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/ReadingDS</jta-data-source>
<shared-cache-mode>NONE</shared-cache-mode>
</persistence-unit>
Arquillian.xml 使用“WritingDS”的定义 seed/clean table 和模式定义
<extension qualifier="persistence">
<property name="defaultDataSeedStrategy">CLEAN_INSERT</property>
<property name="defaultCleanupStrategy">USED_ROWS_ONLY</property>
<property name="defaultDataSource">java:/WritingDS</property>
</extension>
<extension qualifier="persistence-dbunit">
<property name="schema">bish</property>
</extension>
测试中使用的数据集“empBefore.xml”class
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<EMP EMPNO="9998" ENAME="TEst" JOB="Eins" HIREDATE="1982-01-23" SAL="1300" DEPTNO="10"/>
<EMP EMPNO="9999" ENAME="Test" JOB="Zwei" MGR="9998" HIREDATE="1982-01-23" SAL="1300" DEPTNO="10"/>
</dataset>
测试class:
@RunWith(Arquillian.class)
public class DataSourceTest {
@Deployment
public static JavaArchive createDeployment() {
// ...
}
@EJB
EmpService testclass;
@Rule
public ExpectedException thrown = ExpectedException.none();
@UsingDataSet("empBefore.xml")
@Test
public void GetAllEmps() {
List<Emp> allEmps = testclass.getAllEmps();
Assert.assertEquals(2, allEmps.size());
}
@UsingDataSet("empBefore.xml")
@Test
public void DeleteAllEmps() {
thrown.expect(EJBException.class);
thrown.expectCause(CoreMatchers.isA(PersistenceException.class));
testclass.removeAllEmps();
}
}
测试
我首先执行了 GetAllEmps
测试方法来查看 table 是否正确地使用了 DataSet
的数据以及 EJB 的 select 方法作品。在我第一次执行时,我得到了以下异常。 (很抱歉发布了这么多文字,但这很重要,请参见下文!)
19:15:51,553 WARN [com.arjuna.ats.arjuna] (default task-38) ARJUNA012140: Adding multiple last resources is disallowed. Trying to add LastResourceRecord(XAOnePhaseResource(LocalXAResourceImpl@666ebccc[connectionListener=11852abe connectionManager=3f58cd97 warned=false currentXid=< formatId=131077, gtrid_length=29, bqual_length=36, tx_uid=0:ffffc0a80002:d99c90f:59971e1c:4c, node_name=1, branch_uid=0:ffffc0a80002:d99c90f:59971e1c:50, subordinatenodename=null, eis_name=java:/ReadingDS > productName=Oracle productVersion=Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production jndiName=java:/ReadingDS])), but already have LastResourceRecord(XAOnePhaseResource(LocalXAResourceImpl@6027d87b[connectionListener=41a0034d connectionManager=329cdd5f warned=false currentXid=< formatId=131077, gtrid_length=29, bqual_length=36, tx_uid=0:ffffc0a80002:d99c90f:59971e1c:4c, node_name=1, branch_uid=0:ffffc0a80002:d99c90f:59971e1c:4e, subordinatenodename=null, eis_name=java:/WritingDS > productName=Oracle productVersion=Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production jndiName=java:/WritingDS]))
19:15:51,554 WARN [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-38) SQL Error: 0, SQLState: null
19:15:51,554 ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-38) javax.resource.ResourceException: IJ000457: Unchecked throwable in managedConnectionReconnected() cl=org.jboss.jca.core.connectionmanager.listener.TxConnectionListener@11852abe[state=NORMAL managed connection=org.jboss.jca.adapters.jdbc.local.LocalManagedConnection@7fc47256 connection handles=0 lastReturned=1503076551554 lastValidated=1503075869230 lastCheckedOut=1503076551553 trackByTx=false pool=org.jboss.jca.core.connectionmanager.pool.strategy.OnePool@6893c4c mcp=SemaphoreConcurrentLinkedQueueManagedConnectionPool@61e62cb9[pool=ReadingDS] xaResource=LocalXAResourceImpl@666ebccc[connectionListener=11852abe connectionManager=3f58cd97 warned=false currentXid=null productName=Oracle productVersion=Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production jndiName=java:/ReadingDS] txSync=null]
19:15:51,554 ERROR [org.jboss.as.ejb3.invocation] (default task-38) WFLYEJB0034: EJB Invocation failed on component EmpService for method public java.util.List de.test.EmpService.getAllEmps(): javax.ejb.EJBTransactionRolledbackException: org.hibernate.exception.GenericJDBCException: Unable to acquire JDBC Connection
感谢 this SO-Question 我可以通过在我的 wildfly 中设置以下系统属性 来解决问题:
<system-properties>
<property name="com.arjuna.ats.arjuna.allowMultipleLastResources" value="true"/>
</system-properties>
关于此异常最重要的一点是 wildfly 尝试创建两个连接,一个用于异常文本中的每个数据源(请参阅突出显示的 JNDI 名称)。在设置 system-属性 之前,我通过删除 @UsingDataSet
-Annotation 验证了这一点。删除测试用例后失败,因为断言 (Assert.assertEquals(2, allEmps.size());
) 失败,因为 table 中有零行 - 这表明没有为播种创建第二个连接。所以我创建了系统 属性,使用了 DataSet
并得到了一个绿色条。
第二个测试方法试图删除集合中的所有条目,这必须在异常中失败,因为 readingDS
数据源背后的用户无权删除 table 中的行。这个测试也成功了。完整的异常日志是:
javax.ejb.EJBTransactionRolledbackException: org.hibernate.exception.SQLGrammarException: could not execute statement
[...]
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not execute statement
[...]
... 187 more
Caused by: org.hibernate.exception.SQLGrammarException: could not execute statement
[...]
... 217 more
Caused by: java.sql.SQLSyntaxErrorException: ORA-01031: insufficient privileges
[...]
... 226 more
如您所见,由于权限不足,删除语句失败
结论
通过在 arquillian.xml
和 EJB 的持久性单元内定义数据源,可以使用与 seed/delete table 不同的数据源。 Arquillian 和应用服务器可以正确处理这些不同的数据源。
我们正在尝试为我们的项目设置 Arquillian 运行 自动测试。我们想利用 arquillian 持久性扩展来编写使用持久层的测试。所以我们想使用 @UsingDataSet
and/or @CreateSchema@
注释来为数据库做种。
我们所有的应用程序组件都有自己的数据库用户,这些用户只能访问组件需要的那些 tables/attributes。 None 个组件有权执行删除或 DDL 语句。所以我们需要在数据库 user/datasource seed/clean 模式 before/after 测试和执行测试之间切换,如下所示:
- 种子数据库,使用数据源 A 删除和重新创建序列
- 运行 使用数据源 B 的测试
- 使用数据源 A 清理数据库
很明显,如果我们将所需的 delete/DDL-rights 授予组件数据库用户以进行 arquillian 测试,则根据定义测试结果将不可靠。
那么我们如何使用 arquillian.xml 中定义的不同数据源到 seed/clean 数据库和 运行 测试?
目前还不支持(我们正在努力),但是在版本 2.0.0(目前处于 alpha 阶段,在 maven central 上)有一个解决方法,使用编程方式而不是声明方式(使用注释)。您可以在此处查看示例 https://github.com/arquillian/arquillian-extension-persistence/blob/2.0.0/arquillian-ape-sql/container/int-tests/src/test/java/org/arquillian/integration/ape/dsl/ApeDslIncontainerTest.java
我访问过的 arquillian 培训课程的讲师提到,为 EJB 的 seeding/cleaning 和 PersistenceContext
定义不同的数据源应该没有问题。所以我坐下来测试一下。
TL;DR: It's possible to just use two different datasources.
这是我的测试设置和测试结果。
本地测试设置
数据库
作为数据库,我安装了一个 Oracle XE
,因为我们在公司使用 Oracle 数据库。由于组件的数据库用户没有自己的架构,而是访问架构所有者的 table,我创建了三个数据库用户:
- 用户“bish”是模式“bish”的模式所有者,其中包含 empty table 我在测试中使用的“Emp”
- 用户“readinguser”获得了 table“bish.Emp” 的“SELECT、INSERT、UPDATE”权限
- 用户“writinguser”获得了 table“bish.Emp” 的“SELECT、INSERT、UPDATE、DELETE”权限
应用服务器
作为应用程序服务器,我使用了一个 Wildfly 10.x
并定义了两个数据源,一个用于我的两个测试用户
<datasource jndi-name="java:/ReadingDS" pool-name="ReadingDS" enabled="true">
<connection-url>jdbc:oracle:thin:@localhost:1521:xe</connection-url>
<driver>oracle</driver>
<pool>
<min-pool-size>1</min-pool-size>
<max-pool-size>5</max-pool-size>
<prefill>true</prefill>
</pool>
<security>
<user-name>readinguser</user-name>
<password>oracle</password>
</security>
</datasource>
<datasource jndi-name="java:/WritingDS" pool-name="WritingDS" enabled="true">
<connection-url>jdbc:oracle:thin:@localhost:1521:xe</connection-url>
<driver>oracle</driver>
<pool>
<min-pool-size>1</min-pool-size>
<max-pool-size>5</max-pool-size>
<prefill>true</prefill>
</pool>
<security>
<user-name>writingguser</user-name>
<password>oracle</password>
</security>
</datasource>
测试应用程序
然后我写了一个带有实体的小应用程序,还有EJB,persistence.xml,arquillian.xml,数据集和测试class
实体(仅显示 table 具有显式模式命名的定义)
@Entity
@Table(name = "Emp", schema = "bish")
public class Emp implements Serializable {
// Straight forward entity...
}
具有两种方法的 EJB select创建和删除所有条目
@Stateless
@Remote(IEmpService.class)
@LocalBean
public class EmpService implements IEmpService {
@PersistenceContext
private EntityManager em;
public void removeAllEmps() {
em.createQuery("DELETE FROM Emp").executeUpdate();
}
public List<Emp> getAllEmps() {
return em.createQuery("FROM Emp", Emp.class).getResultList();
}
}
persistence.xml 内的持久性单元使用 EJB 内的“ReadingDS”
<persistence-unit name="ReadingUnit" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/ReadingDS</jta-data-source>
<shared-cache-mode>NONE</shared-cache-mode>
</persistence-unit>
Arquillian.xml 使用“WritingDS”的定义 seed/clean table 和模式定义
<extension qualifier="persistence">
<property name="defaultDataSeedStrategy">CLEAN_INSERT</property>
<property name="defaultCleanupStrategy">USED_ROWS_ONLY</property>
<property name="defaultDataSource">java:/WritingDS</property>
</extension>
<extension qualifier="persistence-dbunit">
<property name="schema">bish</property>
</extension>
测试中使用的数据集“empBefore.xml”class
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<EMP EMPNO="9998" ENAME="TEst" JOB="Eins" HIREDATE="1982-01-23" SAL="1300" DEPTNO="10"/>
<EMP EMPNO="9999" ENAME="Test" JOB="Zwei" MGR="9998" HIREDATE="1982-01-23" SAL="1300" DEPTNO="10"/>
</dataset>
测试class:
@RunWith(Arquillian.class)
public class DataSourceTest {
@Deployment
public static JavaArchive createDeployment() {
// ...
}
@EJB
EmpService testclass;
@Rule
public ExpectedException thrown = ExpectedException.none();
@UsingDataSet("empBefore.xml")
@Test
public void GetAllEmps() {
List<Emp> allEmps = testclass.getAllEmps();
Assert.assertEquals(2, allEmps.size());
}
@UsingDataSet("empBefore.xml")
@Test
public void DeleteAllEmps() {
thrown.expect(EJBException.class);
thrown.expectCause(CoreMatchers.isA(PersistenceException.class));
testclass.removeAllEmps();
}
}
测试
我首先执行了 GetAllEmps
测试方法来查看 table 是否正确地使用了 DataSet
的数据以及 EJB 的 select 方法作品。在我第一次执行时,我得到了以下异常。 (很抱歉发布了这么多文字,但这很重要,请参见下文!)
19:15:51,553 WARN [com.arjuna.ats.arjuna] (default task-38) ARJUNA012140: Adding multiple last resources is disallowed. Trying to add LastResourceRecord(XAOnePhaseResource(LocalXAResourceImpl@666ebccc[connectionListener=11852abe connectionManager=3f58cd97 warned=false currentXid=< formatId=131077, gtrid_length=29, bqual_length=36, tx_uid=0:ffffc0a80002:d99c90f:59971e1c:4c, node_name=1, branch_uid=0:ffffc0a80002:d99c90f:59971e1c:50, subordinatenodename=null, eis_name=java:/ReadingDS > productName=Oracle productVersion=Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production jndiName=java:/ReadingDS])), but already have LastResourceRecord(XAOnePhaseResource(LocalXAResourceImpl@6027d87b[connectionListener=41a0034d connectionManager=329cdd5f warned=false currentXid=< formatId=131077, gtrid_length=29, bqual_length=36, tx_uid=0:ffffc0a80002:d99c90f:59971e1c:4c, node_name=1, branch_uid=0:ffffc0a80002:d99c90f:59971e1c:4e, subordinatenodename=null, eis_name=java:/WritingDS > productName=Oracle productVersion=Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production jndiName=java:/WritingDS]))
19:15:51,554 WARN [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-38) SQL Error: 0, SQLState: null
19:15:51,554 ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-38) javax.resource.ResourceException: IJ000457: Unchecked throwable in managedConnectionReconnected() cl=org.jboss.jca.core.connectionmanager.listener.TxConnectionListener@11852abe[state=NORMAL managed connection=org.jboss.jca.adapters.jdbc.local.LocalManagedConnection@7fc47256 connection handles=0 lastReturned=1503076551554 lastValidated=1503075869230 lastCheckedOut=1503076551553 trackByTx=false pool=org.jboss.jca.core.connectionmanager.pool.strategy.OnePool@6893c4c mcp=SemaphoreConcurrentLinkedQueueManagedConnectionPool@61e62cb9[pool=ReadingDS] xaResource=LocalXAResourceImpl@666ebccc[connectionListener=11852abe connectionManager=3f58cd97 warned=false currentXid=null productName=Oracle productVersion=Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production jndiName=java:/ReadingDS] txSync=null]
19:15:51,554 ERROR [org.jboss.as.ejb3.invocation] (default task-38) WFLYEJB0034: EJB Invocation failed on component EmpService for method public java.util.List de.test.EmpService.getAllEmps(): javax.ejb.EJBTransactionRolledbackException: org.hibernate.exception.GenericJDBCException: Unable to acquire JDBC Connection
感谢 this SO-Question 我可以通过在我的 wildfly 中设置以下系统属性 来解决问题:
<system-properties>
<property name="com.arjuna.ats.arjuna.allowMultipleLastResources" value="true"/>
</system-properties>
关于此异常最重要的一点是 wildfly 尝试创建两个连接,一个用于异常文本中的每个数据源(请参阅突出显示的 JNDI 名称)。在设置 system-属性 之前,我通过删除 @UsingDataSet
-Annotation 验证了这一点。删除测试用例后失败,因为断言 (Assert.assertEquals(2, allEmps.size());
) 失败,因为 table 中有零行 - 这表明没有为播种创建第二个连接。所以我创建了系统 属性,使用了 DataSet
并得到了一个绿色条。
第二个测试方法试图删除集合中的所有条目,这必须在异常中失败,因为 readingDS
数据源背后的用户无权删除 table 中的行。这个测试也成功了。完整的异常日志是:
javax.ejb.EJBTransactionRolledbackException: org.hibernate.exception.SQLGrammarException: could not execute statement
[...]
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not execute statement
[...]
... 187 more
Caused by: org.hibernate.exception.SQLGrammarException: could not execute statement
[...]
... 217 more
Caused by: java.sql.SQLSyntaxErrorException: ORA-01031: insufficient privileges
[...]
... 226 more
如您所见,由于权限不足,删除语句失败
结论
通过在 arquillian.xml
和 EJB 的持久性单元内定义数据源,可以使用与 seed/delete table 不同的数据源。 Arquillian 和应用服务器可以正确处理这些不同的数据源。