Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The query cache is not cleared after transaction rolls back to a savepoint #785

Open
luozhenyu opened this issue Feb 16, 2023 · 3 comments

Comments

@luozhenyu
Copy link
Contributor

// assume I inserted a row with 2 columns(id = 1, value = 10);
mapper.insert(1, 10);

// Obviously, the result is 10;
int value = mapper.selectValueById(1);

// Then create a savepoint by Spring
Object savepoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();

// Update its value to 20
mapper.updateValueById(1, 20);

// Select its value, the value is 20 
int value = mapper.selectValueById(1);

// Rollback to savepoint, the value is 10 in database now
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savepoint);

// Select its value again, but the value is still 20 which is retrieved from local cache
int value = mapper.selectValueById(1);
@kazuki43zoo
Copy link
Member

kazuki43zoo commented May 14, 2023

The cache feature belong the MyBatis core module. Probably, the MyBatis core module does not support to clear cache when rollback savepoint. You can prevent this behavior with localCacheScope set to STATEMENT instead of SESSION (default) or call the SqlSession#clearCache().

@harawata
Do you have any comments?

@harawata
Copy link
Member

MyBatis' cache is not aware of Spring transaction's savepoint.
You may have to use the workarounds proposed by @kazuki43zoo .

@luozhenyu
Copy link
Contributor Author

@harawata How about merge the following code into mybatis-spring-boot-starter? I solved this problem by add a clear cache post processor just like spring-tx did.

import org.aopalliance.intercept.MethodInterceptor;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionalProxy;

import java.lang.reflect.Method;

/**
 * Clear mybatis cache after transaction rollback
 * <p>Spring will not notify mybatis to clear cache after a savepoint rollback. This class
 * catches a exception, clears the mybatis cache and rethrows it back</p>
 *
 * @author luozhenyu
 */
@Component
public class MybatisClearCachePostProcessor extends AbstractAdvisingBeanPostProcessor implements InitializingBean {

    private final AnnotationTransactionAttributeSource annotationTransactionAttributeSource
        = new AnnotationTransactionAttributeSource();

    private final SqlSessionTemplate sqlSessionTemplate;

    @Autowired
    public MybatisClearCachePostProcessor(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }

    @Override
    public void afterPropertiesSet() {
        StaticMethodMatcherPointcutAdvisor clearCacheAdvisor = new StaticMethodMatcherPointcutAdvisor() {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                if (targetClass != null && TransactionalProxy.class.isAssignableFrom(targetClass)) {
                    return false;
                }
                TransactionAttribute attribute = annotationTransactionAttributeSource.getTransactionAttribute(method, targetClass);
                return attribute != null && attribute.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED;
            }
        };

        clearCacheAdvisor.setAdvice((MethodInterceptor) invocation -> {
            try {
                return invocation.proceed();
            } catch (Throwable t) {
                Method method = invocation.getMethod();
                Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;

                TransactionAttribute attribute = annotationTransactionAttributeSource.getTransactionAttribute(method, targetClass);
                if (attribute != null && attribute.rollbackOn(t)) {
                    sqlSessionTemplate.clearCache();
                }
                throw t;
            }
        });

        this.advisor = clearCacheAdvisor;
        this.beforeExistingAdvisors = true;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants