Good evening, guys.
I have a method called from an endpoint that saves an objects on my database, and I'd like to add some "protection" to this code by rolling back the transaction in the case of any exception occur inside the service.
I'm using JAX-RS for the REST API part and OracleDB as the database. My code is as follows.
Endpoint:
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response createAcordo(AcordoDTO acordoDTO) {
return acordosService.save(acordoDTO, token);
}
Service:
@Transactional(rollbackOn = Exception.class)
public Response save(AcordoDTO acordoDTO, String token) {
try {
... some bisiness logic and validation
Acordo acordo = acordoRepository.save(AcordoConverter.toAcordo(acordoDTO));
UserDTO usr = getUserInfoFromToken();
ApprovalDTO approvalDTO = new ApprovalDTO ();
approvalDTO .setUser(usr.getName()); // this method is throwing null pointer exception
// because usr is null
return Response.status(Response.Status.CREATED).entity(acordo).build();
} catch (Exception e) {
return new SystemErrorException().toResponse();
}
}
The code above, despite the exception and @Transactional anotation, is saving the new Acordo
object to the database. What I'd expect to happen, however, is that the full transaction was rolledback and no objects were saved.
In summary, what I'd like to know is:
- I'm misundertaing any concepts regarding @Transactional usage ?
- How to fix this code to achieve de desired goal (rollback) ?
PS.1 - I tried without try/catch
block and the result is the same. The save() still commits the object to the database.
PS.2 - The acordoRepository.save() has @Transaction annotation.
PS.3 - I tried adding TransactionSynchronizationRegistry.setRollbackOnly()
inside the catch block and it worked. However (for me) it looks like more like a cheat than a proper solution.
PS.4 - After I used TransactionSynchronizationRegistry.setRollbackOnly()
the transaction status was STATUS_MARKED_ROLLBACK
and in all other tests it was STATUS_ACTIVE
.
UPDATE 1
My acordoRepository.save()
is as follows. I tried to check whether there was one or two transacions. In order to do that, I tried (not sure it's a good idea) to print the tsr.getTransactionKey()
both on my Service.save()
and acordoRepository.save()
and the result was the same key, which led me to believe that there's only one transaction.
@Resource
TransactionSynchronizationRegistry tsr; // I injected it here
// for testing purposes
@Transactional
public T save(T entity) {
if (entity.getId() == null) {
entityManager.persist(entity);
} else {
entityManager.merge(entity);
}
entityManager.flush();
return find(entity.getId());
}
UPDATE 2:
My persistence.xml
is as follows.
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="namePU" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<non-jta-data-source>java:/nameDS</non-jta-data-source>
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<jar-file>lib/client-notification-1.30.jar</jar-file>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
<property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
<property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform"/>
<property name="hibernate.connection.autocommit" value="false"/>
<property name="hibernate.show_sql" value="false"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.generate_statistics" value="false"/>
<!-- desabilita JSR-303 no save/update -->
<property name="javax.persistence.validation.mode" value="none"/>
</properties>
</persistence-unit>
</persistence>
Best regards,
Thomaz.
CodePudding user response:
If you want the @Transactional
method to rollback you must not catch the exception inside that method. Remove the catch(Exception)
from save
and catch it in createAcordo instead. That should work.
CodePudding user response:
may be you can try with Throwable.class instead Exception.class since throwable is super class of if with remove try/catch by the way bydefault spring will take care of rollBack but even if we are declaring explicitly then it will check for checked/unchecked exception specifically- rollbackFor = {AnyException.class} for checked exception becuse runtimeException will take care by spring itself you may look this example: https://www.netsurfingzone.com/spring/transactional-rollbackfor-example-using-spring-boot/
CodePudding user response:
Whell after I couldn't make comment with code :) I'm making an answer: I think that you can just use an exception like this:
@Transactional(rollbackOn = CustomeException.class)
public Response save(AcordoDTO acordoDTO, String token) {
try {
... some bisiness logic and validation
if(! valid)
throw new CustomeException();
Acordo acordo = acordoRepository.save(AcordoConverter.toAcordo(acordoDTO));
UserDTO usr = getUserInfoFromToken();
ApprovalDTO approvalDTO = new ApprovalDTO ();
approvalDTO .setUser(usr.getName()); // this method is throwing null pointer exception
// because usr is null
// if this code throws any exception then throw runtime exception in your catch bloke
return Response.status(Response.Status.CREATED).entity(acordo).build();
} catch (Exception e) {
throw new CustomeException();
}
}