We have a JavaEE application running in WildFly (18.0.1.final). Using JavaEE 8 with JDK 11.
In this application we have a few beans that persists data to a PostgreSQL database, using JPA (with hibernate). In the persistence.xml we define a JTA data source as follow:
<jta-data-source>java:jboss/datasources/CustomDS</jta-data-source>
Our persistence beans all follow the same convention (very watered-down generic version for illustration):
@Stateless
@Local(localinterface.class)
@Remote(remoteinterface.class)
public class GenericPersistenceBean implements localinterface, remoteinterface {
@PersistenceContext
private EntityManager entityManager;
public SomeResponse persist(SomeRequest someRequres) {
....
checkIfDataAlreadyPersisted(entity);
....
entityManager.persist(entity);
....
}
private void checkIfDataAlreadyPersisted(SomeEntity entity) {
CriteriaQuery ....
.....
entityManager.createQuery(...);
}
}
The reason for this question is, we have one persistence bean (UserPersistenceBean) in particular that does a few more checkIfDataAlreadyPersisted
compared to the others. And when invoking this UserPersistenceBean we get an exception:
Caused by: javax.persistence.TransactionRequiredException: WFLYJPA0060: Transaction is required to perform this operation (either use a transaction or extended persistence context)
By using a UserTransaction
and adding the @TransactionManagement(TransactionManagementType.BEAN)
annotation to the UserPersistenceBean, the exception is no longer thrown when invoking the service directly and the data gets persisted. However, when invoking the UserPersistenceBean from another bean such as:
@Stateless
@Local(UserCreatorLocalinterface.class)
@Remote(UserCreatorRemoteinterface.class)
public class UserCreatorBean implements UserCreatorLocalinterface, UserCreatorRemoteinterface {
@Inject
private UserPersistenceInterfaceLocal userPersistence;
public SomeResponse createUser(SomeRequest someRequres) {
.....
userPersistence.persistNewUser();
.....
}
}
then the following exception is thrown:
WFLYEJB0137: Only session and message-driven beans with bean-managed transaction demarcation are allowed to access UserTransaction.
I would really appreciate if someone could please shed some light on:
- Why the UserTransaction is necessary for a particular bean?
- Is it because the service has more 'entityManager.createQuery()' calls?
- How can we handle the above 'WFLYEJB0137' exception in the higher-level bean (UserCreatorBean for example)?
- Is it good practice either way to force the transactions with UserTransaction? As it is not required for any of the other persistence beans used in the same scenarios.
Edit
Below should give some more context to the UserPersistenceBean. I apologize in advance, I know that it is very vague but it is as much as I can provide considering the code is proprietary and it won't be very good for me to share more.
I will understand if it's insufficient, however, if someone could at least help me to understand why UserTransaction is necessary when the beans does around three or more checkIfAlreadyPersisted calls (entityManager.createQuery(criteriaQuery)
) then I should be able to resolve the rest.
@Stateless
@Local(UserPersistenceInterfaceLocal.class)
@Remote(UserPersistenceInterfaceRemote.class)
@TransactionManagement(TransactionManagementType.BEAN)
public class UserPersistenceBean implements UserPersistenceInterfaceLocal, UserPersistenceInterfaceRemote {
@PersistenceContext
private EntityManager entityManager;
@Resource
private UserTransaction utx;
public SomeResponse persistNewUser(SomeRequest someRequest) {
....
checkIfUserAlreadyPersisted(entity);
....
checkIfUserIdentifierExist(entity); // <--- Our own generated identifier based on some business logic
....
checkIfUserAccountAlreadyPersisted(entity);
....
utx.begin();
entityManager.persist(entity);
utx.commit();
....
}
private void checkIfUserAlreadyPersisted(SomeEntity entity) {
CriteriaQuery ....
Predicate exists = criteriaBuilder.equal(root.get(User_.username), entity.username);
.....
entityManager.createQuery(...);
}
private void checkIfUserIdentifierExist(SomeEntity entity) {
List<Long> userIdentifiers = getUserIdentifier(entity.getUserIdentifier());
CriteriaQuery ....
Join<User, UserIdentifiers> join = root.join(User_.userIdentifier);
Expression expression = join.get(UserIdentifiers_.identifier);
Predicate exists = expression.in(accounts);
.....
entityManager.createQuery(...);
}
private List<Long> getUserIdentifier(String userIdentifier) {
CriteriaQuery ...
....
Predicate exists = criteriaBuilder.equal(root.get(UserIdentifiers_.userIdentifier), userIdentifier);
.....
entityManager.createQuery(...);
}
private void checkIfUserAccountAlreadyPersisted(SomeEntity entity) {
List<Long> accounts = getAccount(entity.getAccount().getAccountNumber());
CriteriaQuery ....
Join<User, Account> join = root.join(User_.account);
Expression expression = join.get(Account_.identifier);
Predicate exists = expression.in(accounts);
.....
entityManager.createQuery(...);
}
private List<Long> getAccount(String accountNumber) {
CriteriaQuery ....
....
Predicate exists = criteriaBuilder.equal(root.get(Account_.accountNumber), accountNumber);
.....
entityManager.createQuery(...);
}
}