Catch DataIntegrityViolationException in #Transactional service method - java

I have a REST Spring boot app with Hibernate. For simplicity let's assume this workflow:
Controller handles incoming requests, calls Service methods
Service methods are #Transactional, do some business logic and call Persistence methods
Persistence methods are handled by DAO objects, saving stuff into database.
The database has a unique constraint on username of a User. The way I have it working now is this:
Client sends request to Controller
Controller calls Service
Service attempts to save an object through DAO. If DataViolationException occurs, Service returns a custom Exception
Controller catches the custom Exception and sends back appropriate response
The pseudocode is this:
public class UserController {
#RequestMapping("/user")
public User createUser(...){
try{
return userService.createUser(...);
} catch (UserAlreadyExistsException e){
// Do some processing and return error message to client
}
}
}
public class UserService {
#Transactional
public User createUser(...){
(...)
try{
userDAO.save(newUserObject);
} catch(DataIntegrityViolationException e){
throw new UserAlreadyExistsException(username);
}
}
}
However, this way I am getting an error when a duplicate user is attempted to be created.
javax.persistence.RollbackException: Transaction marked as rollbackOnly
One way to fix this seems to be to let the DataIntegrityViolationException "bubble" up from the transaction (and not catch it in Service). But that means that the Controller has to handle persistence exceptions and I don't like that.
I prefer if the service threw "understandable" exceptions for the Controller to handle. The service knows what persistence exceptions to expect and when and is able to "translate" the broad DataIntegrityViolationException into a meaningful one.
Is there a way to handle the exceptions this way? I don't particularly like the idea of having a "2-layered service layer" to achieve this.
EDIT: Another reason I want to throw my custom Exception is that it is required by the compiler to be caught. I want to enforce the controller to handle all possible exceptions that may occur.

Your repository need to extends JpaRepository, and when you do that. You can use saveAndFlush method from that repository. That mean, you code will be immediately executed on database and exception will be throwed before finish transaction and you will be able to catch it in Catch block. I added also sample for deleting operation.
Repository:
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserDAO extends JpaRepository<User, Long> {
}
Service:
public class UserService {
private UserDAO userDAO;
(...)
#Transactional
public User createUser(...){
(...)
try{
userDAO.saveAndFlush(newUserObject);
} catch(DataIntegrityViolationException e){
throw new UserAlreadyExistsException(username);
}
}
#Transactional
public void deleteUser(...){
(...)
try{
userDAO.delete(deletingUserObject);
userDAO.flush();
} catch(DataIntegrityViolationException e){
throw new UserException(username);
}
}
}

Annotate your service method with
#Transactional(rollbackFor = UserAlreadyExistsException.class)
it will tell spring to not commit the transaction if the Exception is thrown so you will be able to catch it in your controller

Related

Spring Data: JPA and Nested Transaction

I am having a problem with Spring JPA Data and nested transactions. Following are two methods with a nested transaction of my service.
#Service
public UserService {
#Transactional
public User createUser(UserDto userDto) {
....
user = saveUser(user);
sendEmail(user);
....
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public User saveUser(User user) {
return userRepository.save(user);
}
It happens there is one scenario that the method userRepository.save() should throw an exception but somehow is not being thrown, it looks like it is waiting the parent transaction to be finished. I was expecting the exception being thrown on the saveUser method and the sendEmail method not even to be executed.
Because the method UserService.saveUser have the propagation set to Propagation.REQUIRES_NEW I was expecting that transaction to be commited (the SQL statement to be executed) and any exception being propagated.
I did not setup anything related with Transaction, so i believe that the flush mode is set to AUTO.
Can anyone spot what i am doing wrong or what is my misconception?
It's because you're invoking #Transactional method from within same bean.
#Transactional only works on methods invoked on proxies created by spring. It means, that when you create a #Service or other bean, method called from the outside will be transactional. If invoked from within bean, nothing will happen, as it doesn't pass through proxy object.
The easiest solution would be to move the method to another #Service or bean. If you really want to keep it within same component, then you need to invoke it, so that it gets wrapped in proxy by spring AOP. You can do this like that:
private YourClass self;
#Autowired
private ApplicationContext applicationContext;
#PostConstruct
public void postContruct(){
self = applicationContext.getBean(YourClass.class);
}
Then invoking method on self would result in opening a transaction.
In other words: you are not experiencing any of those anomalies, because #Transactional over saveUser does not work.

JTA how to do not rollback anything?

I want to rollback my transaction on an exception which works absolutely fine. But now I do not want to rollback all actions I made.
For example, a request to my application gets processed and several database actions on multiple databases are done. If the exception is thrown I want to rollback all actions on one of my databases and only 2 actions on the second database. How can I do that? I always end up rolling bback the whole transaction...
what I tried so far:
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public <Param> GenericResponseMsg executeRequest (Param myParam) {
entityManager1.persist(someEntity); // rollback
...
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void addFailedRequestToDatabase() {
entityManager2.persist(otherEntity); // do not rollback
...
}
I also tried to annotate the class with
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
but this results in the following exception:
Internal Exception: java.sql.SQLException: Connection can not be used while enlisted in another transaction
Error Code: 0
Any ideas? I am somehow stuck and don't know what to do anymore...
EDIT:
Here is the workflow you asked for:
#Stateless
#Path("my/path")
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyRessource {
#EJB
private MyEJB myEjb;
#POST
#Path("method/Path")
#Consumes({MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_JSON})
public GenericResponseMsg doSomeStuff(Param param) throws Exception {
try {
return myEjb.executeRequest(param);
} catch(Throwable throwable) {
myEjb.addFailedRequestToDatabase();
throw throwable;
}
}
}
#TransactionAttribute is support for EJB as per the specification. #Transactional support is coming in tomee 7 (currently under vote #asf).
Side note: tomee and openejb standalone code is 100% the same for JTA support.

Proxied REQUIRES_NEW method doesn't rollback (spring)

I have spring injecting a service into itself to allow the service to make transactional calls to itself. Unfortunately, I'm finding that a requires_new method that is throwing a NullPointerException and being caught is not rolling back the new transaction. The outer transaction is not interrupted which is what I want, but I'm having trouble explaining why the requires new transaction isn't rolled back. Any ideas?
#Service(value="orderService")
#Transactional
public class OrderServiceImpl implements OrderService {
#Resource
private OrderService orderService; // transactional reference to this service
public void requiredTransMethod(){
try {
orderService.requiresNewTransMethod();
}catch(Throwable t){
LOG.error("changes from requiresNewTransMethod call should be rolled back right?", t);
}
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewTransMethod() {
// database changes that are NOT rolled back
throw new NullPointerException("bla bla bla");
}
}
This could be an instance of transactional annotations not working because you are calling them from within the same class.
The way Spring's AOP implementation works (by default) is by using proxy classes, which will not work as expected for method invocations from within the same class.

Cant catch exception in EJB

I am building a restful webservice using Jax-RS and EJB3 running on wildfly 8. The system is splited into two parts. A rest module and a persistence module. The rest module consists of a rest servlet with a method, reacheable under "mycontext/rest/do". This method calls the method "do()" of a stateless bean which represents the interface of the persistence module. "do()" calls "persistEntity(Entity e)" of another stateless bean which represents a facade (kind of an interface pattern) of a entity class. The entity I want to persist has a few constraints. If they get violated an exception like "ConstraintViolationException" gets thrown (by the EJB container I think). I try to catch the exception in one of the two stateless beans in the persistence module but this is not working. That means that the code in the catch block is not executed. But catching the exception in the servlet works.
Rest servlet (catching exception works here):
#Path("/rest")
public class AccountRestService {
#Inject
private Service service;
#POST
public void do(Entity entity) {
service.do(entity);
}
}
Bean representing interface of the persistence module (catching exception is ignored):
#Stateless
#LocalBean
public class Service {
#Inject
private EntityFacade facade;
public void do(Entity entity) {
facade.persist(entity);
}
}
Entity Facade (catching exception is ignored):
#Stateless
public class EntityFacade{
#PersistenceContext(unitName = "primary")
private EntityManager em;
public void create(Entity entity){
em.persist(entity); // this line throws exception like ConstraintViolationException
}
}
That's expected. The exception is thrown at flush time. The flush happens at transaction-commit time. And the commit happens after the EJB method has returned successfully.
So, contrary to what you say, it's not the persist() method which throws the exception (the stack trace would confirm that), but the method of the transactional proxy which wraps your EJB and commits the transaction.
There is really nothing you can do with such an exception except
show an error message to the user. In that case, catching the exception in the rest service, or letting a generic exception handler handle it is the right thing to do
retry the transaction. In that case, you should have an intermediate layer (or method) between the rest service method and the EJB, which calls the transactional EJB, catches the exception and retries.

Roll back transaction after exception in JPA + Spring

I'm using Spring and JPA with HIbernate underneath. When a PersistenceException is thrown, I want to catch it and return the error message so that it is not propagated up to the caller.
#Transactional
public String save(Object bean) {
String error = null;
try {
EntityManager entityManager = getEntityManager();
for (int i = 0, n = entities.size(); i < n; i ++) {
entityManager.merge(entities.get(i));
}
}
catch (PersistenceException e) {
error = e.getMessage();
}
return error;
}
But I get an exception saying that javax.persistence.RollbackException: Transaction marked as rollbackOnly. I get that the transaction needs to be rolled back after an exception but how do I roll it back when I've catched the exception and do not want to re-throw it?
By using #Transactional if there are any RuntimeExceptions thrown in the method, it will automatically perform the rollback. You don't need to manually do it. You probably shouldn't be catching that exception at all and instead let it pass to a higher level ExceptionHandler that shows some standard error page to the user (not the stack trace). Also your method is marked void but you are returning a String.
You can use Spring's Exception Translation with a custom PersistenceExceptionTranslator to translate PersistenceException into something useful.
Oh, btw, you shouldn't use #Transactional at the DAO level. Transactions should be started at the service level.
It appears that there is no way to roll back a failed transaction managed by Spring ORM. The code shown in the question is a service class. Extracting its persistence routine to a separate DAO class and having the service class handle PersistenceExceptions did the trick.
Use #Transactional(noRollbackFor={PersistenceException.class}) on the method that is throwing the exception.

Categories

Resources