Issue
My current method contains transactions and it is correct because I use it in places where data is updated and created and I know that in these places the transaction is mandatory.
public static <T> Response<T> withinTransation(JPA jpa, String jpaPersistence, Logger logger, Invocation<T> invocation) {
EntityManager em = null;
EntityTransaction tx;
Response<T> result;
try {
em = jpa.getEntityManager(jpaPersistence);
tx = em.getTransaction();
try {
tx.begin();
result = invocation.invoke(em);
tx.commit();
return result;
}
finally {
if (tx.isActive())
tx.rollback();
}
}
catch (PersistenceException | ResourceNotFoundException e) {
logger.error("Exception in method invocation", e);
return new Response<T>(Status.SERVER_ERROR);
}
finally {
if (em != null && em.isOpen())
em.close();
}
}
I need to write another method (the same) that will not contain a transaction. The method will be used in places where data is not modified, but only retrieved from the database. Below is an example of my method without transaction. The question for you is is this a correct workaround or is there any other better way to do it?
public static <T> Response<T> withoutTransation(JPA jpa, String jpaPersistence, Logger logger, Invocation<T> invocation) {
EntityManager em = null;
Response<T> result;
try {
em = jpa.getEntityManager(jpaPersistence);
result = invocation.invoke(em);
return result;
}
catch (PersistenceException | ResourceNotFoundException e) {
logger.error("Exception in method invocation", e);
return new Response<T>(Status.SERVER_ERROR);
}
finally {
if (em != null && em.isOpen())
em.close();
}
}
Solution
You can actually use same method, but customize the way how transaction is obtained.
Steps you need:
- Create your own dummy
EntityTransaction
implementation that does nothing, literally.
class FakeEntityTransaction implements EntityTransaction {
@Override
public void begin() { }
@Override
public void commit() { }
@Override
public void rollback() { }
@Override
public void setRollbackOnly() { }
@Override
public boolean getRollbackOnly() { return false; }
@Override
public boolean isActive() { return false; }
}
- Modify your method by passing the strategy of obtaining the transaction as an argument:
public static <T> Response<T> method(
JPA jpa, String jpaPersistence, Logger logger, Invocation<T> invocation,
Function<EntityManager, EntityTransaction> transactionProvider // new argument
) {
EntityManager em = null;
EntityTransaction tx;
Response<T> result;
try {
em = jpa.getEntityManager(jpaPersistence);
// tx = em.getTransaction(); // rigid strategy (old)
tx = transactionProvider.apply(em); // flexible strategy (new)
// ... other stuff
- Explicitly choose strategy whenever you need:
Function<EntityManager, EntityTransaction> withTransaction = em -> em.getTransaction();
Function<EntityManager, EntityTransaction> withoutTransaction = em -> new FakeEntityTransaction();
// will be executed within real transaction
method(jpa, jpaPersistense, logger, invocation, withTransaction);
// will be executed within fake (noop) transaction
method(jpa, jpaPersistense, logger, invocation, withoutTransaction);
P.S. I'd recommend to consider usage of Spring AOP Transactions to get rid off explicit boilerplate code that covers transaction stuff. https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/transaction.html#transaction-declarative
Answered By - Nikolai Shevchenko