美文网首页
mybatis源码解析八(spring处理sqlsession线

mybatis源码解析八(spring处理sqlsession线

作者: 为梦想前进 | 来源:发表于2020-04-07 15:34 被阅读0次

    上一期,分析了下关于mybatis的处理sqlsession线程安全的问题,主要是通过sqlSessionManager代理类增强的形式,通过每次创建一个新的DefautSqlsession或者将当前线程放入到Threadlocal中实现的,那么我们在使用mybatis的时候,一般不可能单独使用mybatis的,一般都是和sprig框架配合使用,现在都是面向spring编程了,所以,本次我们一起分析下spring是怎样保证sqlSession线程安全的,
    我们先通过一个案例,看看结果,一般在引入spring后,我们都会通过SqlSessionTemplete操作mybatis,当扫描到@Mapper的注解后,在执行业务逻辑代码,执行数据库交互的时候,这个时候,就会被SqlSessionTemplete拦截了,所以,不管是我们显示的调用SqlSessionTemplete执行相关的操作,还是在引入spring框架后,,默认的执行,都是通过SqlSessionTempete拦截保证sqlSqssion线程安全的,那马接下来,我们就一起分析下,看看spring到底是怎样支持sqlSession线程安全的
    我们先通过一个案例来说明问题,下面是一个特备简单的案例,我们通过他,先来分析下,然后在通过多线程来分析具体的线程安全问题

    @Test
        public void test5(){
            final RyxAccount ryxAccountByPrimaryKey = ryxAccountService.getRyxAccountByPrimaryKey(1);
            System.out.println(ryxAccountByPrimaryKey);
            final RyxAccount ryxAccountByPrimaryKey1 = ryxAccountService.getRyxAccountByPrimaryKey(1);
            System.out.println(ryxAccountByPrimaryKey1);
    
        }
    

    当我们执行 ryxAccountService.getRyxAccountByPrimaryKey的接口方法的时候,到mapper接口,SqlSqlSessionTemplete通过代理的方式拦截mapper接口,生成代理类,获取sqlSession,执行后续的数据库crud

    public class SqlSessionTemplate implements SqlSession, DisposableBean {
    
        private final SqlSessionFactory sqlSessionFactory;
    
        private final ExecutorType executorType;
    
        private final SqlSession sqlSessionProxy;
    
        private final PersistenceExceptionTranslator exceptionTranslator;
    
             省略........
    
        /**
         * Constructs a Spring managed {@code SqlSession} with the given
         * {@code SqlSessionFactory} and {@code ExecutorType}.
         * A custom {@code SQLExceptionTranslator} can be provided as an
         * argument so any {@code PersistenceException} thrown by MyBatis
         * can be custom translated to a {@code RuntimeException}
         * The {@code SQLExceptionTranslator} can also be null and thus no
         * exception translation will be done and MyBatis exceptions will be
         * thrown
         *
         * @param sqlSessionFactory a factory of SqlSession
         * @param executorType an executor type on session
         * @param exceptionTranslator a translator of exception
         */
        public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                                  PersistenceExceptionTranslator exceptionTranslator) {
    
            notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
            notNull(executorType, "Property 'executorType' is required");
    
            this.sqlSessionFactory = sqlSessionFactory;
            this.executorType = executorType;
            this.exceptionTranslator = exceptionTranslator;
                    //jdk动态代理方法
            this.sqlSessionProxy = (SqlSession) newProxyInstance(
                                    //获取类加载器
                    SqlSessionFactory.class.getClassLoader(),
                                    //具体需要代理的类
                    new Class[] { SqlSession.class },
                                    //执行增强的方法
                    new SqlSessionInterceptor());
        }
    
        省略......
    
        /**
         * Proxy needed to route MyBatis method calls to the proper SqlSession got
         * from Spring's Transaction Manager
         * It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
         * pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
         */
        private class SqlSessionInterceptor implements InvocationHandler {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                SqlSession sqlSession = getSqlSession(
                        SqlSessionTemplate.this.sqlSessionFactory,
                        SqlSessionTemplate.this.executorType,
                        SqlSessionTemplate.this.exceptionTranslator);
                try {
                    Object result = method.invoke(sqlSession, args);
                    if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                        // force commit even on non-dirty sessions because some databases require
                        // a commit/rollback before calling close()
                        sqlSession.commit(true);
                    }
                    return result;
                } catch (Throwable t) {
                    Throwable unwrapped = unwrapThrowable(t);
                    if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                        // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
                        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                        sqlSession = null;
                        Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
                        if (translated != null) {
                            unwrapped = translated;
                        }
                    }
                    throw unwrapped;
                } finally {
                    if (sqlSession != null) {
                        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                    }
                }
            }
        }
    
    }
    

    看到这段代码是不是有似曾相识的感觉,没错,前一期在介绍SqlSesionManager的时候,也是这个写法,通过动态代理的技术增强目标方法,这里的作用就是在生成目标类之前,获取sqlSession(线程安全的),接下来我们吧重心放到SqlSessionInterceptor 的getSqlSession中去

    public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
            //参数校验
            notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
            notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
            
            /**
             * 1.1
             * 这个步骤都是指的是存在事务的情况下的结果,也就是说,在你的方法上开启了事务注解的时候,才有意义,当不开启事务注解的时候,
             * 会直接调用openSession返回session,也就是说,不存在事务的情况下,每一个线程都是相当于开启一个新的session,也就不会存在一级缓存的问题
             * 当然也就不会存在线程安全问题
             * 调用事务同步管理器方法获取SqlSession回话持有者,回去ThreadLocal中去取会话持有者对象.会话持有者对象
             * 放在ThreadLocal中.ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
             */
            SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    
            //会话持有者的数量执行+1操作,有人想获取持有者资源,将引用计数器加1
            SqlSession session = sessionHolder(executorType, holder);
            if (session != null) {
                return session;
            }
    
            //如果回话持有者中不存在SqlSession.则调用sessionFactory开启一个session
            LOGGER.debug(() -> "Creating a new SqlSession");
            session = sessionFactory.openSession(executorType);
    
            /**
             * 1.2
             * 注册到会话持有者中,如果存在事务,就会将当前的会话持有者和sessionFactory添加到thredLocal中
             */
            registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    
            return session;
        }
    

    这节我们的重点是分析SqlSession.所以事物我们暂且放下,看懂了,代码比较简单.我这里在总结下
    1:当我们引入spring框架后,我们执行查询,SqLSessionTemplete户通过代理方式"拦截"执行方法,由于DefaultSqSLession线程不安全
    2:这里获取线程安全的SqlSession,
    3:判断是否存在事务
    4:事务不存在,直接调用SqlSessionFactory获取sesison返回,直接执行后续的方法,也就是说,不通的线程访问,或者同一个线程范根同样的查询方法,都会去创建一个新的session,这样的话,也就导致以及缓存不存在了.
    如果存在事务,回去事务资源管理器获取session持有者,如果存在,就将当前的引用计数加1,如果不存在,则通过SqlSessionFactory.openSession获取一个session,然后注册到事务管理器中,这里是将当前资源添加到ThreadLOcal中,,在local中定义了一个map类似于这样的结构ThreadLocal<Map<String,Object>>,
    5:调用DefaultSqLSession执行后续的逻辑,所以也就是说,当存在事物的情况下,一级缓存才有意义

    我们通过案例来加强一下理解,以下案例,查询两次,默认回去走SqLSessionTemplete,我们执行下,看看结果

    @Test
        public void test5(){
            final RyxAccount ryxAccountByPrimaryKey = ryxAccountService.getRyxAccountByPrimaryKey(1);
            System.out.println(ryxAccountByPrimaryKey);
            final RyxAccount ryxAccountByPrimaryKey1 = ryxAccountService.getRyxAccountByPrimaryKey(1);
            System.out.println(ryxAccountByPrimaryKey1);
    
        }
    

    我们看到结果中查询两次都走了数据库查询,一级缓存没有起作用


    image.png

    再来看另一个案例,加了注解后,第二次查询回去走缓存

         */
        @Test
        @Transactional
        public void test5(){
            final RyxAccount ryxAccountByPrimaryKey = ryxAccountService.getRyxAccountByPrimaryKey(1);
            System.out.println(ryxAccountByPrimaryKey);
            final RyxAccount ryxAccountByPrimaryKey1 = ryxAccountService.getRyxAccountByPrimaryKey(1);
            System.out.println(ryxAccountByPrimaryKey1);
    
        }
    
    
    image.png

    那就有一个问题,为什么mybatis不适用线程安全额SqlSessionManager那,而是默认使用线程不安全的DefaultSqlSession那
    我觉得这个问题的答案是
    DefaultSqlSession已经开发完了,那马在他的基础上修改的话,势必要考虑的东西比较多,还不如直接通过代理的方式增强这个方法,调用底层的DefauleSqLSession,如果再来个框架,整合mybatis,又要修改这一块,导致越来越臃肿,通过代理的方式不修改原来代码的基础上,实现了该功能,其实是很值得我们借鉴的,做到了解耦操作.
    文中要是有不合理的地方,还请包涵指正,

    相关文章

      网友评论

          本文标题:mybatis源码解析八(spring处理sqlsession线

          本文链接:https://www.haomeiwen.com/subject/gcckphtx.html