1

We are trying to setup Arquillian for our projects to run automatic tests. We would like to make use of the arquillian persistence extension to write tests using the persistance layer. So we would like to seed the database using the @UsingDataSet and/or @CreateSchema@ annotations.

All of our application components have own database users which have only access to those tables/attributes the component needs. None of the components has rights to execute delete or DDL statements. So we need to switch between a database user/datasource seed/clean the schema before/after the tests and executing the tests like this:

  1. Seed database, drop and recreate sequences using datasaource A
  2. Run the test using datasource B
  3. Clean database using datasource A

It should be obvious that if we would grant the needed delete/DDL-rights to the component database user for the arquillian tests the test results would not be reliable per defintion.

So how can we use different datasources, definied in the arquillian.xml, to seed/clean the database and running the tests?

bish
  • 3,381
  • 9
  • 48
  • 69

2 Answers2

3

The lecturer of an arquillian training course I visited mentioned that it should be no problem to just define different datasources for seeding/cleaning and for the PersistenceContext of the EJBs. So I sat down to test this.

TL;DR: It's possible to just use two different datasources.

Here are my test setup and the results of my tests.

Local test setup

Database

As the database I installed an Oracle XE as we use Oracle databases in my company. As the database users of the components don't have their own schema but access the tables of the schema owner I created three database users:

  • User "bish" is the schema owner of the schema "bish" which contains the empty table "Emp" I use in the test
  • User "readinguser" which got "SELECT, INSERT, UPDATE" priviledges for the table "bish.Emp"
  • User "writinguser" which got "SELECT, INSERT, UPDATE, DELETE" priviledges for the table "bish.Emp"

Application server

As an application server I used an Wildfly 10.x and definied two data sources, one for each of my two testusers

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

Test application

Then I wrote a small application with an entity, and EJB, persistence.xml, arquillian.xml, dataSet and test class

Entity (Only table definition with explicit schema naming shown)

@Entity
@Table(name = "Emp", schema = "bish")
public class Emp implements Serializable {
  // Straight forward entity...
}

EJB with two methods for selecting and deleting all entries

@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 unit inside persistence.xml to use the "ReadingDS" inside the EJB

<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 with definition of using the "WritingDS" to seed/clean the table and schema definition

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

Dataset "empBefore.xml" used in test 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>

Test 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();
  }

}

The test

I first executed the GetAllEmps test method to see if the table is correctly seeded with the data of the DataSet and if the select method of the EJB works. On my first execution I got the following exception. (Sorry for posting so much text but it's important, see below!)

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

Thanks to this SO-Question I could fix the problem by setting the following systemproperty in my wildfly:

 <system-properties>
     <property name="com.arjuna.ats.arjuna.allowMultipleLastResources" value="true"/>
 </system-properties>

The most important thing about this exception is the fact that wildfly tries to create two connections, one for each data source (see highlighted JNDI-names) in exception text. Before setting the system-property I verified that by removing the @UsingDataSet-Annotation. After removing the test case failed because the assertion (Assert.assertEquals(2, allEmps.size());) failed as there were zero rows in the table - which indicates there was no second connection created for seeding. So I created the system property, used the DataSet and got a green bar.

The second test method tries to delete all entries in the set, which must fail in an exception as the user behind the readingDS datasource has no rights to delete rows in the table. This test was also successfull. The full exception log was:

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

As you can see the delete statement failes because of insufficient privileges

Conclusion

It's possible to use different data sources to seed/delete tables by defining the datasource in the arquillian.xml and inside an persistence-unit for an EJB. Arquillian and the application server can handle these different datasources correctly.

Community
  • 1
  • 1
bish
  • 3,381
  • 9
  • 48
  • 69
0

Currently this is not supported yet (we are working on it), but in version 2.0.0 (currently are on alpha stage are on maven central) there is a workaround by using programmatic way instead of declarative way (using annotations). You can see an example here 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

lordofthejars
  • 713
  • 1
  • 6
  • 14
  • Do I understand the example correctly, that with `persistenceConfiguration.getDefaultDataSource()` I also could choose another dataSource for seeding? – bish Jun 07 '17 at 06:37
  • What you can do is since you are in-container, inject the datasource inside test (all datasources you need) and use it there. Not tried but intheory should be possible. – lordofthejars Jun 08 '17 at 07:22
  • Please have a look to my answer – bish Aug 21 '17 at 08:19