Mybatis源码分析——@MapperScan将Mapper接口生成代理注入到Spring

前言

上一篇文章我们讲了SqlSessionFactoryBean,通过这个FactoryBean创建SqlSessionFactory并注册进Spring容器,这篇文章我们就讲剩下的部分,通过MapperScannerConfigurer将Mapper接口生成代理注入到Spring。

扫描Mapper接口

我们上一篇文章介绍了扫描Mapper接口有两种方式,一种是通过bean.xml注册MapperScannerConfigurer对象,一种是通过@MapperScan("com.yibo.mapper")注解的方式,如下
方式一:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.chenhao.mapper" />
</bean>

方式二:

@Configuration
@MapperScan("com.yibo.mapper")
public class AppConfig {

@MapperScan

我们来看看@MapperScan这个注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {

MapperScan使用@Import将MapperScannerRegistrar导入。

MapperScannerRegistrar

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;
  
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 获取MapperScan 注解,如@MapperScan("com.yibo.mapper")
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    // 创建路径扫描器,下面的一大段都是将MapperScan 中的属性设置到ClassPathMapperScanner ,做的就是一个set操作
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
      // 设置资源加载器,作用:扫描指定包下的class文件。
      scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    //设置SqlSessionFactory的名称
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    //获取配置的包路径,如com.yibo.mapper
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    // 注册过滤器,作用:什么类型的Mapper将会留下来。
    scanner.registerFilters();
    // 扫描指定包
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

}

ClassPathMapperScanner

接着我们来看看扫描过程scanner.doScan(StringUtils.toStringArray(basePackages))

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        //调用父类进行扫描,并将basePackages下的class都封装成BeanDefinitionHolder,并注册进Spring容器的BeanDefinition
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

        if (beanDefinitions.isEmpty()) {
            logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
            //继续对beanDefinitions做处理,额外设置一些属性
            processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }
}

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {

    private final BeanDefinitionRegistry registry;

    private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();

    private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();

    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        //遍历basePackages进行扫描
        for (String basePackage : basePackages) {
            //找出匹配的类
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                if (checkCandidate(beanName, candidate)) {
                    //封装成BeanDefinitionHolder 对象
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    //将BeanDefinition对象注入spring的BeanDefinitionMap中,后续getBean时,就是从BeanDefinitionMap获取对应的BeanDefinition对象,取出其属性进行实例化Bean
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }
}

我们重点调用父类的doScan方法,获取basePackages下的所有Class,并将其生成BeanDefinition,注入spring的BeanDefinitionMap中,也就是Class的描述类,在调用getBean方法时,获取BeanDefinition进行实例化。此时,所有的Mapper接口已经被生成了BeanDefinition。

接着我们看下processBeanDefinitions(beanDefinitions),对生成的BeanDefinition做一些额外的处理。

processBeanDefinitions

上面BeanDefinition已经注入进Spring容器了,接着我们看对BeanDefinition进行哪些额外的处理

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
          + "' and '" + definition.getBeanClassName() + "' mapperInterface");
      }

      // 设置definition构造器的输入参数为definition.getBeanClassName(),这里就是com.yibo.mapper.UserMapper
      // 那么在getBean实例化时,通过反射调用构造器实例化时要将这个参数传进去
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      
      // 修改definition对应的Class
      // 看过Spring源码的都知道,getBean()返回的就是BeanDefinitionHolder中beanClass属性对应的实例
      // 所以我们后面ac.getBean(UserMapper.class)的返回值也就是MapperFactoryBean的实例
      // 但是最终被注入到Spring容器的对象的并不是MapperFactoryBean的实例,根据名字看,我们就知道MapperFactoryBean实现了FactoryBean接口
      // 那么最终注入Spring容器的必定是从MapperFactoryBean的实例的getObject()方法中返回
      definition.setBeanClass(this.mapperFactoryBean.getClass());

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        //往definition设置属性值sqlSessionFactory,那么在MapperFactoryBean实例化后,进行属性赋值时populateBean(beanName, mbd, instanceWrapper);,会通过反射调用sqlSessionFactory的set方法进行赋值
        //也就是在MapperFactoryBean创建实例后,要调用setSqlSessionFactory方法将sqlSessionFactory注入进MapperFactoryBean实例
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }
}

definition.setBeanClass(this.mapperFactoryBean.getClass()),将definition的beanClass属性设置为MapperFactoryBean.class,我们知道,在getBean的时候,会通过反射创建Bean的实例,也就是创建beanClass的实例,如下Spring的getBean的部分代码:

public class SimpleInstantiationStrategy implements InstantiationStrategy {

    @Override
    public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
            final Constructor<?> ctor, Object... args) {
        // 没有覆盖
        // 直接使用反射实例化即可
        if (!bd.hasMethodOverrides()) {
            if (System.getSecurityManager() != null) {
                // use own privileged to change accessibility (when security is on)
                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                    ReflectionUtils.makeAccessible(ctor);
                    return null;
                });
            }
            // 通过BeanUtils直接使用构造器对象实例化bean
            return BeanUtils.instantiateClass(ctor, args);
        }
        else {
            // 生成CGLIB创建的子类对象
            return instantiateWithMethodInjection(bd, beanName, owner, ctor, args);
        }
    }
}

public abstract class ReflectionUtils {

    public static void makeAccessible(Constructor<?> ctor) {
        //如果没有权限
        if ((!Modifier.isPublic(ctor.getModifiers()) ||
                !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) {
            //进行暴力破解
            ctor.setAccessible(true);
        }
    }
}

看到没,是通过bd.getBeanClass();从BeanDefinition中获取beanClass属性,然后通过反射实例化Bean,如上,所有的Mapper接口扫描封装成的BeanDefinition的beanClass都设置成了MapperFactoryBean,我们知道在Spring初始化的最后,会获取所有的BeanDefinition,并通过getBean创建所有的实例注入进Spring容器,那么意思就是说,在getBean时,创建的的所有Mapper对象是MapperFactoryBean这个对象了。

我们看下definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()),设置了BeanDefinition构造器参数,那么当getBean中通过构造器创建实例时,需要将设置的构造器参数definition.getBeanClassName(),这里就是com.yibo.mapper.UserMapper传进去。

还有一个点要注意,在definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory)处,往BeanDefinition的PropertyValues设置了sqlSessionFactory,那么在创建完MapperFactoryBean的实例后,会对MapperFactoryBean进行属性赋值,也就是Spring创建Bean的这句代码,populateBean(beanName, mbd, instanceWrapper);,这里会通过反射调用MapperFactoryBean的setSqlSessionFactory方法将sqlSessionFactory注入进MapperFactoryBean实例,所以我们接下来重点看的就是MapperFactoryBean这个对象了。

MapperFactoryBean

接下来我们看最重要的一个类MapperFactoryBean

//继承SqlSessionDaoSupport、实现FactoryBean,那么最终注入Spring容器的对象要从getObject()中取
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    //intentionally empty 
  }
  
  //构造器,我们上一节中在BeanDefinition中已经设置了构造器输入参数
  //所以在通过反射调用构造器实例化时,会获取在BeanDefinition设置的构造器输入参数
  //也就是对应得每个Mapper接口Class
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

  //最终注入Spring容器的就是这里的返回对象
  @Override
  public T getObject() throws Exception {
    //获取父类setSqlSessionFactory方法中创建的SqlSessionTemplate
    //通过SqlSessionTemplate获取mapperInterface的代理类
    //我们例子中就是通过SqlSessionTemplate获取com.chenhao.mapper.UserMapper的代理类
    //获取到Mapper接口的代理类后,就把这个Mapper的代理类对象注入Spring容器  
    return getSqlSession().getMapper(this.mapperInterface);
  }  
  
  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }
  
  @Override
  public boolean isSingleton() {
    return true;
  }  
  
  public void setMapperInterface(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }

  public boolean isAddToConfig() {
    return addToConfig;
  }
}

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;

  //还记得上一节中我们往BeanDefinition中设置的sqlSessionFactory这个属性吗?
  //在实例化MapperFactoryBean后,进行属性赋值时,就会通过反射调用setSqlSessionFactory
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      //创建一个SqlSessionTemplate并赋值给sqlSession
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }

  public SqlSession getSqlSession() {
    return this.sqlSession;
  }

  @Override
  protected void checkDaoConfig() {
    notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }

}  

我们看到MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean,那么getBean获取的对象是从其getObject()中获取,并且MapperFactoryBean是一个单例,那么其中的属性SqlSessionTemplate对象也是一个单例,全局唯一,供所有的Mapper代理类使用。

这里我大概讲一下getBean时,这个类的过程:

  • 1、MapperFactoryBean通过反射调用构造器实例化出一个对象,并且通过上一节中definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName())设置的构造器参数,在构造器实例化时,传入Mapper接口的Class,并设置为MapperFactoryBean的mapperInterface属性。

  • 2、进行属性赋值,通过上一节中definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);设置的属性值,在populateBean属性赋值过程中通过反射调用setSqlSessionFactory方法,并创建SqlSessionTemplate对象设置到sqlSession属性中。

  • 3、由于MapperFactoryBean实现了FactoryBean,最终注册进Spring容器的对象是从getObject()方法中取,接着获取SqlSessionTemplate这个SqlSession调用getMapper(this.mapperInterface);生成Mapper接口的代理对象,将Mapper接口的代理对象注册进Spring容器

至此,所有com.yibo.mapper中的Mapper接口都生成了代理类,并注入到Spring容器了。接着我们就可以在Service中直接从Spring的BeanFactory中获取了,如下

public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper userMapper;
    
}  

SqlSessionTemplate

还记得我们前面分析Mybatis源码时,获取的SqlSession实例是什么吗?我们简单回顾一下

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;
  
  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

    /**
     * ExecutorType 指定Executor的类型,分为三种:SIMPLE, REUSE, BATCH,默认使用的是SIMPLE
     * TransactionIsolationLevel 指定事务隔离级别,使用null,则表示使用数据库默认的事务隔离界别
     * autoCommit 是否自动提交,传过来的参数为false,表示不自动提交
     */  
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 获取配置中的环境信息,包括了数据源信息、事务等
      final Environment environment = configuration.getEnvironment();
      // 创建事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 创建事务,配置事务属性
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 创建Executor,即执行器
      // 它是真正用来Java和数据库交互操作的类,后面会展开说。     
      final Executor executor = configuration.newExecutor(tx, execType);
      // 创建DefaultSqlSession对象返回,其实现了SqlSession接口
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }  
}

大家应该还有印象,就是上面的DefaultSqlSession,那上一节的SqlSessionTemplate是什么鬼???我们来看看

// 实现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的每个方法调用都使用
  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;
  
  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
  }

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
    this(sqlSessionFactory, executorType,
        new MyBatisExceptionTranslator(
            sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
  }

  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;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

  public SqlSessionFactory getSqlSessionFactory() {
    return this.sqlSessionFactory;
  }

  public ExecutorType getExecutorType() {
    return this.executorType;
  }

  public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
    return this.exceptionTranslator;
  }

  @Override
  public <T> T selectOne(String statement) {
    //由真实的对象sqlSessionProxy执行查询
    return this.sqlSessionProxy.<T> selectOne(statement);
  }

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    //由真实的对象sqlSessionProxy执行查询
    return this.sqlSessionProxy.<T> selectOne(statement, parameter);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
    //由真实的对象sqlSessionProxy执行查询
    return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
    //由真实的对象sqlSessionProxy执行查询
    return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
    //由真实的对象sqlSessionProxy执行查询
    return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey, rowBounds);
  }

  @Override
  public <T> Cursor<T> selectCursor(String statement) {
    return this.sqlSessionProxy.selectCursor(statement);
  }

  @Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter) {
    return this.sqlSessionProxy.selectCursor(statement, parameter);
  }

  @Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
    return this.sqlSessionProxy.selectCursor(statement, parameter, rowBounds);
  }

  @Override
  public <E> List<E> selectList(String statement) {
    return this.sqlSessionProxy.<E> selectList(statement);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.sqlSessionProxy.<E> selectList(statement, parameter);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);
  }

  @Override
  public void select(String statement, ResultHandler handler) {
    this.sqlSessionProxy.select(statement, handler);
  }

  @Override
  public void select(String statement, Object parameter, ResultHandler handler) {
    this.sqlSessionProxy.select(statement, parameter, handler);
  }

  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
  }

  @Override
  public int insert(String statement) {
    return this.sqlSessionProxy.insert(statement);
  }

  @Override
  public int insert(String statement, Object parameter) {
    return this.sqlSessionProxy.insert(statement, parameter);
  }

  @Override
  public int update(String statement) {
    return this.sqlSessionProxy.update(statement);
  }

  @Override
  public int update(String statement, Object parameter) {
    return this.sqlSessionProxy.update(statement, parameter);
  }

  @Override
  public int delete(String statement) {
    return this.sqlSessionProxy.delete(statement);
  }

  @Override
  public int delete(String statement, Object parameter) {
    return this.sqlSessionProxy.delete(statement, parameter);
  }

  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

  @Override
  public void commit() {
    throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
  }

  @Override
  public void commit(boolean force) {
    throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
  }

  @Override
  public void rollback() {
    throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
  }

  @Override
  public void rollback(boolean force) {
    throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
  }

  @Override
  public void close() {
    throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
  }

  @Override
  public void clearCache() {
    this.sqlSessionProxy.clearCache();
  }

  @Override
  public Configuration getConfiguration() {
    return this.sqlSessionFactory.getConfiguration();
  }

  @Override
  public Connection getConnection() {
    return this.sqlSessionProxy.getConnection();
  }

  @Override
  public List<BatchResult> flushStatements() {
    return this.sqlSessionProxy.flushStatements();
  }

  @Override
  public void destroy() throws Exception {
  //This method forces spring disposer to avoid call of SqlSessionTemplate.close() which gives UnsupportedOperationException
  }

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

}  

我们看到SqlSessionTemplate实现了SqlSession接口,那么Mapper代理类中执行所有的数据库操作,都是通过SqlSessionTemplate来执行,如上我们看到所有的数据库操作都由对象sqlSessionProxy执行查询。

静态代理的使用

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

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

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;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
}

动态代理的使用

不是吧,又使用了动态代理??真够曲折的,那我们接着看 new SqlSessionTemplate.SqlSessionInterceptor() 这个InvocationHandler

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  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 {
        //直接通过新创建的SqlSession反射调用method
        //这也就解释了为什么不需要目标类属性了,这里每次都会创建一个
        Object result = method.invoke(sqlSession, args);
        
        // 如果当前操作没有在一个Spring事务中,则手动commit一下
        // 如果当前业务没有使用@Transation,那么每次执行了Mapper接口的方法直接commit
        // 还记得我们前面讲的Mybatis的一级缓存吗,这里一级缓存不能起作用了,因为每执行一个Mapper的方法,sqlSession都提交了
        // sqlSession提交,会清空一级缓存
        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);
        }
      }
    }
  }
}

这里大概讲一下Mapper代理类调用方法执行逻辑:

  • 1、SqlSessionTemplate生成Mapper代理类时,将本身传进去做为Mapper代理类的属性,调用Mapper代理类的方法时,最后会通过SqlSession类执行,也就是调用SqlSessionTemplate中的方法。

  • 2、SqlSessionTemplate中操作数据库的方法中又交给了sqlSessionProxy这个代理类去执行,那么每次执行的方法都会回调其SqlSessionInterceptor这个InvocationHandler的invoke方法

  • 3、在invoke方法中,为每个线程创建一个新的SqlSession,并通过反射调用SqlSession的method。这里sqlSession是一个线程局部变量,不同线程相互不影响,实现了SqlSessionTemplate的线程安全性

  • 4、如果当前操作并没有在Spring事务中,那么每次执行一个方法,都会提交,相当于数据库的事务自动提交,Mysql的一级缓存也将不可用

接下来我们还要看一个地方,invoke中是如何创建SqlSession的?

public final class SqlSessionUtils {

  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    //通过TransactionSynchronizationManager内部的ThreadLocal中获取
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Creating a new SqlSession");
    }
    //这里我们知道实际上是创建了一个DefaultSqlSession
    session = sessionFactory.openSession(executorType);
    //将创建的SqlSession对象放入TransactionSynchronizationManager内部的ThreadLocal中
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }
}

通过sessionFactory.openSession(executorType)实际创建的SqlSession还是DefaultSqlSession。如果读过我前面Spring源码的朋友,肯定知道TransactionSynchronizationManager这个类,其内部维护了一个ThreadLocal的Map,这里同一线程创建了SqlSession后放入ThreadLocal中,同一线程中其他Mapper接口调用方法时,将会直接从ThreadLocal中获取。

参考:
https://www.cnblogs.com/java-chen-hao/p/11839958.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341

推荐阅读更多精彩内容