mybatis guice 事务代理切面

mybatis Guice 事务源码解析的基础上,我们得以自定义事务代理切面

 

 

 1 

myorm【重点】

单数据源事务

引用计数传播法

 

2 目前做法,mybatis-guice框架下

单数据源事务

引用计数传播法

 

3

单数据源事务,支持多第一数据源事务,运行期第二数据源不享受回滚

引用计数传播法

与2的区别在于,3支持第二数据源作为service层开启事务

 

4 mybatis-guice

多数据源事务

sqlmanager绑定传播法

 

 

 

模型

 N=0 1 2 3 4  5 (2022.7.25)
service tran

以con1开启事务

N++

以sqlManager1开启事务

N++

以sqlManager1 or 2开启事务

N++

if(!isSessionInherited1) 

以sqlManager1开启事务

以sqlManager1 or 2开启事务

N1++

  mapper1.xxx 执行 执行 执行 执行 执行
  mapper2.xxx? 数据源报错

以sqlManager2非事务执行

SqlSessionInterceptor finally

sqlSession.close

以sqlManager2非事务执行

SqlSessionInterceptor finally

sqlSession.close

以sqlManager2非事务执行

SqlSessionInterceptor finally

sqlSession.close

以sqlManager2非事务执行

SqlSessionInterceptor finally

sqlSession.close

  dao tran  

本打算开启事务

但N==1,退出

N++

本打算开启事务

但N==1,退出

N++

if(!isSessionInherited2) 

以sqlManager2开启事务

N1==1,N2==0

N2++

开启事务

    mapper2.xxx 数据源报错

以sqlManager2非事务执行

SqlSessionInterceptor finally

sqlSession.close

以sqlManager2非事务执行

SqlSessionInterceptor finally

sqlSession.close

以sqlManager2事务执行

以sqlManager2事务执行
     非事务 非事务

if(!isSessionInherited2) 

提交sqlManager2

N2==1 

提交sqlManager2

  catch

    rollback? throw

N!=1

退出

N!=1

退出

N!=1

退出

if(!isSessionInherited2) 

回滚mapper2

N2==1

回滚mapper2

  finally          
    close? return?

N--

N!=0

退出

N--

N!=0

退出

N--

N!=0

退出

if(!isSessionInherited2) 

sqlManager2.close

N2--

N2==0

sqlManager2.close

  N==1

提交mapper1

N==1

提交mapper1

N1==1

提交mapper1

if(!isSessionInherited1) 

提交sqlManager1

N1==1

提交mapper1

catch

  rollback 

N==1

回滚mapper1

N==1

回滚mapper1

N==1

回滚mapper1

if(!isSessionInherited1) 

回滚mapper1

无法回滚mapper2

N1==1

回滚mapper1

finally          
  close

N--

N==0

return con1

N--

N==0

sqlManager1.close

N--

N==0 s

qlManager1.close

if(!isSessionInherited1) 

sqlManager1.close

N1--

N1==0

sqlManager1.close

*isSessionInherited 借用局部变量,相当聪明 

 

4* 

isSessionInherited1=SqlSessionManager1.isManagedSessionStarted=SqlSessionManager1.localSqlSession.get() != null==false

借助非静态threadlocal,ThreadLocal内存泄漏问题实践(三)非静态threadlocal,得以实现多数据源事务嵌套,当然也是不完全版

1 与2 3 4 的根本区别:非静态threadlocal多数据源事务应用

4 与 2 3 的根本区别:在非静态threadlocal基础上,更准确的把握多数据源事务传播,2 3 忽略内层无论第1还是第2数据源的开启事务请求

TransactionalMethodInterceptor实践下来:

(1)

service

@Transactional
public void multi() {
myMapper1.insert(); 以事务1回滚
myMapper2.insert(); 以非事务执行
myDao.multi();
int i=1/0;
}

dao

@Transactional
public void multi() {
myMapper2.insert2();  以事务2提交
}

 

(2)

service

    @Transactional
public void multi() {
myMapper1.insert();  以事务1回滚
myMapper2.insert();  以非事务执行
myDao.multi();
// int i=1/0;
}

dao

@Transactional
public void multi() {
myMapper2.insert2();    以事务2回滚
int i=1/0;
}

 本例使用方案2时,dao层语句是以非事务执行的,故没有回滚

因为dao层在内层,N!=0,不开启事务

 

(3)

service

    @Transactional
public void multi() {
myMapper1.insert();  以事务1回滚
myMapper2.insert();  以非事务执行
myDao.multi();
// int i=1/0;
}

dao

//    @Transactional
public void multi() {
myMapper2.insert2();  以非事务执行
int i=1/0;
}

 

项目实际情况:

第一数据源有事务,第二数据源只有简单的查询,不需要事务,用方案2

import com.google.inject.Injector;
import org.apache.ibatis.session.SqlSessionManager;

import java.lang.reflect.*;
import java.sql.SQLException;

public class OdsTransactionProxyFactory implements InvocationHandler {

    private static final ScefLogbackFactory.ScefLogger logger = ScefLogbackFactory.getLogger(OdsTransactionProxyFactory.class);

    private static final ThreadLocal<Integer> hasInTransaction = new ThreadLocal<>();

    private static volatile SqlSessionManager sqlSessionManager = null;

    private Object target;
    public OdsTransactionProxyFactory(Object target){
        this.target = target;
    }

    private <T> T getBeanFromFactoryGuice(Class<T> c) {
        Injector injector = CRFGuiceContext.getInjector();
        return injector.getInstance(c);
    }

    private Class getBeanInjectAnnotationGuice() {
        return com.google.inject.Inject.class;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Class clProxy = target.getClass();
        Field [] fields = clProxy.getDeclaredFields();
        for(Field field : fields) {
            field.setAccessible(true);
            if(!field.isAnnotationPresent(getBeanInjectAnnotationGuice()))
                continue;
            String name = field.getName();
            Class fieldType = field.getType();
            Object obj = getBeanFromFactoryGuice(fieldType);
            if(obj != null)
                field.set(target, obj);
        }

        SCEF_DB_TRANSACTIONAL scef_db_transactional = method.getAnnotation(SCEF_DB_TRANSACTIONAL.class);
        if(scef_db_transactional != null) {
            return invokeHasTransactional(scef_db_transactional, method, args);
        } else {
            return invokeNoTransactional(method, args);
        }

    }

    private Object invokeHasTransactional(SCEF_DB_TRANSACTIONAL scef_db_transactional, Method method, Object[] args) throws Throwable {

        Boolean readOnly = scef_db_transactional.readOnly();
        int isolation = scef_db_transactional.isolation();
        try {
            startTransaction(readOnly, isolation);
            Object returnValue = method.invoke(target, args);
            commit();
            return returnValue;
        } catch (InvocationTargetException ie) {
            Throwable throwable = ie.getTargetException();
            Class c1 = throwable.getClass();
            Class [] c2 = scef_db_transactional.noRollbackFor();
            int sum = 0;
            for(Class c : c2) {
                if(c.equals(c1))
                    sum ++ ;
            }

            //logger.error(throwable);

            if (sum == 0) {
                rollback();
            } else {
                commit();
            }

            // RuntimeException will popup the default error popup view of ecore
            throw throwable;

        } catch (Exception e) {
            throw new SCEFApplicationException(e);
        } finally {
            endTransaction();
        }
    }

    private Object invokeNoTransactional(Method method, Object[] args) throws Throwable {
        try {
            Object returnValue = method.invoke(target, args);
            return returnValue;
        } catch (InvocationTargetException e) {
            Throwable throwable = e.getTargetException();
            throw throwable;
        }
    }

    public Object getProxyInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    private void startTransaction(boolean readOnly, int isolation) throws DBException {
        if (hasInTransaction.get() == null || hasInTransaction.get() == 0) {
            startTransactionReal(readOnly, isolation);
            hasInTransaction.set(1);
        } else {
            hasInTransaction.set(hasInTransaction.get() + 1);
        }
    }

    private void startTransactionReal(boolean readOnly, int isolation) {
        SqlSessionManager sqlSessionManager = getOdsSqlSessionManager();
        sqlSessionManager.startManagedSession(false);
    }

    private SqlSessionManager getOdsSqlSessionManager() {
        if(sqlSessionManager == null) {
            synchronized (OdsTransactionProxyFactory.class) {
                if(sqlSessionManager == null) {
                    OdsTransactionMapper odsTransactionMapper = (OdsTransactionMapper)getBeanFromFactoryGuice(OdsTransactionMapper.class);
                    InvocationHandler lvInvHandler0 = Proxy.getInvocationHandler(odsTransactionMapper);
                    ManagedMapperProvider managedMapperProvider0 = (ManagedMapperProvider) lvInvHandler0;
                    sqlSessionManager = managedMapperProvider0.getSqlSessionManager();
                }
            }
        }
        return sqlSessionManager;
    }

    private void commit() {
        if (hasInTransaction.get() == 1) {
            getOdsSqlSessionManager().commit();
        }
    }

    private void rollback() {
        if (hasInTransaction.get() == 1) {
            getOdsSqlSessionManager().rollback();
        }
    }

    private void endTransaction() {
        if (hasInTransaction.get() != null && hasInTransaction.get() > 0) {
            hasInTransaction.set(hasInTransaction.get() - 1);
            if (hasInTransaction.get() == 0) {
                endTransactionReal();
                hasInTransaction.remove();
            }
        }
    }

    private void endTransactionReal() {
        getOdsSqlSessionManager().close();
    }
}

 

在自己的实践中:

    private SqlSessionManager getOdsSqlSessionManager() {
        if(sqlSessionManager == null) {
            synchronized (OdsTransactionProxyFactory.class) {
                if(sqlSessionManager == null) {
                    try {

                        MyMapper1 mapper1 = (MyMapper1) getBeanFromFactoryGuice(MyMapper1.class);
                        InvocationHandler lvInvHandler = Proxy.getInvocationHandler(mapper1);
                        MapperProxy lvT = (MapperProxy) lvInvHandler;
                        Field lvSessField = lvT.getClass().getDeclaredField("sqlSession");
                        lvSessField.setAccessible(true);
                        sqlSessionManager = (SqlSessionManager) lvSessField.get(lvT);

                    }catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            }
        }
        return sqlSessionManager;
    }

 借助:mybatis 动态代理 事务 初探中的方法,由mapper获取sqlSessionManager

 

 

类加载的并发,单例模式,静态资源加载  中使用静态内部类加载sqlSessionManager,此部分不进入test git仓库,这里看看就好

 

 

 

5

2020.10.29 运行期修改数据源

DefaultSqlSessionFactory.openSessionFromConnection 调用(本部分代码不入自己的代码库)

this.bindTransactionFactoryType(ScefJdbcTransactionFactory.class);
public class ScefJdbcTransactionFactory implements TransactionFactory {

private DataSourceDictionary dataSourceDictionary;
public void setProperties(Properties props) {
}

public Transaction newTransaction(Connection conn) {
return new JdbcTransaction(conn);
}

public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
DataSourceDictionary dictionary = this.getDataSourceDictionary();
if (dictionary == null) {
throw new RuntimeException("Can not get DataSourceDictionary form Injector");
}
DataSource datasource = dictionary.getDataSource("xxx");

return new JdbcTransaction(datasource, level, autoCommit);
}

无视掉传入的初始DataSource(由provider提供),换成运行期的,即可
((ComboPooledDataSource)((LazyDataSource)dataSource).getDelegateDataSourceForOps()).softResetAllUsers();

 

 

 

6

2022.7.25 多数据源,与mybatis-guice一致

transaction继承备案2 多数据源

 

做了实践:

 

    @project_DB_TRANSACTIONAL(noRollbackFor = {projectApplicationEmailSendingException.class})
    public projectRequestDto saveCommunication(projectRequestDto dto, projectRequestCommunicationDto requestComm, projectUserDto loggedInUser) throws projectApplicationException, projectOptimisticLockCheckedException {
        requestComm.setReconFlag(projectConstants.FLAG_N);
        tranSecondSourceMainMethodWithAnno();
                                  数据源2已提交 if(configuration.getInt("project.xxx") == 3)   不会回滚 throw new RuntimeException(); return save(dto, requestComm, loggedInUser); } @project_DB_TRANSACTIONAL(mapper = MatrixTransactionMapper.class) protected void tranSecondSourceMainMethodWithAnno() { CdsIndexNettingMap cdsIndexNettingMap = cdsIndexNettingMapMatrixMapper.queryForObject(11667L); tranSecondSourceSubMethodWithoutAnno(cdsIndexNettingMap);                               数据源2未提交 if(configuration.getInt("project.xxx") == 2)    会回滚 throw new RuntimeException(); } protected void tranSecondSourceSubMethodWithoutAnno(CdsIndexNettingMap cdsIndexNettingMap) { cdsIndexNettingMap.setTableId(cdsIndexNettingMap.getTableId() + " MM"); cdsIndexNettingMapMatrixMapper.update(cdsIndexNettingMap);                               数据源2未提交 if(configuration.getInt("project.xxx") == 1)    会回滚 throw new RuntimeException(); }

  

 

与mybatis-guice相比:

1 mybatis-guice service与sqlSessionManager绑定恒定,所以service必须注册在private db module中,单个数据源

而本文方式可以允许service两个方法使用不同的数据源事务,所以本文方式可能存在性能问题,因为每次切方法都要重新获取该方法所对应的sqlSessionManager

2 threadlocal N对象是static的,所以无法处理多数据源,方案有

1)把facotry 变成一个数据源单例,像mybatis-guice那样,threadlocal改为非静态,但是这样的话ioc就不能用第一次进来在执行一次的方法了

2)后来采用 threadlocal map,以sqlSessionManager为key➕双检锁(双检锁的另一种形式

 

posted on 2020-08-07 21:02  silyvin  阅读(552)  评论(0编辑  收藏  举报