TransactionRolledbackLocalException Client's transaction aborted when accessing @Singleton

Snekse picture Snekse · Oct 18, 2012 · Viewed 10.2k times · Source

NOTE: Adding this question in short form with an answer after spending far more time than I'd like to admit on finding the cause. Hopefully I'll save someone else some pain.

When delegating a method call to an EJB annotated with @Singleton, the container throws an Exception along the lines of:

TransactionRolledbackLocalException Client's transaction aborted 

The Singleton bean has no data access occurring.

ServiceBeanImpl.java

@Stateless
@Local
public class ServiceBean extends BaseBean{ 
  @EJB private CacheService cacheService;

  public FooObj getFooFromCache(int id) {
    FooObj fooObj = (FooObj) cacheService.get(id);
    if (fooObj == null) {
      fooObj = getEntityById(FooObj.class, id);
      cacheService.put(id, fooObj);  //This throws exception
    }
    return cacheService.get(id);
  }
}

CacheServiceImpl.java

@Singleton
@Startup
public class CacheServiceImpl implements CacheService {
    private Cache cache;

    @PostConstruct
    public void init() {
        CacheManager instance = CacheManager.getInstance();
        cache = instance.getCache("cache");
    }

    @PreDestroy
    public void destroy() {
        CacheManager.getInstance().shutdown();
    }

    public Object get(Object id) {
      return cache.get(id);
    }

    public void put(Object id, Object obj) {
      return cache.put(id, obj);
    }
}

Question Why would calling a Singleton bean that does no data access throw a Transaction exception?

Answer

Snekse picture Snekse · Oct 18, 2012

Short answer: check the code stack that executed prior (assuming there are no clues in the StackTrace) for an Exception that is trapped and not propagated (thus no clues in the StackTrace).

In this particular case there was a try/catch around creating a named query. If that named query failed, a secondary query was executed in the catch block. The queries didn't require a transaction, so the fallback query executed properly and returned the expected entities.

However...

That also (I think) marks the transaction as needing a rollback. The @Singleton bean is somewhat of a red-herring. Something about transferring control to that bean made the container check the Transaction, which in turn threw the exception at that point, but the exception was appropriate since the transaction was no longer valid.

Moral of the story:

  • work your way backwards to the last database interaction because that's probably where your problem is.
  • Just because the last database interaction returned data doesn't mean it didn't have an issue (this was the killer for me).