How to make one JPA repository Transactional but another not? - java

I have method where I'm trying to change some entity, also in this method I want to save transaction information.
When any exception occurred I want to roll back saving entity but still want to save transaction.
So how to make one repository for entity transactional but repository for transactions not?
There code from repository
#Override
#Transactional(noRollbackFor=NotEnoughAmountInAccountException.class)
<T extends Transaction> T save(T transaction);
but it doesn't help.
Saving transaction placed in final block.
UPDATE
I solved it by using AOP. I create Transaction object in aspect advice and save it here, out of JPA transaction.

Do it in a new #Transactional
#Transactional(propagation=Propagation.REQUIRES_NEW)
<T extends Transaction> T save(T transaction);
This will save your transaction element even if the other #Transactional gets rolled back

I solved it by using AOP. I create Transaction object in aspect advice and save it here, out of JPA transaction.
It's #Transactional method
#SaveTransaction
#Transactional
public synchronized Transaction move(#NonNull String accountName, #NonNull String destinationName, #NonNull BigDecimal amount, String comment) {
checkAmountIsPositive(amount);
Account donor = getAccount(accountName);
Account acceptor = getAccount(destinationName);
if (!isEnoughAmount(accountName, amount)) throw new NotEnoughAmountInAccountException();
BigDecimal newDonorAmount = donor.getAmount().add(amount.negate());
BigDecimal newAcceptorAmount = acceptor.getAmount().add(amount);
donor.setAmount(newDonorAmount);
acceptor.setAmount(newAcceptorAmount);
accountRepository.save(donor);
accountRepository.save(acceptor);
return null;
}
And it's aspect advice
#Around("#annotation(com.softjourn.coin.server.aop.annotations.SaveTransaction)")
public Transaction saveTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
Transaction transaction = prepareTransaction(joinPoint);
try {
joinPoint.proceed();
transaction.setStatus(TransactionStatus.SUCCESS);
return transaction;
} catch (Throwable e) {
transaction.setStatus(TransactionStatus.FAILED);
transaction.setError(e.getLocalizedMessage());
throw e;
} finally {
transactionRepository.save(transaction);
}
}
Also it's important to make order of this advice higher than order of #Transactional so this advice will be over transaction.
Set #Order(100) on aspect class.
By default it's order is smaller so it's under transaction.

Related

How to perform another DB query in the same transaction in case of unique constraint violation in Java Spring #Transactional method?

I am struggling with handling exceptions during transactional methods with Spring #Transactional annotations. Currently, when my Dao insert method throws a unique contraint violation, I want to perform a read instead to get the existing item. However, this fails because the UniqueIdViolation causes the transaction to fail. How can I refactor this to work as intended whilst not doing a dodgy boolean return on the insert?
I have a DAO with an insert method, e.g.:
public class ItemDao {
public void insertItem(Item item) {
// insert into db via jooq
}
public Item fetch(String id) {
// fetch from db
}
...
}
If it violates a unique constraint I have an aspect that handles the DataAccessException and rethrows it as a UniqueIdException.
I have a service method that takes in some information and creates an Item, before returning that item.
If the dao throws a UniqueIdException, I want the service to catch this error and then call itemDao.find() to get the existing Item and return it.
public class MyServiceImpl implements MyService {
#Transactional(isolation = SERIALIZABLE)
public Item createItem(...) {
Item item = new Item(...);
try {
itemDao.insert(item);
return item;
} catch (UniqueIdException ex) {
return itemDao.find(item.getId());
}
}
}
The issue here is when itemDao.insert(item) throws an exception this causes the transaction to fail and therefore itemDao.find() doesn't run.
#Transactional is setup to rollback, by default, only when an unchecked exception is thrown.
Since DataAccessException is unchecked exception your transaction will be rolled backed.
A better strategy will be to first call fetch() if returned null then call insertItem

Hibernate - Rollback list of entities if one entity fails

im just working on a project to create, change user in my mysql database. Therefore i have UserService (REST) which creates a user and a GenericDAO class where i can persist users. In my DAO for each user i begin, persist and commit a transaction. Creating single users or find users works perfect.
Now i am facing with the problem to persist or update a list of users. Especially if one user can not be persisted (e.g. duplicates) the hole transaction should be rolled back. It doesnt work in my current setup.
My first idea is to outsource the commit in a separate method. With an loop over all users i only persist them. At the end of the loop i would call my method to commit everything. If a single or more users fails i can catch them with the rollback. Is that a good approach?
AbstractDAO (current)
public abstract class GenericDAO<T> implements IGenericDAO<T>{
#PersistenceContext
protected EntityManager em = null;
private CriteriaBuilder cb = null;
private Class<T> clazz;
public GenericDAO(Class<T> class1) {
this.clazz = class1;
this.em = EntityManagerUtil.getEntityManager();
this.em.getCriteriaBuilder();
}
public final void setClazz(Class<T> clazzToSet) {
this.clazz = clazzToSet;
}
public T create(T entity) {
try {
em.getTransaction().begin();
em.persist(entity);
em.getTransaction().commit();
return entity;
} catch (PersistenceException e) {
em.getTransaction().rollback();
return null;
}
}
public T find(int id) {
return em.find(this.clazz, id);
}
public List<T> findAll() {
return em.createQuery("from "+this.clazz.getName()).getResultList();
}
/** Save changes made to a persistent object. */
public void update(T entity) {
em.getTransaction().begin();
em.merge(entity);
em.getTransaction().commit();
}
/** Remove an object from persistent storage in the database */
public void delete(T entity) {
em.getTransaction().begin();
em.remove(entity);
em.getTransaction().commit();
}
Wouldn't the most convenient solution be to simply add methods like createAll()/updateAll()?
Adding separate public methods for starting and persisting the transaction like start() and commit() creates a whole bunch of problems because it means you suddenly introduce a stateful conversation between the Dao and its clients.
The Dao methods now need to be called in a certain order and, worse still, the state of the EntityManager transaction is retained. If you forget to commit() at the end of one service call using your Dao, a subsequent call is going to mistakenly assume a transaction was not yet started, and that call is going to fail 'for no apparent reason' (not to mention that the original call will appear completed when in reality the transaction was left hanging). This creates bugs that are hard to debug, and tricky to recover from.
EDIT As I already pointed out in the comment below this answer, getting programmatic transaction management right is tricky in a multi-layer application structure, and so, I would recommend to have a look at declarative transaction management.
However, if you insist on managing transactions yourself, I would probably introduce sth like a TransactionTemplate:
public class TransactionTemplate {
private EntityManager em; //populated in a constructor, for instance
public void executeInTransaction(Runnable action) {
try {
em.getTransaction().begin();
action.run();
em.getTransaction().commit();
} catch (Exception e) {
em.getTransaction().rollback();
} finally {
em.clear(); // since you're using extended persistence context, you might want this line
}
}
}
and use it in a service like so:
public class UserService {
private TransactionTemplate template;
private RoleDao roleDao;
private UserDao userDao; //make sure TransactionTemplate and all Daos use the same EntityManager - for a single transaction, at least
public void saveUsers(Collection<User> users, String roleName) {
template.executeInTransaction(() -> {
Role role = roleDao.findByName(roleName);
users.forEach(user -> {
user.addRole(role);
userDao.create(user);
});
// some other operations
});
}
}
(of course, using the above approach means only one layer - the service layer in this case - is aware of transactions, and so DAOs must always be called from inside a service)

How to implement saving in a Repository with JPA/Hibernate

What I want is to implement the Repository pattern in a JPA/Hibernate application. I have a generic interface that describes the basic contract of my repositories:
public interface EntityRepository<Entity extends Object, EntityId> {
Entity add(Entity entity);
Entity byId(EntityId id);
void remove(Entity entity);
void removeById(EntityId id);
void save();
List<Entity> toList();
}
And here is an implementation of such an interface:
public class EntityRepositoryHibernate<Entity extends Object, EntityId>
implements Serializable,
EntityRepository<Entity, EntityId> {
private static final long serialVersionUID = 1L;
#Inject
protected EntityManager entityManager;
protected Class<Entity> entityClass;
public EntityRepositoryHibernate(Class<Entity> entityClass) {
this.entityClass = entityClass;
}
public EntityManager getEntityManager() {
return entityManager;
}
#Override
public Entity add(Entity entity) {
entityManager.persist(entity);
return entity;
}
#SuppressWarnings("unchecked")
#Override
public Entity byId(EntityId id) {
DetachedCriteria criteria = criteriaDAO.createDetachedCriteria(entityClass);
criteria.add(Restrictions.eq("id", id));
return (Entity)criteriaDAO.executeCriteriaUniqueResult(criteria);
}
#Override
public void remove(Entity entity) {
if(entity==null)
return;
entityManager.remove(entity);
}
#Override
public void removeById(EntityId id) {
remove(byId(id));
}
#Override
public List<Entity> toList() {
throw new UnsupportedOperationException("toList() not implemented in "+entityClass.getName());
}
#Override
public void save() {
entityManager.flush();
}
}
All methods are working fine, except save(), so this is the focus here.
As far as I understand, Hibernate is able to track all changes in any instance returned by a query (the byId() method). So, the idea of the save() method is to save any instances that where retrieved and changed, that's why the method does not receives any parameters, it is supposed to save everything that has to be saved (which means, any persistent instance that was retrived and somehow updated while the repository lives.
In a possible scenario, I could call byId() 10 times to retrieve 10 different instances and change only 4 of them. The idea is that by calling save() once, those 4 instances would be saved in the data server.
Problem is when I call flush() I receive an exception stating that there is no transaction active. Since I'm using a JTA persistence unit, it's illegal to open the transation programatically by calling entityManager.getTransaction().
Considering that, what to do to fix the code?
First of all, it seems that your are missunderstanding the purpose of EntityManager.flush method. It doesn't commit any changes managed by persistence context, just sends SQL instructuions to the database. I mean, for the same JTA transaction, when you retrieve and modify some entity instances the changes/SQL instructions are cached waiting to be sent to the database. If the underlying transaction is commited this changes are flushed to the database along with the commit instruction. If you invoke flush before transaction is commited, only flush the changes until the invokation point (well, some SQL instruction could have been flushed previously by reasons out of this matter) but not the commit instruction is sent.
How to fixed?
I suggest you to don't mix Repository Pattern with Transaction manipulation.
Looks like you are using Container Managed Transactions (javaee tutorial) so just erase the save method and let container to manage the transactions. This will change your focus, you now have to care about rolling back transactions (throwing exception or invoking setRollbackOnly) but you don't need to explicit commmit.

How to return different value, when transaction rollback?

I use ssh framework to develop a web application.
There is an example of my transaction.
#Transactional
public StudentEntity addStudent(StudentEntity studentEntity) {
return studentDAO.save(studentEntity);
}
Now, I want to return null when exception is thrown and then transaction rollback.
In general it is not advised to return null.
If you anticipate any Exception from your logic you should inform the caller via throws clause so that they are prepared for such scenarios.
Regarding rollback you should consider below update to your #Transactional annotation
#Transactional(rollbackFor=Exception.class)
Do note that this will rollback transaction after throwing any exception.
To rollback transaction programatically take a look at TransactionAspectSupport class.
#Transactional
public StudentEntity addStudent(StudentEntity studentEntity) {
try {
return studentDAO.save(studentEntity);
}
catch(Exception ex) {
//set transaction for rollback.
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
You could do it declarative manner
#Transactional(rollbackFor={SomeSpecificException.class, SomeOtherSpecificException.class})

How to rollback the first transaction and not rollback the new transaction?

I have a method that save a object but I need consumes an API and save their return. If the API return is "not authorized", I need rollback the transaction but I want preserve the return.
E.g.
#Resource
private SessionContext context;
#Transactional
public Invoice createSale(SaleDTO saleDTO) {
this.dao.save(saleDTO);
Send send = this.context.getBusinessObject(Send.class);
Invoice invoice = this.send.send(saleDTO);
if (invoice.isAuthorized()) {
invoice.setSale(saleDTO);
return invoice;
} else {
throw new IllegalArgumentException();
}
}
public class Send implements Serializable {
#Transactional(Transactional.TxType.REQUIRES_NEW)
private Invoice send(SaleDTO saleDTO) {
Invoice invoice;
...
this.dao.save(invoice);
return invoice;
}
}
When I thrown the IllegalArgumentException, the invoice is not saved. I need save it.
Annotating a private method, or even a public method called from another method of the same class, can't work.
Transactional handling is based on proxies.
A transaction can only be started if you call a transactional method on another bean, injected in the current bean, so that the transactional proxy wrapping the other bean intercepts the call and starts a transaction.
Read https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#tx-decl-explained. You don't seem to use Spring, but the way it works in Java EE is the same.

Categories

Resources