tomee-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Alexander Saint Croix" <saintx.opensou...@gmail.com>
Subject Transactions, detached objects and fault loading in Unit Tests
Date Mon, 21 Jan 2008 22:36:31 GMT
Hello,

I'm trying to keep transactions alive inside of a TRANSACTION typed
transaction context long enough to test collection valued references of the
query results.  Right now I have a test case that makes 26 different
transactions to the database, when I'm sure it should be more like 3 or 4.

The crux of my problem is that I have entities with numerous
collection-valued references to other entities in my library.  In my tests I
want to load an entity, and each of its collection valued reference.  An
example would look something like this:

...
> Party party = myBeanManager.findParty(id);
> Assert.assertNotNull(party);
> Set<AssociatedAddress> addresses = party.getAddresses();
> Set<Capabilities> caps = party.getCapabilities();
> Set<PartyRole> roles = party.getPartyRoles();
> Set<RegisteredIdentifiers> regIDs = party.getRegisteredIdentifiers();
> Set<Binding> prefs = party.getPreferences();
> Assert.assertNotNull(addresses);
> Assert.assertNotNull(caps);
> Assert.assertNotNull(roles);
> Assert.assertNotNull(regIDs);
> Assert.assertNotNull(prefs);
> Assert.assertTrue(caps.size() > 0);
> Assert.assertTrue(roles.size() > 0);
> Assert.assertTrue(regIDs.size() > 0);
> Assert.assertTrue(prefs.size() > 0);
> Assert.assertTrue(addresses.size() > 0);
> ...
>

However, since my stateless session bean is using a TRANSACTION type
persistence context to get my Party object detaches it, so I get NPEs for
the lower block of assertions.  Just about all of my classes have cascades
set up for persist and merge between the entity references, and I find if I
use the EXTENDED type persistence context I get
ConcurrentModificationExceptions.

I initially tried to use OpenJPA-recommended FetchGroups but got
ConcurrentModificationExceptions when I tried to merge objects.  I found
that I could avoid these if I set the FetchType on my field references to
EAGER, but I also understand the danger in doing that and want to avoid
it--it should the be the exception for almost all of my entities--not the
rule.  In the end the only thing I could think of to do was to call a
transaction with a LEFT JOIN FETCH for each collection-valued reference in
my object.  Thus, the above code morphed into this ugly and slow code:

...
> Party party;
>
> party = myBeanManager.findParty(id, "addresses");
> Set<AssociatedAddress> addresses = party.getAddresses();
> Assert.assertNotNull(party);
> Assert.assertNotNull(addresses);
> Assert.assertTrue(addresses.size() > 0);
>
> party = myBeanManager.findParty(id, "roles");
> Set<PartyRole> roles = party.getPartyRoles();
> Assert.assertNotNull(party);
> Assert.assertNotNull(roles);
> Assert.assertTrue(roles.size() > 0);
>
> party = myBeanManager.findParty(id, "capabilities");
> Set<Capability> caps = party.getCapabilities();
> Assert.assertNotNull(party);
> Assert.assertNotNull(caps);
> Assert.assertTrue(caps.size() > 0);
>
> party = myBeanManager.findParty(id, "preferences");
> Set<Binding> prefs = party.getPreferences();
> Assert.assertNotNull(party);
> Assert.assertNotNull(prefs);
> Assert.assertTrue(prefs.size() > 0);
>
> party = myBeanManager.findParty(id, "registeredIdentifiers");
> Set<RegisteredIdentifier> regIDs = party.getRegisteredIdentifiers();
> Assert.assertNotNull(party);
> Assert.assertNotNull(regiIDs);
> Assert.assertTrue(regIDs.size() > 0);
> ...
>

The code above uses five connections to the database for what should be done
in one.  Although my tests work, they're needlessly slow and I know this
isn't how things are supposed to be done.  So, before I can continue testing
the other modules in my library I want to get these tests streamlined.  I
showed the code to Dain and he had some suggestions.  I blogged that
conversation here:

http://blog.lib.umn.edu/saintx/eremite/2008/01/fault_loading_and_usertransact.html

Dain mentioned an example in the itests module for the OpenEJB project that
does something like this, but his example was written for version 2.1 entity
beans, and the beans were gotten via the LocalHome interfaces for the bean
object--I don't know this example is the best one for what I'm trying to do.

At Dain's suggestion I looked into the UserTransaction technique--it is
covered in the EJB 3 book by O'Reilly.  However, Container Managed
Transaction beans cannot use UserTransaction.  I also have been attempting
different ways of moving my test code into the session bean.  I tried
implementing a visitor pattern that I could push into the Session bean's
finder methods.  Here's one example of that:

public class PartyCascadeTest extends PersistenceTest {
>     public void testPartyCascade() {
>
>         Party initial = new Party();
>         mgr.add(initial);
>
>         Visitor<Party> visitor = new PartyVisitor<Party>();
>         mgr.check("Party", visitor);
>
>     }
>
>     private class PartyVisitor<A extends Party> implements Visitor<A> {
>         public void test(List<A> list) {
>             Assert.assertNotNull("party list should not be null", list);
>             Assert.assertTrue(list.size() == 1);
>             Party p = list.get(0);
>             Set<PartyRole> roles = p.getPartyRoles();
>             Assert.assertNotNull("roles should not be null", roles);
>         }
>     }
> }
>

The operant part of the session bean looks like this:

    public void check(String table, Visitor visitor) {
>         Query query = entityManager.createQuery("Select a from " + table +
> " as a");
>         List<Party> l = query.getResultList();
>         Assert.assertNotNull(l);
>
>         visitor.test(l);
>     }
>

However, I get NullPointerExceptions in that call to visitor.test().  I
tried sending "visitor.test(query.getResultList())", but it appears by the
time I get that result list the transaction is already over and the object
is detached.  So, I can't do fault loading on any of the objects.

Now I'm trying a different method--really just grasping at straws.  I'm
trying to get the EntityManager from directly inside of the unit test and
explicitly control the transaction manager.  That isn't working either--I
get NPE on the transaction manager after trying to look it up.  Here's my
experimental test class:

public class EfficientPartyCascadeTest extends PersistenceTest {
>
>     private TransactionManager txMan;
>     private EntityManagerFactory emf;
>     private EntityManager em;
>
>     public void beginTransaction() throws Exception {
>         txMan.begin();
>     }
>
>     protected void completeTransaction() throws Exception {
>         int status = txMan.getStatus();
>         if (status == Status.STATUS_ACTIVE) {
>             txMan.commit();
>         } else if (status != Status.STATUS_NO_TRANSACTION) {
>             txMan.rollback();
>         }
>     }
>
>     public void setUp() throws Exception {
>         super.setUp();
>         try {
>             Assert.assertNotNull("context", context);
>             txMan = (TransactionManager) context.lookup
> ("java:openejb/TransactionManager");
>         } catch (NamingException e) {
>             e.printStackTrace();
>         }
>         Assert.assertNotNull("transactionManager", txMan); // This fails
> at the moment--can't find the transaction manager
>
>         emf = Persistence.createEntityManagerFactory("party-test-unit");
>         em = emf.createEntityManager();
>     }
>
>     public void test02_ASetBDropExisting() throws Exception {
>         Party initial = new Party();
>         initial.setName("Initial");
>         mgr.add(initial); // TODO: +1 unit of work
>
>         beginTransaction();
>         try {
>             Party party = findParty(initial.getID());
>             Assert.assertNotNull(party);
>         } finally {
>             completeTransaction();
>         }
>     }
>
>     private Party findParty(long ID) {
>         return em.find(Party.class, ID);
>     }
> }


I know that EM injection doesn't work for unit tests at present.  Like I
said before, I can just make another round trip to the db every time I need
a collection-valued reference, but it's bad for business.  Ideas?
Suggestions?

Regards,
--
Alex

Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message