I am using oracleDB and elasticsearch for persistence and if anything goes wrong in that method, it throws a custom exception. Now I need to rollback from DB if any thing fails in elastic also.
I have already added #transactional annotation on service classes. And everything I found on web.
#Transactional(rollbackOn = BaseException.class)
public void transaction(ab ab, a a) {
persistenceService.save(a);
persistenceService.updateSignalCountDb(a.abc(), a.bcd(), ab);
elasticService.saveSignal(a);
try {
elasticService.updateSignalCountElastic(a);
} catch (Exception e) {
throw new BaseException(ErrorCodes.FAILED_ELASTIC_SEARCH_UPDATE, e);
}
}
persistenceService.save() method saves in db.
persistenceService.updateSignalCountDb() method update another table in db.
elasticService.saveSignal() method saves in elastic. throws base exception in case of failure.
elasticService.updateSignalCountElastic() method update another index in elastic. It also calls elasticService.delete() method to delete anything saved in elastic in case of failure and throws a base exception.
I expected this would work in case of any failure in the entire method. But if anything fails on elastic, i get a base exception but my data from oracle db doesn't rollback.
At first glance it looks like you are doing everything right. But before you search for the reason why it doesn't work, small change in your code would prevent the problem from occurring in the first place:
#Transactional(rollbackOn = BaseException.class)
public void transaction(ab ab, a a){
try {
elasticService.saveSignal(a);
elasticService.updateSignalCountElastic(a);
persistenceService.save(a);
persistenceService.updateSignalCountDb(a.abc(), a.bcd(), ab);
}catch (Exception e){
throw new BaseException(ErrorCodes.FAILED_ELASTIC_SEARCH_UPDATE, e);
}
}
In this case your exception will occur before you even save in DB and you won't have a problem
If you are using Spring #Transactional, I researched there is only
rollbackFor option but not rollbackOn(I'm not sure this), like
this: #Transactional(rollbackFor = BaseException.class).
Make sure Transactional is enabled in spring configuration: #EnableTransactionManagement
In your case, only BaseException would trigger rollback, other Exception would not.
Related
I want to throw a custom error class when a user searches my repo with an invalid ID. This should be very straight forward, but I cannot seem to catch any errors thrown by JpaRepository. I have made several attempts to solve this, but the following is my most straight forward attempt:
try {
Object obj = repository.getOne(id)
}
catch (EntityNotFoundException e) {
throw CustomException("message");
}
When running this in a debugger, repository throws the exact exception I am expecting javax.persistence.EntityNotFoundException, but the code simply skips over my catch statement and the function returns with an error.
I tried using repository.findById(id) with similar results. I also tried catching Exception and Throwable. Any ideas? I will add more information to my post if it ends up my problem is not immediately obvious.
getOne() is just a wrapper for EntityManager.getReference(). That method will not throw any exception.
It returns an uninitialized proxy, assuming that the entity indeed exists. It doesn't get the entity state from the database, and thus doesn't even know if it exists. It assumes it does.
You'll only get an exception later, if you try to access the state of the entity.
Use findById()/findOne(), check if you get a non-empty/non-null result (because these methods don't throw any exception if the entity doesn't exist, they return empty or null), and throw your exception if that's the case.
You can try this:
try {
Object obj = repository.findById(id).orElseThrow(EntityNotFoundException::new);
}
catch (EntityNotFoundException e) {
throw CustomException("message");
}
You need to annotate your repository class with #Repository annotation to perform wrapping of exceptions to specific persistence exceptions.
What regarding question - you don't need to use getOne() with potential exception, but could use some other methods without throwing errors in case if entity not present, like findById() or another one that return Optional<> object
you can catch DataAccessException instead of EntityNotFoundException
try {
Object obj = repository.getOne(id)
}
catch (DataAccessException e) {
throw CustomException("message");
}
In my service, have handled DataIntegrityViolationException when calling myCrudRepository.saveAndFlush to handle concurrent persist (insertion) requests. It works and I can catch the exception. After this, I prefer to make sure if the exception is exactly because entity already exists, not due to any other possible unknown issues. So, I call myCrudRepository.exists(entity.getId()) , but DataIntegrityViolationException is thrown again.
Here is my simple code:
private void save(final Employee entity) throws MyAppException {
try{
this.myCrudRepository.saveAndFlush(entity);
}catch (org.springframework.dao.DataIntegrityViolationException e){
// check if the error is really because the entity already exists
// entity.getId() is already generated before any save. it's like National ID
boolean exists = this.myCrudRepository.exists(entity.getId()); // DataIntegrityViolationException is thrown here again!
if (exists)
throw new MyAppException(HttpStatus.CONFLICT, "Entity already exists.");
else
throw e;
}
}
But if I use findOne instead of exists, it works fine. It's somehow strange, but of course it has a technical reason that I'm not good enough to make a guess.
Any idea? Thanks in advance.
The problem is when you are using Transactional method and after the method returns the transaction will auto commit and hibernate will wrap up the exceptions into another one. Actually the exception is occurring when transaction commits and at that time you are out of that method already. To catch the exception from inside the method you can simply use em.flush() after this.myCrudRepository.saveAndFlush(entity);.
I'm not sure if I'm asking it right, but I've got a method that's transactional, and within that I fetch a list of items. Now, while processing one of the items, I encounter an invalid data usage exception[null is passed to a repository, and fine one throws this exception which is fine], and upon getting that exception, I catch it, and mark the item being processed 'failed'. The transactional method ends but the changes do not go through.
#Transactional
public void method(){
try{
List items = itemRepo.getItems(NOT_SUBMITTED);
for(Item item: items){
processItem(item); //this is where the exception happens
}
}
catch(Exception e){
item.setStatus(FAILED) // this doesn't go through to the db
}
}
private void processItem(Item item){
otherRepo.findOne(item.X); //item.X is null, and I get "invalid data usage" exception
}
I think the object somehow is getting detached; not sure why. I did a test throwing an exception myself in the try block, worked perfectly. The status was reflected properly.
I tried grabbing this object again doing itemRepo.findOne(item).setState(FAILED), didn't work. I tried, itemRepo.save(item) - didn't work.
Any pointers?
Thanks
The exception is being thrown by the JPA implementation, which also sets the transaction status to setRollbackOnly. That's why you can save the failed state when it's you throwing the exception.
Once the JPA entityManager throws an exception it's done, you can't use that entityManager anymore. Saving the item status as failed would have to be in a separate transaction using a different entityManager instance.
From my DAO method I need to return a result (even if exception occured). I try to do it in such a manner by it doesn't work in case of exception(I have an exception: don't flush the Session after an exception occurs).
#Repository
#Transactional(rollbackFor=HibernateException.class)
public class UserDAO {
#Override
public boolean save(Proxy proxy) {
try{
sessionFactory.getCurrentSession().save(proxy);
}
catch(HibernateException e){
//TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return false;
}
return true;
}
}
However when I uncomment //TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
all works like I expect. I know that documentation don't recommend to use this approach, but annotation rollback doesn't work for me. Can you explain, please, why? Can I modify the code to get it work, if it is possible?
Spring will rollback only if the exception is thrown by the method. If it's thrown and caught inside the method, Spring doesn't know anything about the exception, and thus considers that everything went fine, and thus doesn't rollback the transaction.
So, either keep the call to setRollbackOnly, or, much preferred, don't use boolean results to indicate that an operation succeeded or failed. An exception should be thrown if the save wasn't successful.
rollbackFor attribute works if annotated method throws specified exception. In your case exception is catched inside the method and is not propagated up so rollbackFor has no effect.
If you do need to return a value instead of throwing an exception to your caller, you do need to rely on setRollbackOnly(), which I don't think is bad, but pretty standard way.
Besides, I think throwing an exception is a better idea because your caller has no notion of what goes wrong when there is a 'false'. Otherwise, you might need to return an error code or error message or something more meaningful than a 'false'.
I'm using JPA toplink-essential, building REST web app.
I have a servlet that find one entity and delete it.
Below code I thought I could catch optimistic lock exception in servlet level but its not!
Instead RollbackException is thrown, and that's what documentation says:
But then when I see the Netbean IDE GlassFish log, somewhere, optimisticLockException is thrown. It's just not being caught in my code. (my system print message doesn't get displayed so I'm sure its not going in there.)
I tried to import each packages (one at a time of course) and tested with catch clause but both time, it is not going into the catch block even though log error says "optimistic exception".
import javax.persistence.OptimisticLockException;
import oracle.toplink.essentials.exceptions.OptimisticLockException;
So where the OptimisticLockException is thrown?????
#Path("delete")
#DELETE
#Consumes("application/json")
public Object planDelete(String content) {
try {
EntityManager em = EmProvider.getInstance().getEntityManagerFactory().createEntityManager();
EntityTransaction txn = em.getTransaction();
txn.begin();
jObj = new JSONObject(content);
MyBeany bean = em.find(123);
bean.setVersion(Integer.parseInt(12345));
em.remove(bean);
//here commit!!!!!
em.getTransaction().commit();
}
catch(OptimisticLockException e) { //this is not caught here :(
System.out.pritnln("here");
//EntityTransactionManager.rollback(txn);
return HttpStatusHandler.sendConflict();
}
catch(RollbackException e) {
return HttpStatusHandler.sendConflict();
}
catch(Exception e) {
return HttpStatusHandler.sendServerError(e);
}
finally {
if(em != null) {
em.close();
}
}
Error msg:
[TopLink Warning]: 2011.01.28 05:11:24.007--UnitOfWork(22566987)
--Exception [TOPLINK-5006]
(Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))):
oracle.toplink.essentials.exceptions.OptimisticLockException
[TopLink Warning]: 2011.02.01 08:50:15.095--UnitOfWork(681660)--
javax.persistence.OptimisticLockException: Exception [TOPLINK-5006] (Oracle TopLink
Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))):
oracle.toplink.essentials.exceptions.OptimisticLockException
Not 100% sure, but could it be that you're catching javax.persistence.OptimisticLockException (notice the package), but as the thrown exception is oracle.toplink.essentials.exceptions.OptimisticLockException, it's not getting caught? Even though the name of exception-class is the same, they're not the same class.
I would guess that it is thrown in the em.getTransaction().commit(); statement.
Because the java doc of RollbackException if said:
Thrown by the persistence provider when EntityTransaction.commit() fails.
I strongly belive that this is not the code you realy use (it would not compile because of a missing ) in line bean.setVersion(Integer.parseInt(12345);), but i "hope" that the real code has the same problem.
Have you tried calling entityManager.flush(); inside your try/catch block? When JPA flushes is when the OptimisticLock exception is thrown.
Also you need not commit the transaction in the manner you did. You simply could have done txn.commit(); instead of em.getTransaction().commit();.
I have a similar situation where I am able to catch javax.persistence.OptimisticLockException. In my case I made the ReST endpoint a SSB and inject the entity manager. I then call a method on another SSB which is also injected and acts as a controller for this piece of biz logic. This controller performs a flush() and catches the OLEX and rethrows and ApplicationException which the Rest endpoint / SSB catches and retries. Using this pattern you also need to make sure to specify TransactionAttributeType.RequiresNew so that each retry you make occurs in a fresh transaction since the OLEX invalidates the old one.