Package org.wamblee.test.persistence

This package provides test library for database testing in general and JPA testing specifically.

See:
          Description

Interface Summary
Database Represents a database.
DatabaseProvider Database provider.
DatabaseUtils.JdbcUnitOfWork<T> Represents a unit of work (transaction).
DatabaseUtils.TableSet Represents a set of tables.
DatabaseUtils.TableSetOperation Operation to be executed on a set of tables for each table individually.
JpaBuilder.JpaUnitOfWork<T> Callback interface to execute some JPA code within a transaction with the entitymanager to use provided as input.
JpaCustomizer JPA customizer is used to customize properties for a given JPA implementation.
TransactionResultCallback Callback to notify the test of the result of the transaction.
 

Class Summary
AbstractDatabase Abstract database class providing the creation of the datasource, preventing duplicate starts of the same database, and checking for connection leaks when the database is stopped.
AbstractDatabaseProvider Base class for database providers.
CompositeJpaCustomizer Composite JPA customizer that applies the customizations from several JPA customizers.
CompositeJpaTables Composite JPA tables.
DatabaseBuilder DatabaseBuilder is used from unit test to obtain a reference to a database from unit test.
DatabaseDescription Description of a specific database.
DatabaseStarter This class is used for starting the database as a main program.
DatabaseUtils Database utilities is a simple support class for common tasks in working with databases.
DerbyDatabase Derby database setup.
DerbyDatabaseProvider Derby database provider.
ExternalDatabase Database that encapsulates connection to an external database.
ExternalDatabaseProvider Database provider for an external database.
JpaBuilder Utility for building an appropriately configured EntityManagerFactory.
JpaCustomizerBuilder JPA customizer builder implements the ServiceLoader based mechanism for looking up JPA customizers.
JpaTester This class is the entry point for JPA tests.
LoggingTransactionResultCallback Logging the result of a transaction.
PersistenceUnitDescription Describes a persistence unit.
RequireTransactionStatus Specific transaction result callback to require a specific transaction result.
 

Package org.wamblee.test.persistence Description

This package provides test library for database testing in general and JPA testing specifically. As part of this it provides a means to transparently start an inmemory database from a junit test or connect to an external database. Also, given a persistence unit it is easy to start testing it in a junit test with only a few lines of code.

The main use cases are explained below:

See also the design overview.

Basic database testing, transparently connecting to a database

Starting the database:
      Database db = DatabaseBuilder.getDatabase();
      DataSource dataSource = db.start();
 

If nothing is specified in the user's environment, an inmemory database is started (derby). Using the datasource is just standard JDBC now.

After a test it is good practice to stop the database:

      db.stop();
 

Connecting to an external database

Connecting to an external database can be done by requiring the 'external' capability on the database provider.
      Database db = DatabaseBuilder.getDatabase(DatabaseProvider.CAPABILITY_EXTERNAL); 
 
This also requires a number of environment variables or system properties to be set, see ExternalDatabase.

However, the most convenient way to set the capabilities is usually to set a system property or environment variable see the javadocs of DatabaseBuilder. and specifically DatabaseBuilder.DB_CAPABILITIES_PROP

Executing code within a JDBC transaction

To execute code withing a JDBC transaction, use the DatabaseUtils and use the DatabaseUtils#executeInTransaction(org.wamblee.support.persistence.DatabaseUtils.JdbcUnitOfWork) method.

       DatabaseUtils dbutils = new DatabaseUtils(dataSource);
       boolean result = dbutils.executeInTransaction(
           new JdbcUnitOfWork<Boolean>() {
               @Override
               public Boolean execute(Connection aConnection) throws Exception {
                   ResultSet res = jpaTester.getDbUtils().executeQuery(
                       aConnection, GROUP_QUERY, aGroup);
                   return res.next();
               }
           });
 
DatabaseUtils also provides various other utility methods to work with JDBC queries.

Using DB Unit in your tests

To work with DBUnit, DatabaseUtils#createDbTester(org.dbunit.dataset.filter.ITableFilterSimple) must be used passing it in the tables to use in the form of a ITableFilterSimple object.

      IDatabaseTester dbtester = dbutils.createDbTester(new ITableFilterSimple() {
          public boolean accept(String aTableName) throws DataSetException {
              return aTableName.startsWith("XYZ_");
          }
      });
 

The reason for using a DatabaseUtils instead of DBUnit directly is that DatabseUtils will keep track of connections and close them when DatabaseUtils is closed

Basic JPA testing

First step is to create a PersistenceUnitDescription that matches the persistence unit you want to test.

Second step is to make sure that all entities are listed explicitly in your persistence.xml. Currently, class path scanning appears to fail when run from junit. Specifying all entities explicitly is not necessarily a bad thing as it is also more efficient.

Now create a JpaTester in your test code:

      @Before
      public void setUp() throws Exception {
 
          // First we create the JpaTester by telling us which persistence unit we
          // are going to test
          jpaTester = new JpaTester(new MyPersistenceUnit());
          jpaTester.start();     
      }
 

Then in test code execute some JPA code within a unit of work:

      jpaTester.getJpaBuilder().execute(new JpaUnitOfWork() {
          public Void execute(EntityManager aEm) {
              MyEntity entity = new MyEntity("a", "b");
              aEm.persist(entity);
              return null;
          }
      });
 

Note that in addition to this method it is also possible to directly control transactions through JpaBuilder#begin(), JpaBuilder#commit(javax.persistence.EntityManager), and JpaBuilder#rollback(javax.persistence.EntityManager).

JPA testing combined with JDBC and DBUnit

The JPATester provides access to all required object. It is usually convenient to get them directly from the JPATester after initializing it:

      builder = jpaTester.getJpaBuilder();
      dbutils = jpaTester.getDbUtils();
      dbtester = dbutils.createDbTester(new MyTables());
 

Testing a service that requires a transaction

Using TransactionProxyFactory it is possible to create a proxy for a given service interface to provide the semantics of 'requires new' transaction semantics.

      TransactionProxyFactory factory = new TransactionProxyFactory(
           jpaTester.getJpaBuilder(), Service.class);
      Service service = new ServiceImpl(factory.getTransactionScopedEntityManager()); 
      Service proxy = factory.getProxy(service);
      proxy.execute(...);
 

In the above example, the Service POJO requires an EntityManager in its constructor and it is passed a transaction scoped entitymanager from the factory. This entitymanager is in fact a so-called contextual reference. Next, the proxy is obtained from the factory. Invoking any method on it will make sure a new transaction is started and a new entity manager is created for the scope of that transaction.

Controlling transactions through a UserTransaction

It is also possible to manage JPA transaction through a UserTransaction. This is mostly useful if you are test software that uses UserTransaction for managing transactions, or if you want to manage multiple transactional resources. See the explanation at SimpleTransactionManager for more details.

Design overview

Database transparency


database

DatabaseProvider uses java.util.ServiceLoader to find all implementations of DatabaseProvider on the classpath. It then asks the database providers whether they support the required capabilities (by default inmemory), and the first provider that supports the capabilities is used to create the database.

Note that the Database interface is not intended to always create a database. It will do so for DerbyDatabase (inmemory), but with ExternalDatabase it simply connects to an external database based on system properties or environment variables.

JPA tester overview


database

JPATester is responsible for:



Copyright © 2021. All Rights Reserved.