I have the following code:
try {
userDAO1.save(userRecord);
userDAO2.save(userRecord);
}
catch(DataIntegrityViolationException e) {
throw new ApplicationException("Contraint violated")
}
userDAO1.save(userRecord) violates an integrity constraint - so after the entire code has been run, there is nothing written to the table userDAO1 refers to.
However, the userDAO1.save() statement doesn't throw an error/exception - so userDAO2.save() is executed as well.
But the DataIntegrityViolationException is caught, and the stack trace is null.
How do I check where the DataIntegrityViolationException is thrown from, and prevent userDAO2.save() from being executed if userDAO1.save() violates a constraint?
I tried adding a #Transactional annotation around this code, but that didn't work either.
Stack trace:
org.springframework.dao.DataIntegrityViolationException: ORA-00001: unique constraint (UNIQUE_EMAIL) violated
; SQL [n/a]; constraint [UNIQUE_EMAIL]; nested exception is org.hibernate.exception.ConstraintViolationException: ORA-00001: unique constraint (UNIQUE_EMAIL) violated
at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:643)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:104)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:516)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy76.updateUser(Unknown Source)
at com.osiris.UserReg.UpdateUserCommand.execute(UpdateUserCommand.java:63)
The code I've posted is in UpdateUserCommand, which is annotated with #Transactional(rollbackFor=Exception.class, propagation=Propagation.REQUIRES_NEW)
Ok, this is a bit of a tricky one, but I'll do my best.
Hibernate will only commit a transaction when the method annotated with #Transactional exits. Hence your DataIntegrityViolationException will only be catchable after that method returns. There is no way that you can get Hibernate to not call UserDAO2.save() because it can't detect that a violation has occurred. I'll provide an example below
#Service
/*These variable names are used for clarity's sake, I don't actually use these names myself*/
public UserServiceImpl implements UserService{
#Autowired
private HibernateUserDAO1 userDao1;
#Autowired
private HibernateUserDAO2 userDao2
#Transactional
/*Put your try catch block around where this method is called*/
public void saveUserDao1(User user){
userDao1.saveOrUpdate(user);
}
#Transactional
/*Only call this if saveUserDao1 succeeds*/
public void saveUserDao2(User user){
userDao2.saveOrUpdate(user)
}
}
Then in your HibernateUserDAO1:
public void saveOrUpdate(User user){
currentSession().saveOrUpdate(user);
}
The exception can only be caught above your service layer. Ideally what you want to be doing, is individual saves using 2 different DAO's and checking that the first succeeded before doing the second.
EDITED:
Also be aware that Hibernate will not pick up private methods annotated with #Transactional because Hibernate depends on creating Proxy objects from the interface that your class implements. No interface definition = no proxy object = no Hibernate Session. So you can't call a private method annotated with #Transactional. I'd try to make your SessionFactory an object in an abstract superclass and have both DAO's inherit from this. A better option is to use 2 transaction managers each pointing to your different databases, then specify which database things are saving too. That way you can use just 1 DAO, and use whichever session factory you require to do your saves.
What made you to believe that DataIntegrityViolationException was not thrown while the statement userDAO1.save() was executed? Also, why do you believe that the statement userDAO2.save() was executed as well?
If the above opinions were made upon observations of code execution progression in an IDE debug console such as that of Eclipse then the interpretations might be wrong.
Please try to observe the results by punching-in some debug statements like the ones below, and executing the code. This may help you to find out the root cause of the failure -
try {
userDAO1.save(userRecord);
System.out.println("-- After userDAO1.save(userRecord) --");
userDAO2.save(userRecord);
System.out.println("-- After userDAO2.save(userRecord) --");
} catch(DataIntegrityViolationException e) {
e.printStackTrace();
throw new ApplicationException("Contraint violated")
}
Related
I have a test method which sometimes fails during deploy and sometimes does not. I have never seen it fail on my local. You can see my code below.
I have the following retry mechanism which is asynchronously called from another service:
#Transactional
public boolean retry(NotificationOrder order) {
notificationService.send(order);
return true;
}
public void resolveOnFailedAttempt(Long orderId) { //automatically called if `retry` method fails
notificationOrderCommonTransactionsService.updateNotificationOrderRetryCount(orderId);
}
The notification service is like this :
#Service
#RequiredArgsConstructor
public class NotificationServiceImpl implements NotificationService {
private final NotificationOrderCommonTransactionsService notificationOrderCommonTransactionsService;
#Override
#Transactional
public NotificationResponse send(NotificationOrder order) {
NotificationRequest request;
try {
request = prepareNotificationRequest(order);
} catch (Exception e) {
notificationOrderCommonTransactionsService.saveNotificationOrderErrorMessage(order.getId(),
e.getMessage());
throw e;
}
...
return response;
}
private void prepareNotificationRequest(NotificationOrder order) {
...
throw new Exception("ERROR");
}
}
And the commmon transactions service is like this :
#Transactional(propagation = Propagation.REQUIRES_NEW)
public NotificationOrder saveNotificationOrderErrorMessage(Long orderId, String errorMessage) {
NotificationOrder order = notificationRepository.findOne(orderId);
order.setErrorDescription(errorMessage);
notificationRepository.save(order);
return order;
}
public NotificationOrder updateNotificationOrderRetryCount(Long orderId) {
NotificationOrder order = notificationRepository.findOne(orderId);
order.setRetryCount(order.getRetryCount() + 1);
order.setOrderStatus(NotificationOrderStatus.IN_PROGRESS);
notificationRepository.save(order);
return order;
}
Here is my integration test :
#Test
public void test() {
NotificationOrderRequest invalidRequest = invalidRequest();
ResponseEntity<NotificationOrderResponse> responseEntity = send(invalidRequest);
NotificationOrder notificationOrder = notificationOrderRepository.findOne(1);
softly.assertThat(notificationOrder.getOrderStatus().isEqualTo(NotificationOrderStatus.IN_PROGRESS))
softly.assertThat(notificationOrder.getErrorDescription())
.isEqualTo("ERROR"); //This the line that fails.
softly.assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
In the test method it is confirmed that updateNotificationOrderRetryCount is called and the order status is updated as IN_PROGRESS. However, the error message is null and I get the following assertion error :
-- failure 1 --
Expecting:
<null>
to be equal to:
<"ERROR">
but was not.
I expect saveNotificationOrderErrorMessage transaction to be completed and the changes to be committed before updateNotificationOrderRetryCount method is called. But it seems like it does work that way. Could anyone help me find out why my code behave like this ?
How can I reproduce this error on my local? And what can I do to fix it ?
Thanks.
Try enabling SQL logging and parameter bind logging and look through the statements. I don't know all of your code, but maybe your are setting the message to null somewhere? It could also be, that the actions are interleaving somehow such that updateNotificationOrderRetryCount is called before/while saveNotificationOrderErrorMessage in a way that causes this. If both run right before commit, but saveNotificationOrderErrorMessage commits before updateNotificationOrderRetryCount, you could see the error message being overwritten with null.
If the code snippet of the question is accurate, pay attention to the fact that you are rethrowing the exception raised in the prepareNotificationRequest method, I assume in order to enable the retry mechanism:
NotificationRequest request;
try {
request = prepareNotificationRequest(order);
} catch (Exception e) {
notificationOrderCommonTransactionsService.saveNotificationOrderErrorMessage(order.getId(),
e.getMessage());
throw e; // You are rethrowing the exception
}
For your comment, the exception thrown extends RuntimeException.
As the Spring documentation indicates:
In its default configuration, the Spring Frameworkâs transaction infrastructure code marks a transaction for rollback only in the case of runtime, unchecked exceptions. That is, when the thrown exception is an instance or subclass of RuntimeException. ( Error instances also, by default, result in a rollback). Checked exceptions that are thrown from a transactional method do not result in rollback in the default configuration.
Probably Spring is performing rollback of the initial transaction, that one associated with saveNotificationOrderErrorMessage. I realize that this method is annotated as #Transactional(propagation = Propagation.REQUIRES_NEW) and that it is initiating a new transaction, but perhaps the problem could be related with it.
When the retry mechanism takes place, another transaction, associated with the invocation of the method updateNotificationOrderRetryCount is performed, and this transaction is successfully committed. This is the reason why the changes performed in this second method are properly committed.
The solution of the problem will depend on how your retry mechanism is implemented, but you can, for example, raise the original exception and, as a first step in the retry mechanism, trace the problem in the database or, raise a checked exception - Spring by default will not perform rollback for it - and handle it as appropriate.
Update
Another possible reason of the problem could be the transaction demarcations in the send method.
This method is annotated as #Transactional. As a consequence, Spring will initiate a new transaction for it.
The error occurs, and you trace the error in the database, in a new transaction but please, be aware that the initial transaction is still there.
Although not described in your code, in some way, the retry mechanism takes place, and updates the retry count. It this operation is performed within the initial transaction (or a higher level one), due to the transaction boundaries, database isolation levels, and related stuff, it is possible that this transaction, the initial, fetch an actually outdated, but current from the transaction boundary point of view, NotificationOrder. And this information is the one that finally is committed, overwriting the information of the error. I hope you get the idea.
One simple solution, maybe for both possibilities, could be to include the error message in the updateNotificationOrderRetryCount method itself, reducing the problem to a single transaction:
/* If appropriate, mark it as Transactional */
#Transactional
public NotificationOrder updateNotificationOrderRetryCount(Long orderId, String errorMessage) {
NotificationOrder order = notificationRepository.findOne(orderId);
order.setRetryCount(order.getRetryCount() + 1);
order.setOrderStatus(NotificationOrderStatus.IN_PROGRESS);
order.setErrorDescription(errorMessage);
// It is unnecessary, all the changes performed in the entity within the transaction will be committed
// notificationRepository.save(order);
return order;
}
To simplify my scenario I would give the following example:
I'm using a Spring Boot application with spring.jpa.properties.hibernate.order_inserts=true property.
Inside a java method marked with #Transactional annotation I want to perform a batch insert of a few thousand records with pure hibernate. The batch insert is performed by a separate method which is called by the main method with #Transactional. I'm doing it like that:
#Transactional
public void doSomeStuff() {
......
insertRecords(records, session);
......
}
private void insertRecords(final List<Record> records, final Session session) {
int counter = 0;
int batchSize = 50;
for(Record r: records){
session.save(r);
counter ++;
if(counter > 0 && counter % batchSize == 0){
session.flush();
session.clear();
}
}
}
The problem is that I want the operation to be atomic and if one of the batch inserts, fails everything to be rolled back. I know #Transactional anotation will guarantee rollback automatically for RunTimeException or Error. Does this mean that if one of the batches fails #Transactional will cover the rollback or I should surround the for cycle with try/catch block and throw a checked exception and use the rollbackFor propery of #Transactional? I'm not sure if the exceptions thrown during batch failure are checked or unchecked.
Another important question is does in this scenario hibernate commit every batch automatically in case of successfull insert? This would mean that if one batch fails I wouldn't be able to rollback the already committed once which breaks the atomicity.
In addition, I don't want to manage the commits manually and want to handle this to the transactional context provided by Spring.
You are right. By default , #Transactional will only rollback for RuntimeException and Error but not for checked exception. That means if you want to also rollback the checked exception , you have to catch all the checked exception and re-throw it as the RuntimeException or simply use rollbackFor setting in #Transactional.
I'm not sure if the exceptions thrown during batch failure are checked
or unchecked.
The java compiler will help to check it.If some of the inner methods throw checked exception ,it requires you must handle it which you can neither catch it or specify to throw this checked exception in the method declaration. The codes cannot be compiled if you do not do so.
That means if your codes can compile, no checked exception is thrown from the inner methods and you can simply stick to the current settings. On the other hands, if some checked exception are thrown from the inner methods , you can choose to catch it and re-throw it as RuntimeException :
#Transactional
public void doSomeStuff() {
try{
}catch(Exception ex){
throw new RuntimeException("something goes wrong.." ,ex);
}
}
or configure rollbackFor
#Transactional(rollbackFor={Exception.class})
public void doSomeStuff() throws Exception{
}
Also , please note that session.flush() is not the same as committing transaction, so your codes will not commit for every batch. Instead, transaction will commit after the method marked with #Transactional return successfully which in your case is the returned of doSomeStuff() when all the records are inserted.
The point of flush() is for the subsequent session.clear() which clear the memory from the session such that it will not cause the JVM to run out of memory if you insert a lot of records.
I have a SpringBoot 2 project and i'm using spring data jpa with hibernate with MySQL5.7
I have problems with the following use case: i have a service method that calls another service's method. If second service's method generates a runtime exception, also the first method is marked as rollback and i cannot commit things anymore. I'd like to only rollback second method and still commit something in the first one.
I tried to use propagation.NESTED but nested transaction are not allowed with hibernate (even if jpaTransactionManager supports them and MySQL supports savepoints).
How can i solve this problem? Can i configure nested in some way?
Please remember i need second method to see changes committed by first so i can't mark the second method as propagation.REQUIRES_NEW
Here is come sample code to clarify my problem:
FirstServiceImpl.java
#Service
public class FirstServiceImpl implements FirstService
#Autowired
SecondService secondService;
#Autowired
FirstServiceRepository firstServiceRepository;
#Transactional
public void firstServiceMethod() {
//do something
...
FirstEntity firstEntity = firstServiceRepository.findByXXX();
firstEntity.setStatus(0);
firstServiceRepository.saveAndFlush(firstEntity);
...
boolean runtimeExceptionHappened = secondService.secondServiceMethod();
if (runtimeExceptionHappened) {
firstEntity.setStatus(1);
firstServiceRepository.save();
} else {
firstEntity.setStatus(2);
firstServiceRepository.save();
}
}
SecondServiceImpl.java
#Service
public class SecondServiceImpl implements SecondService
#Transactional
public boolean secondServiceMethod() {
boolean runtimeExceptionHappened = false;
try {
//do something that saves to db but that may throw a runtime exception
...
} catch (Exception ex) {
runtimeExceptionHappened = true;
}
return runtimeExceptionHappened;
}
So the problem is that when secondServiceMethod() raises a runtime exception it rollback its operations (and that's OK) and then set its return variable runtimeExceptionHappened to false, but then firstServiceMethod is marked as rollback only and then
firstEntity.setStatus(1);
firstServiceRepository.save();
isn't committed.
Since i can't use NESTED propagation how can i achieve my goal?
I would suggest you break them up into two separate transactions.
In the first transaction do all of the work presently in firstServiceMethod that you know you want to commit. (e.g. through saveAndFlush). Now as you exit this method the changes are committed, so they will be available to subsequent calls.
Then have whatever called firstServiceMethod call a new Transactional method setFirstEntityStatus() that calls secondServiceMethod and sets the status of the entity as appropriate.
Basically, instead of attempting to NEST the transactions, split them into two fully separate transactions and use the ordering to ensure the result of the 1st is available to the 2nd.
I have entity Foo, which maps to sql table with some unique constraints. Thus saving Foo may fail. I am using FooDao to save Foo:
#Repository
public class FooDao
{
#Autowired
private SessionFactory sessionFactory;
#Transactional
#Override
public void add(Foo item) {
sessionFactory.save(item);
}
}
when I call method FooDao#add(Foo) it may fail for two reasons: either because of unique constraint violation (in this case, I know how to handle the problem) or because of some other problem (in this I probably should propagate the exception). How do I distinguish between those two situations?
I could add method find(Foo item) to FooDao and check, whether something like item, which I was trying to add is database. But this would require additional select from database and I am a bit worried about this.
Thats actually SQLState.
do something like this
Catch(HibernateException he){
SQLException sqe = he.getSQLEception();
String sqlState = sqe.getSQLState();
if(sqlState.equals("23000"){
// Handle your exception
}
}
Java doc:
SQLState - an XOPEN or SQL:2003 code identifying the exception
One link I found for ISO sqlStates,
link to reference
But look for exact reference and value..
One obvious (but maybe nasty) solution is that you catch javax.persistence.PersistenceException and parse the error message for "violant" or "constraint".
From my point of view you should do the select/find upfront. Remember that you are using an ORM! Hibernate has caches involved so neither the select/find nor the key contraint error might be the result of an actual db query but the result of an calculation of Hibernate based on your already in cache loaded data.
Sebastian
Catch org.hibernate.JDBCException. This has getErrorCode(). For unique constraint voilation its ORA-00001.
-Maddy
If there is a database exception it gets caught in a HibernateException, which is a checked exception. Spring wraps this in a DataAccessException, which is unchecked. This exception will run up to your Controller and out to the Servlet and end up in a stack trace in the browser and log file. You can catch this exception and print it out so you can see what is happening. This will at least get you to the point where you know what is actually breaking.
Bad keys is probably one issue. But bad values is probably another. Some non-null fields are null, or something isn't long/short enough etc. Fixing this probably involves validation. You can use the Hibernate Validator. You give your fields some nifty annotations and then you get validation errors in java before you even get to the database - errors happen faster.
We have a Java application running on JBoss 5.1 and in some cases we need to prevent a transaction from being closed in case a JDBCException is thrown by some underlying method.
We have an EJB method that looks like the following one
#PersistenceContext(unitName = "bar")
public EntityManager em;
public Object foo() {
try {
insert(stuff);
return stuff;
} (catch PersistenceException p) {
Object t = load(id);
if (t != null) {
find(t);
return t;
}
}
}
If insert fails because of a PersistenceException (which wraps a JDBCException caused by a constraint violation), we want to continue execution with load within the same transaction.
We are unable to do it right now because the transaction is closed by the container. Here's what we see in the logs:
org.hibernate.exception.GenericJDBCException: Cannot open connection
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Cannot open connection
at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:614)
...
Caused by: javax.resource.ResourceException: Transaction is not active: tx=TransactionImple < ac, BasicAction: 7f000101:85fe:4f04679d:182 status: ActionStatus.ABORT_ONLY >
The EJB class is marked with the following annotations
#Stateless
#TransactionManagement(TransactionManagementType.CONTAINER)
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
Is there any proper way to prevent the transaction from rolling back in just this specific case?
You really should not try to do that. As mentioned in another answer, and quoting Hibernate Docs, no exception thrown by Hibernate should be treated as recoverable. This could lead you to some hard to find/debug problems, specially with hibernate automatic dirty checking.
A clean way to solve this problem is to check for those constraints before inserting the object. Use a query for checking if a database constraint is being violated.
public Object foo() {
if (!objectExists()) {
insertStuff();
return stuff();
}
// Code for loading object...
}
I know this seems a little painful, but that's the only way you'll know for sure which constraint was violated (you can't get that information from Hibernate exceptions). I believe this is the cleanest solution (safest, at least).
If you still want to recover from the exception, you'd have to make some modifications to your code.
As mentioned, you could manage the transactions manually, but I don't recommend that. The JTA API is really cumbersome. Besides, if you use Bean Managed Transaction (BMT), you'd have to manually create the transactions for every method in your EJB, it's all or nothing.
On the other side, you could refactor your methods so the container would use a different transaction for your query. Something like this:
#Stateless
public class Foo {
...
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public Object foo() {
try {
entityManager.insert(stuff);
return stuff;
} catch (PersistenceException e) {
if (e.getCause() instanceof ConstraintViolationException) {
// At this point the transaction has been rolled-backed.
// Return null or use some other way to indicate a constrain
// violation
return null;
}
throw e;
}
}
// Method extracted from foo() for loading the object.
public Object load() {
...
}
}
// On another EJB
#EJB
private Foo fooBean;
public Object doSomething() {
Object foo = fooBean.insert();
if (foo == null) {
return fooBean.load();
}
return foo;
}
When you call foo(), the current transaction (T1) will be suspended and the container will create a new one (T2). When the error occurs, T2 will be rolled-backed, and T1 will be restored. When load() is called, it will use T1 (which is still active).
Hope this helps!
I don't think it's possible.
In may depend on your JPA provider, but, for example, Hibernate explicitly states that any exception leaves session in inconsistent state and thus shouldn't be treated as recoverable (13.2.3. Exception handling).
I guess the best thing you can do is to disable automatic transaction management for this method and create a new transaction after exception manually (using UserTransaction, as far as I remember).