Mybatis-Spring源码分析(三):SqlSessionTemplate基于动态代理实现线程安全

版权声明:非商业目的可自由转载,转载请标明出处 https://blog.csdn.net/u010013573/article/details/88091186

概述

  • 在mybatis中定义了SqlSession接口用于封装一个数据库的连接,通过该数据库连接来对数据库发起相关数据库操作请求并获取结果集。SqlSession接口在mybatis中的默认实现为DefaultSqlSession,其中DefaultSqlSession不是线程安全的,即多个需要访问数据库的线程不能共享同一个DefaultSqlSession的对象实例,否则会出现线程之间的数据相互影响。DefaultSqlSession的类定义如下:在内部实际的操作是通过Executor执行器来完成的。

    public class DefaultSqlSession implements SqlSession {
      // 全局配置引用,内部维护了mapper接口的方法和mapper.xml中SQL的包装类MappedStatement的映射集合,
      // 所以主要从这个集合,获取指定mapper接口的方法对应的SQL的包装类MappedStatement
      private final Configuration configuration;
    
      // 每个sqlSession包含一个executor
      // sqlSession不是线程安全的,故每个线程要有自己独立的sqlSession,
      // 在sqlSessionFactory创建该sqlSession时,根据是否开启了二级缓存来决定是否使用CachingExecutor来包装executor,
      // 如果开启了二级缓存,则该executor的类型为CachingExecutor
      private final Executor executor;
    
      private final boolean autoCommit;
      private boolean dirty;
      private List<Cursor<?>> cursorList;
      
      ...
      
    }
    
  • 在mybatis-spring中,由于spring的IOC容器管理的相关组件对象基本是单例线程安全的,如Service,Controller组件等,故为了与spring的设计保持一致,在spring整合mybatis的时候,对于SqlSession接口提供了一个SqlSessionTemplate的模板实现类用于实现SqlSession的功能。与mybaits的DefaultSqlSession不同的是,SqlSessionTemplate是单例、线程安全的,即其对象实例被所有应用线程共享,在spring容器启动时,将SqlSessionTemplate的一个对象实例作为bean注册到spring容器中,从而可以注入到其他bean中,具体为mapper接口在mybatis-spring中对应的包装类MapperFactoryBean,MapperFactoryBean在创建并注册mapper接口在mybatis中对应的MapperProxy对象bean到spring容器时,会将该DefaultSqlSession的对象bean引用传给该MapperProxy对象bean,从而之后该MapperProxy对象bean可以通过该SqlSessionTemplate对象bean来执行数据库操作。

类设计与实现:静态代理和动态代理的使用

静态代理的使用

  • SqlSessionTemplate在内部实现中也是委派给mybatis的DefaultSqlSession来执行数据库操作的,SqlSessionTemplate不是自身重新实现了一套mybatis数据库访问的逻辑。

  • SqlSessionTemplate通过静态代理机制来提供SqlSession接口的行为,即实现SqlSession接口来获取SqlSession的所有方法;SqlSessionTemplate的定义如下:标准的静态代理实现模式,即实现SqlSession接口并在内部包含一个SqlSession接口实现类引用sqlSessionProxy。

    // 实现SqlSession接口,单例、线程安全,使用spring的事务管理器的sqlSession,
    // 具体的SqlSession的功能,则是通过内部包含的sqlSessionProxy来来实现,这也是静态代理的一种实现。
    // 同时内部的sqlSessionProxy实现InvocationHandler接口,则是动态代理的一种实现,而线程安全也是在这里实现的。
    
    // 注意mybatis默认的sqlSession不是线程安全的,需要每个线程有一个单例的对象实例。
    // SqlSession的主要作用是提供SQL操作的API,执行指定的SQL语句,mapper需要依赖SqlSession来执行其方法对应的SQL。
    public class SqlSessionTemplate implements SqlSession, DisposableBean {
    
      private final SqlSessionFactory sqlSessionFactory;
    
      private final ExecutorType executorType;
    
      // 一个代理类,由于SqlSessionTemplate为单例的,被所有mapper,所有线程共享,
      // 所以sqlSessionProxy要保证这些mapper中方法调用的线程安全特性:
      // sqlSessionProxy的实现方式主要为实现了InvocationHandler接口实现了动态代理,
      // 由动态代理的知识可知,InvocationHandler的invoke方法会拦截所有mapper的所有方法调用,
      // 故这里的实现方式是在invoke方法内部创建一个sqlSession局部变量,从而实现了每个mapper的每个方法调用都使用
      // 不同的sqlSession,从而实现相互不影响,实现了线程安全特性,具体看SqlSessionInterceptor
      private final SqlSession sqlSessionProxy;
    
      private final PersistenceExceptionTranslator exceptionTranslator;
      
      ...
      
    }
    

动态代理的使用

  • 通过动态代理机制,即实现了JDK的InvocationHandler接口来动态拦截SqlSession接口所有方法的执行。具体为在SqlSessionTemplate实现中,不是SqlSessionTemplate自身直接实现InvocationHandler接口,而是在内部定义了一个私有内部类SqlSessionInterceptor来实现InvocationHandler接口,然后在SqlSessionTemplate内部包含SqlSessionInterceptor的一个对象属性,在SqlSessionTemplate的所有方法中,通过该SqlSessionInterceptor对象属性来执行相关操作。

  • SqlSessionTemplate的构造函数如下:创建了一个SqlSessionInterceptor对象实例并赋值给sqlSessionProxy引用。

    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;
    
        // 创建动态代理对象,核心实现为SqlSessionInterceptor
        this.sqlSessionProxy = (SqlSession) newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[] { SqlSession.class },
            new SqlSessionInterceptor());
    }
    
  • SqlSessionInterceptor动态代理的实现:在invoke方法中拦截SqlSession的每个方法调用,最关键的实现是对于每次调用都是创建一个DefaultSqlSession局部对象来完成实际的数据库操作的,故每个线程虽然共享了一个SqlSessionTemplate对象,但是在实际方法执行时,也是通过一个独立的局部DefaultSqlSession对象来执行操作的,从而实现了线程安全。

    /**
    * 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来执行proxy的method对应的SQL,
          // 每次调用都获取创建一个sqlSession线程局部变量,故不同线程相互不影响,在这里实现了SqlSessionTemplate的线程安全性
          SqlSession sqlSession = getSqlSession(
              SqlSessionTemplate.this.sqlSessionFactory,
              SqlSessionTemplate.this.executorType,
              SqlSessionTemplate.this.exceptionTranslator);
          try {
            Object result = method.invoke(sqlSession, args);
    
            // 如果当前操作没有在一个Spring事务中,则手动commit一下
            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 {
    
            // 关闭sqlSession
            if (sqlSession != null) {
              closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
          }
        }
    }
    

猜你喜欢

转载自blog.csdn.net/u010013573/article/details/88091186