Testing Transactions Example has been edited by David Blevins (Aug 08, 2008).

(View changes)

Content:

Overview

Testing an EntityManager that uses the default, PersistenceContextType.TRANSACTION, can be challenging due to Entities detaching around transaction boundaries. If you were to take the Injection of EntityManager Example which uses an EXTENDED persistence context and switch it to a TRANSACTION persistence context making no other changes, you'd find that the test would fail. This would be because of the detach.

Generally, when using an EntityManager with TRANSACTION persistence context, your transaction should surround your use of the Entities themselves, not just the use of the EntityManager. Using a transaction in your unit test can make testing these scenarios possible.

This example shows use of @PersistenceContext to have an EntityManager with an TRANSACTION persistence context injected into a @Stateful bean using the @TransactionAttribute annotation and a TestCase that runs test code in a JTA Transaction. An EJB 3 @Entity bean is used with the EntityManager to create, persist and merge data to a database.

The source for this example is in the "testing-transactions" directory located in the openejb-examples.zip available on the download page.

The Code

@Stateful(name = "Movies")
@TransactionAttribute(MANDATORY)
public class MoviesImpl implements Movies {

    @PersistenceContext(unitName = "movie-unit", type = PersistenceContextType.TRANSACTION)
    private EntityManager entityManager;

    public void addMovie(Movie movie) throws Exception {
        entityManager.persist(movie);
    }

    public void deleteMovie(Movie movie) throws Exception {
        entityManager.remove(movie);
    }

    public List<Movie> getMovies() throws Exception {
        Query query = entityManager.createQuery("SELECT m from Movie as m");
        return query.getResultList();
    }
}

In the above bean code we see that the transaction attribute for all method of the bean has been changed the default, which is TransactionAttributeType.REQUIRED, to TransactionAttributeType.MANDATORY. The MANDATORY transaction attribute is similar to REQUIRED in that the bean method is guaranteed to be executed in a transaction, but with the added restriction that if a transaction isn't started by the client before calling the method, an exception will be thrown.

The benefit of MANDATORY in this example is that it is impossible for the client to get detached Entity issues. The client either has a transaction and therefore gets Entities which are still attached and persistent, or the client would get an exception stating that the use of a transaction is mandatory.

See the Transaction Annotations page for a full description of the available transaction attributes.

Writing a unit test for the example

The magic in the TestCase below is the TransactionBean @Stateless bean which is tucked away as an inner class of the TestCase itself. With this bean, we can call our test code within the scope of a container controlled transaction. This allows our test code to use the EntityManager and the Entities in the scope of a transaction, avoid any detach issues and satisfying the TransactionAttributeType.MANDATORY requirement of our MoviesImpl @Stateful bean.

public class MoviesTest extends TestCase {
    private Context context;

    protected void setUp() throws Exception {
        Properties p = new Properties();
        p.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
        p.put("movieDatabase", "new://Resource?type=DataSource");
        p.put("movieDatabase.JdbcDriver", "org.hsqldb.jdbcDriver");
        p.put("movieDatabase.JdbcUrl", "jdbc:hsqldb:mem:moviedb");

        p.put("movieDatabaseUnmanaged", "new://Resource?type=DataSource");
        p.put("movieDatabaseUnmanaged.JdbcDriver", "org.hsqldb.jdbcDriver");
        p.put("movieDatabaseUnmanaged.JdbcUrl", "jdbc:hsqldb:mem:moviedb");
        p.put("movieDatabaseUnmanaged.JtaManaged", "false");

        context = new InitialContext(p);
    }

    private void doWork() throws Exception {
        Movies movies = (Movies) context.lookup("MoviesLocal");

        movies.addMovie(new Movie("Quentin Tarantino", "Reservoir Dogs", 1992));
        movies.addMovie(new Movie("Joel Coen", "Fargo", 1996));
        movies.addMovie(new Movie("Joel Coen", "The Big Lebowski", 1998));

        List<Movie> list = movies.getMovies();
        assertEquals("List.size()", 3, list.size());

        for (Movie movie : list) {
            movies.deleteMovie(movie);
        }

        assertEquals("Movies.getMovies()", 0, movies.getMovies().size());
    }

    public void testWithTransaction() throws Exception {
        Caller transactionBean = (Caller) context.lookup("TransactionBeanLocal");

        transactionBean.call(new Callable(){
            public Object call() throws Exception {
                doWork();
                return null;
            }
        });
    }

    public void testWithoutTransaction() throws Exception {
        try {
            doWork();
            fail("The Movies bean should be using TransactionAttributeType.MANDATORY");
        } catch (javax.ejb.EJBTransactionRequiredException e) {
            // good, our Movies bean is using TransactionAttributeType.MANDATORY as we want
        }
    }


    public static interface Caller {
        public <V> V call(Callable<V> callable) throws Exception;
    }

    /**
     * This little bit of magic allows our test code to execute in
     * the scope of a container controlled transaction.
     *
     * The src/test/resource/META-INF/ejb-jar.xml will cause this
     * EJB to be automatically discovered and deployed when
     * OpenEJB boots up.
     */
    @Stateless
    @TransactionAttribute(REQUIRES_NEW)
    public static class TransactionBean implements Caller {

        public <V> V call(Callable<V> callable) throws Exception {
            return callable.call();
        }

    }

}

Curious on the InitialContext parameters used? See the Injection of DataSource Example for an explanation of how any Resource can be configured via properties in the TestCase itself or via an openejb.xml file.

Running

Running the example is fairly simple. In the "testing-transactions" directory of the examples zip, just run:

$ mvn clean install

Which should create output like the following.

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running org.superbiz.injection.tx.MoviesTest
Apache OpenEJB 3.0    build: 20080408-04:13
http://openejb.apache.org/
INFO - openejb.home = /Users/dblevins/work/openejb-3.0/examples/testing-transactions
INFO - openejb.base = /Users/dblevins/work/openejb-3.0/examples/testing-transactions
INFO - Configuring Service(id=Default Security Service, type=SecurityService, provider-id=Default Security Service)
INFO - Configuring Service(id=Default Transaction Manager, type=TransactionManager, provider-id=Default Transaction Manager)
INFO - Configuring Service(id=movieDatabaseUnmanaged, type=Resource, provider-id=Default JDBC Database)
INFO - Configuring Service(id=movieDatabase, type=Resource, provider-id=Default JDBC Database)
INFO - Configuring Service(id=Default JDK 1.3 ProxyFactory, type=ProxyFactory, provider-id=Default JDK 1.3 ProxyFactory)
INFO - Found EjbModule in classpath: /Users/dblevins/work/openejb-3.0/examples/testing-transactions/target/test-classes
INFO - Found EjbModule in classpath: /Users/dblevins/work/openejb-3.0/examples/testing-transactions/target/classes
INFO - Configuring app: /Users/dblevins/work/openejb-3.0/examples/testing-transactions/target/test-classes
INFO - Configuring Service(id=Default Stateless Container, type=Container, provider-id=Default Stateless Container)
INFO - Auto-creating a container for bean TransactionBean: Container(type=STATELESS, id=Default Stateless Container)
INFO - Loaded Module: /Users/dblevins/work/openejb-3.0/examples/testing-transactions/target/test-classes
INFO - Configuring app: /Users/dblevins/work/openejb-3.0/examples/testing-transactions/target/classes
INFO - Configuring Service(id=Default Stateful Container, type=Container, provider-id=Default Stateful Container)
INFO - Auto-creating a container for bean Movies: Container(type=STATEFUL, id=Default Stateful Container)
INFO - Configuring PersistenceUnit(name=movie-unit)
INFO - Loaded Module: /Users/dblevins/work/openejb-3.0/examples/testing-transactions/target/classes
INFO - Assembling app: /Users/dblevins/work/openejb-3.0/examples/testing-transactions/target/test-classes
INFO - Jndi(name=TransactionBeanLocal) --> Ejb(deployment-id=TransactionBean)
INFO - Created Ejb(deployment-id=TransactionBean, ejb-name=TransactionBean, container=Default Stateless Container)
INFO - Deployed Application(path=/Users/dblevins/work/openejb-3.0/examples/testing-transactions/target/test-classes)
INFO - Assembling app: /Users/dblevins/work/openejb-3.0/examples/testing-transactions/target/classes
INFO - PersistenceUnit(name=movie-unit, provider=org.apache.openjpa.persistence.PersistenceProviderImpl)
ERROR - JAVA AGENT NOT INSTALLED. The JPA Persistence Provider requested installation of a ClassFileTransformer which 
        requires a JavaAgent.  See http://openejb.apache.org/3.0/javaagent.html
INFO - Jndi(name=MoviesLocal) --> Ejb(deployment-id=Movies)
INFO - Created Ejb(deployment-id=Movies, ejb-name=Movies, container=Default Stateful Container)
INFO - Deployed Application(path=/Users/dblevins/work/openejb-3.0/examples/testing-transactions/target/classes)
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.25 sec

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

Powered by Atlassian Confluence (Version: 2.2.9 Build:#527 Sep 07, 2006) - Bug/feature request

Unsubscribe or edit your notifications preferences