MyBatis-Spring rollback not working with multiple transaction managers - spring

I have configured an application to work with MyBatis-Spring and I would like to connect to multiple databases.
For this purpose, in my applicationContext.xml I have defined one datasource, one Transaction Manager (org.springframework.jdbc.datasource.DataSourceTransactionManager), one Sql Session Factory (org.mybatis.spring.SqlSessionFactoryBean) and one MapperScannerConfigurer (org.mybatis.spring.mapper.MapperScannerConfigurer) for each one of them.
Then, inside my service class I would like to perform CRUD operations with multiple databases inside the same method. As I must point to the correct transaction manager I have done what is commented below:
#Service("myServiceDB")
public class MyServiceDB implements MyService {
[...]
#Transactional(value = "TransactionManager1", rollbackFor = MyCustomException.class)
public MyUser multipleMethod(final int idUser) throws MyCustomException {
MyUser myUser = null;
int rowsAffected1 = -1;
int rowsAffected2 = -1;
try {
myUser = this.mapperOne.getById(idUser);
if (myObject != null) {
rowsAffected1 = this.mapperOne.deleteByIdUser(idUser);
}
if (rowsAffected1 == 1) {
insertUser(myUser);
}
} catch (DataAccessException dae) {
throw new MyCustomException(TRANSACTION_ERROR, dae);
}
if ((myUser == null) || (rowsAffected1 != 1)) {
throw new MyCustomException(TRANSACTION_ERROR);
}
return myUser;
}
#Transactional(value = "TransactionManager2", rollbackFor = MyCustomException.class)
public void insertUser(final MyUser myUser) throws MyCustomException{
int rowsAffected = -1;
try {
rowsAffected = this.mapperTwo.insert(myUser);
**throw new MyCustomException();**
} catch (DataAccessException dae) {
throw new MyCustomException(TRANSACTION_ERROR, dae);
}
//if (rowsAffected != 1) {
// throw new MyCustomException(TRANSACTION_ERROR);
//}
}
[...]
}
So each method points to its corresponding transaction manager.
If I throw the custom exception in the second method after the insert, I get the delete made in the first method correctly rolled back. However, the insert performed by the second Transaction Manager is not rolled back properly as I would desire. (i.e. the user is inserted in the second database but not deleted in the first one).
My questions are:
Is it possible to achieve what I want?
How should I configure the #Transactional annotation?
Thanks in advance.

I found the solution here by #RisingDragon:
"If you are calling it from another local method then it will not work, because spring has no way of know that it is called and to start the transaction.
If you are calling it from a method of another class by using autowired object of the class which contains insertNotes() method, then it should work."
In my case, I created a second class (e.g. RisingDragom´s NoteClass) with some #Transactional methods (e.g. insertUser in my code) and then, the rollback worked!! This second class appeared in the debugger with the tail "$$EnhancedByCGLib".
However, if you need a method with several steps in different databases another "custom" rollback should be applied...The rollback is just applied method by method, not for the full process, so surely some data should be restored "by hand" in case of failure in any of the steps.

Related

Transaction propagation in spring #required

#GetMapping("trans")
#Transactional()
public String primaryTrans() {
User u1 = new User(0,"test","test#email.com");
us.save(u1);
User u2 = new User(0,"test1","test1#email.com");
us.save(u2);
secondaryTrans();
return "index";
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
private void secondaryTrans() {
// TODO Auto-generated method stub
User u2 = new User(0,"test2","test3#email.com".repeat(300));
us.save(u2);
}
Here i am manually raising DATA TOO LONG exception from secondary transaction, But it causes primary transaction also rolled back. How can we make sure that primary transaction to be committed irrespective of secondary transaction
In this case, since the second method is called from the same class, the second transaction is most likely not created. Springs transactional support uses AOP proxies to create transactions. The docs contain a description on why this will not work.
The simplest way is to catch the exception thrown from secondaryTrans() method, so just wrap secondaryTrans() into try-catch block:
try {
secondaryTrans();
} catch (Exception e) {
//...
}

Spring : Difference between method with #Transactional(propagation=Propagation.SUPPORTS) vs no #Transactional

What is the difference between method with #Transactional(propagation = Propagation.SUPPORTS) vs having no #Transactional annotation at all?
#Transactional(propagation = Propagation.SUPPORTS)
public void MyMethod()
vs
public void MyMethod()
Wouldn't the one without the annotation also use a transaction if one is already open, otherwise continue without any transaction?
There is a small difference. suppose we have two methods a() and b(), and a() is going to call b(). a itself is transnational and its propagation level is Propagation.REQUIRED but b one time is annotated with #Transactional(propagation = Propagation.SUPPORTS) and one time is not with annotation.
case 1:
#Transactional
public void a() {
for (int i = 0; i < 10; i++) {
try {
productRepository.b(i, "product " + i);
} catch (RuntimeException ex){
// do nothing
}
}
}
public void b(int id, String name) {
if(id > 5)
throw new RuntimeException();
String sql = "INSERT INTO test_table VALUES(?, ?)";
template.update(sql, id, name);
}
in case 1 we have aspect() -> a() -> b() and you are able to prevent RuntimeException to reach aspect and aspect inspects the transaction to see whether it is marked to rollback, so aspect consider this transaction successful and you can see we have this result in our database
0,product 0
1,product 1
2,product 2
3,product 3
4,product 4
5,product 5
even though multiple exceptions were thrown but we were able to commit the operations have been done so far.
now consider case 2:
#Transactional
public void a() {
for (int i = 0; i < 10; i++) {
try {
productRepository.b(i, "product " + i);
} catch (RuntimeException ex){
// do nothing
}
}
}
#Transactional(propagation = Propagation.SUPPORTS)
public void b(int id, String name) {
if(id > 5)
throw new RuntimeException();
String sql = "INSERT INTO test_table VALUES(?, ?)";
template.update(sql, id, name);
}
with propagation = Propagation.SUPPORTS if a transaction already exists it's going to use it. so if you throw an exception in b it is going to mark the same transaction to rollback and even if you use the try/catch block to prevent the RuntimeException to reach aspect in a() the transaction is already marked to rollback in b and you see no result in your database. aspect() -> a() -> aspect() -> b()
credit goes to Laurentiu Spilca, see this and read the comment section
From your link, it states that Propagation.SUPPORTS might have impact on
synchronization:
SUPPORTS is slightly different from no transaction at all, as it defines a transaction scope that synchronization will apply for. As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) will be shared for the entire specified scope
Yes one without any annotation will use existing transaction if one is already open but what if there is not any won't you want to revert the changes if your transaction fails.
Propagation has also other attribute has well in case you method is not dependant of parent transaction and runs in individual transaction scope, probably you can go for REQUIRES_NEW this way you can have independent transaction for your method.

Difference about Ajax and REST

I'm trying to understand a little problem I'm facing in a project that I'm working at the moment.
In my project, i'm the back end developer and I make some functions to be called by the front end with some Ajax functions. Today i'm facing a strange and random problem.
Whenever I use RESTful client to call my endpoints, it works pretty fine, without a single error in any of the individual endpoints. But when the front end calls them using ajax, it sometimes gets an error of "Can't operate on a closed Connection!" or "Can't operate on a closed Statement!", it's pretty random which one of them appears.
I'm using the following structure:
#Repository
public class RandomRepository {
#Autowired
private BasePersistence dao;
EntityManager em;
Session session;
public UserDTO getUserById(Long userId) {
if (userId == null)
return null;
em = BasePersistence.entityManagerFactory.createEntityManager();
em.getTransaction().begin();
session = em.unwrap(Session.class);
User user = session.doReturningWork(new ReturningWork<User>() {
#Override
public User execute(Connection con) throws SQLException {
try (PreparedStatement stmt = con.prepareStatement("SELECT * FROM tbl_users AS u WHERE id = "+ userId +";")) {
ResultSet rs = stmt.executeQuery();
User u = null;
while(rs.next()) {
u = new User(rs.getLong(1),rs.getString(2));
}
return u;
}
}
});
em.getTransaction().commit();
em.close();
return user != null ? new UserDTO(user) : null;
}
}
I use this same structure to make all my database operations.
I create the EntityManager > I begin the transaction > I unwrap the session > I execute a doWork or doReturningWork (depending of the type of query) > I commit > I close the EntityManager.
My question is, even by creating the EntityManager inside the method, could it be closed by another EntityManager created on another method like this by using Ajax to call the endpoints?
If that's the case, how could I prevent this problem?
Is this really a difference between the REST call and the AJAX call?

Handle Hibernate optimistic locking with Spring

I am using Hibernate and Spring Data, it will perform optimistic locking when insert or update an entity, and if the version in database doesn't match with the one to persist, it will throw exception StaleObjectStateException, in Spring, you need to catch it with ObjectOptimisticLockingFailureException.
What I want to do is catch the exception and ask the user to refresh the page in order to get the latest data from database like below:
public void cancelRequest()
{
try
{
request.setStatus(StatusEnum.CANCELLED);
this.request = topUpRequestService.insertOrUpdate(request);
loadRequests();
//perform other tasks...
} catch (ObjectOptimisticLockingFailureException ex)
{
FacesUtils.showErrorMessage(null, "Action Failed.", FacesUtils.getMessage("message.pleaseReload"));
}
}
I assume it will also work with the code below but I have not tested it yet.
public void cancelRequest()
{
RequestModel latestModel = requestService.findOne(request.getId());
if(latestModel.getVersion() != request.getVersion())
{
FacesUtils.showErrorMessage(null, "Action Failed.", FacesUtils.getMessage("message.pleaseReload"));
}
else
{
request.setStatus(StatusEnum.CANCELLED);
this.request = requestService.insertOrUpdate(request);
loadRequests();
//perform other tasks...
}
}
I need to apply this checking on everywhere I call requestService.insertOrUpdate(request); and I don't want to apply them one by one. Therefore, I decide to place the checking code inside the function insertOrUpdate(entity) itself.
#Transactional
public abstract class BaseServiceImpl<M extends Serializable, ID extends Serializable, R extends JpaRepository<M, ID>>
implements BaseService<M, ID, R>
{
protected R repository;
protected ID id;
#Override
public synchronized M insertOrUpdate(M entity)
{
try
{
return repository.save(entity);
} catch (ObjectOptimisticLockingFailureException ex)
{
FacesUtils.showErrorMessage(null, FacesUtils.getMessage("message.actionFailed"),
FacesUtils.getMessage("message.pleaseReload"));
return entity;
}
}
}
My main question is, there will be one problem with this approach. The caller side will not know whether the entity persisted successfully or not since the exception will be caught and handled inside the function, so the caller side will always assume the persist was success, and continue do the other tasks, which is I don't want. I want it to stop performing tasks if fail to persist:
public void cancelRequest()
{
try
{
request.setStatus(StatusEnum.CANCELLED);
this.request = topUpRequestService.insertOrUpdate(request);
//I want it to stop here if fail to persist, don't load the requests and perform other tasks.
loadRequests();
//perform other tasks...
} catch (ObjectOptimisticLockingFailureException ex)
{
FacesUtils.showErrorMessage(null, "Action Failed.", FacesUtils.getMessage("message.pleaseReload"));
}
}
I know when calling the insertOrUpdate , I can catch the returned entiry by declaring new model variable, and compare it's version to the original one, if version is same, means the persistance was failed. But if I doing it this way, I have to write the version checking code on everywhere I call insertOrUpdate. Any better approach then this?
The closest way to being able to do this and not having to necessarily make significant code changes at all the invocation points would be to look into some type of Spring AOP advice that works similar to Spring's #Transactional annotation.
#FacesReloadOnException( ObjectOptimisticLockingFailureException.class )
public void theRequestHandlerMethod() {
// call your service here
}
The idea is that the #FacesReloadOnException annotation triggers an around advice that catches any exception provided in the annotation value and does basically handles the call the FacesUtils should any of those exception classes be thrown.
The other options you have available aren't going to be nearly as straight forward and will require that you touch all your usage points in some fashion, its just inevitable.
But I certainly would not consider putting the try/catch block in the service tier if you don't want to alter your service tier's method return types because the controllers are going to need more context as you've pointed out. The only way to push that try/catch block downstream would be if you returned some type of Result object that your controller could then inspect like
public void someControllerRequestMethod() {
InsertOrUpdateResult result = yourService.insertOrUpdate( theObject );
if ( result.isSuccess() ) {
loadRequests();
}
else {
FacesUtils.showErrorMessage( ... );
}
}
Otherwise you'd need to get creative if you want to somehow centralize this in your web tier. Perhaps a web tier utility class that mimics your BaseService interface like the following:
public <T extends BaseService, U> U insertOrUpdate(T service, U object, Consumer<U> f) {
try {
U result = service.insertOrUpdate( object );
f.accept( result );
return result;
}
catch ( ObjectOptimisticLockingFailureException e ) {
FacesUtils.showErrorMessage( ... );
}
}
But being frank, unless you have a lot of call sites that are similar enough to where such a generalization with a consumer like this makes sense, you may find its more effort and work to generalize it than it would to just place the try/catch block in the controller itself.

CMT rollback : how to roll back the transaction

The following code does not help in roll back even if I throw null pointer exception at update() method. Everytime it inserts values into the database if I run the code. Please help me how can I roll back the transaction if null pointer is thrown at update() method. Am I missing something in the code?
#TransactionManagement(value = TransactionManagementType.CONTAINER)
public class Bean implements RemoteIF {
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void insertIntoDb() {
insert();
update();
}
private Integer update() {
val=0;
try {
Connection con = DbConn.getConnection();
Statement st = con.createStatement();
val1 = st.executeUpdate("INSERT INTO tab VALUES('ab')");
st.close();
throw new NullPointerException();
} catch (Exception e) {
System.out.println(e);
}
return val;
}
private Integer insert() {
int val = 0;
try {
Connection con = DbConn.getConnection();
Statement st = con.createStatement();
val = st.executeUpdate("INSERT INTO tab VALUES('bnm')");
st.close();
} catch (Exception e) {
System.out.println(e);
}
return val;
}
}
Couple things that stick out at me as suspect.
No #Stateless, #Stateful or #Singleton annotation on the Bean class. Unless you've declared the bean in an ejb-jar.xml file, then this is not getting recognized as an EJB. Definitely double check that.
The DbConn.getConnection() looks suspiciously like you might be trying to manage database connections yourself. If you have any code that uses the DriverManager or does new FooDataSource(), then that is definitely the problem. If you want transaction management to work you have to get all resources from the container via either
Injection via a #Resource DataSource datasource field in the EJB class
JNDI lookup of java:comp/env/yourDataSource, where yourDataSource is the name of a datasource you configured in the ejb-jar.xml or declared on the bean class via using #Resource(type=DataSource.class, name="youDataSource") -- that annotation goes on the class itself rather than a method or field.
See also these answers for some insight as to how transaction management works:
How does UserTransaction propagate?
Programming BMT - UserTransaction

Resources