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?
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:
I ran into a similar problem, and found a interesting thing:
From within a @Startup @Singleton I have the following calls
@PostConstruct
public void initHardcodedUsers() {
for (User u : getHardcodedUsers()) {
if (!userRepository.userExists(u.getName())) {
userRepository.add(u);
}
}
}
UserRepository is a @Stateless EJB with an EntityManager.
In the userExists()
Method I basically do a "findUserByName" Query with getSingleResult()
. If no result is available, a NoResultException
is thrown by JPA. I catch that one inside userExists()
and return false ("user does not exist").
As NoResultException is a RuntimeException a rollback is forced here, as I just learned!
The most simple approach for that is, to avoid the call to getSingleResult()
. Instead of that I use getResultList()
and setMaxResults(1)
. That will simply return an empty List in case of no results, therefore no exception, therefore no rolled back tx.
Good luck :)
Chris
A similar problem with TransactionRolledbackLocalException
in GlassFish / Payara Server happened to me. But it was caused by a bug in GlassFish / Payara, nothing rolled back the transaction in my application. Restarting the domain fixed it, as described here: javax.ejb.TransactionRolledbackLocalException (Glassfish 3 + JPA + EclipseLink)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With