Closed
Description
I propose to support to propagate a savepoint operation via TransactionSynchronization
.
Background
I'm the MyBatis's developer. Now, If application developer use the savepoint(=PROPAGATION_NESTED
) feature together with MyBatis, If perform select statement after rollback a savepoint, an application may get the rolled back data from MyBatis's local cache.
I want to the opportunity for clearing local cache that hold on MyBatis module at savepoint operations(at roll backed mainly) for resolving potential problem.
Related Issues
Example Testing Code
package com.example;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.transaction.support.TransactionTemplate;
@SpringBootTest
class MybatisSpringGh785ApplicationTests {
@Autowired
MessageMapper mapper;
@Autowired
PlatformTransactionManager transactionManager;
@Test
void contextLoads() {
// TransactionOperations for standard JDBC Transaction
DefaultTransactionDefinition txDef = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionOperations txOperations = new TransactionTemplate(transactionManager, txDef);
// TransactionOperations for JDBC savepoint Transaction
DefaultTransactionDefinition savepointTxDef = new DefaultTransactionDefinition(
TransactionDefinition.PROPAGATION_NESTED);
TransactionOperations savepointTxOperations = new TransactionTemplate(transactionManager, savepointTxDef);
// Insert test data
txOperations.executeWithoutResult(s -> {
mapper.insert(new Message(1, "Hello!"));
});
// Do Testing
txOperations.executeWithoutResult(txStatus -> {
// Select latest message and save to local cache
Message messageBefore = mapper.select(1);
// Execute processing on savepoint transaction
savepointTxOperations.executeWithoutResult(savepointTxStatus -> {
// Update and clear local cache
mapper.update(new Message(1, "Hello World!!"));
// Select latest message and save to local cache
mapper.select(1);
// Mark to rollback savepoint
savepointTxStatus.setRollbackOnly();
});
// Select latest message but does not return latest message from database because does not clear local cache when savepoint rollback
Message messageAfter = mapper.select(1);
Assertions.assertThat(messageAfter.message()).isEqualTo(messageBefore.message()); // Failed assertion
});
}
record Message(int id, String message) {
}
@Mapper
interface MessageMapper {
@Select("SELECT id, message FROM messages WHERE id = #{id}")
Message select(int id);
@Insert("INSERT INTO messages (id, message) VALUES(#{id}, #{message})")
void insert(Message message);
@Update("UPDATE messages SET message = #{message} WHERE id = #{id}")
void update(Message message);
}
}
CREATE TABLE messages
(
id bigint primary key,
message varchar(256) not null
);