Skip to content

Support to propagate a savepoint operation via TransactionSynchronization #30509

Closed
@kazuki43zoo

Description

@kazuki43zoo

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
);

Metadata

Metadata

Assignees

Labels

in: dataIssues in data modules (jdbc, orm, oxm, tx)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions