Spring整合Mybatis的源码解读——@MapperScan注解与SqlSessionTemplate的线程安全实现

回顾一下Mybatis的Mapper接口实例的生成与执行流程

众所周知,我们基于MyBatis 操作数据库,实际上就是通过 SqlSession 获取一个 JDBC 连接调用api来操作。
1、SqlSession接口。SqlSession 接口有常用的实现类有:DefaultSqlSession(线程不安全)、SqlSessionTemplate (spring中线程安全类,接下来的主角)

/*
 * The default implementation for {@link SqlSession}.
 * Note that this class is not Thread-Safe.
 *
 * @author Clinton Begin
 */
public class DefaultSqlSession implements SqlSession {}

2、我们通过sqlsession获取mapper接口实例的基本过程如下:
Spring整合Mybatis的源码解读——@MapperScan注解与SqlSessionTemplate的线程安全实现_第1张图片
通过SqlSession的getMapper(type)方法获取的Mapper接口实例,这个实例是通过MapperProxyFactory生成的代理实例MapperProxy同时实现了invocationhandler接口。

3、MapperProxy的方法执行,在它的invoke方法中调用SqlSession的具体api来执行的。

public class MapperProxy<T> implements InvocationHandler, Serializable {
//通过传入的sqlsession进行方法调用
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

这个过程用到JDK的动态代理设计模式。

今天的问题:

问题1、spring整合mybatis后是如何把Mapper代理对象注入ioc容器的呢?

  • 基于注解@MapperScan和@Mapper,前者是spring的注解,后者是mybatis的注解
  • 基于Xml配置文件

今天我们讨论的是基于@MapperScan注解的方式!

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

我们通过@MapperScan(“com.xxx.xxx”)扫描package把Mapper接口生成实例并注入IOC容器。扫描注册的类就是通过MapperScannerRegistrar.class实现的!

  • MapperScannerRegistrar.class
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  /**
 - {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {
//获取MapperScannerConfigurer.class的BeanDefinitionBuilder
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);

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

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      builder.addPropertyValue("markerInterface", markerInterface);
    }

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

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }

    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
      builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }

    String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    if (StringUtils.hasText(sqlSessionFactoryRef)) {
      builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
    }

    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
        .collect(Collectors.toList()));

    if (basePackages.isEmpty()) {
      basePackages.add(getDefaultBasePackage(annoMeta));
    }

    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    if (StringUtils.hasText(lazyInitialization)) {
      builder.addPropertyValue("lazyInitialization", lazyInitialization);
    }

    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
//注册MapperScannerConfigurer.class的BeanDefinition。
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }

1、我们知道:Spring容器初始化时,从资源中读取到bean的相关定义后,保存在BeanDefinitionMap,在实例化bean的操作就是依据这些bean的定义来做的,而在实例化之前,Spring允许我们通过自定义扩展来改变bean的定义,定义一旦变了,后面的实例也就变了。
2、而MapperScannerRegistrar注册beandefinition其实是MapperScannerConfigurer.class!

  • MapperScannerConfigurer.class!
public class MapperScannerConfigurer
   implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
 private String basePackage;

 private boolean addToConfig = true;

 private String lazyInitialization;

 private SqlSessionFactory sqlSessionFactory;

 private SqlSessionTemplate sqlSessionTemplate;

 private String sqlSessionFactoryBeanName;

 private String sqlSessionTemplateBeanName;

 private Class<? extends Annotation> annotationClass;

 private Class<?> markerInterface;

 private Class<? extends MapperFactoryBean> mapperFactoryBeanClass;

 private ApplicationContext applicationContext;

 private String beanName;

 private boolean processPropertyPlaceHolders;

 private BeanNameGenerator nameGenerator;

   @Override
 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
   if (this.processPropertyPlaceHolders) {
     processPropertyPlaceHolders();
   }

   ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
   scanner.setAddToConfig(this.addToConfig);
   scanner.setAnnotationClass(this.annotationClass);
   scanner.setMarkerInterface(this.markerInterface);
   scanner.setSqlSessionFactory(this.sqlSessionFactory);
   scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
   scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
   scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
   scanner.setResourceLoader(this.applicationContext);
   scanner.setBeanNameGenerator(this.nameGenerator);
   scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
   if (StringUtils.hasText(lazyInitialization)) {
     scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
   }
   scanner.registerFilters();
   scanner.scan(
       StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
 }
 }

在postProcessBeanDefinitionRegistry的方法中通过ClassPathMapperScanner的实例scanner调用了scan()方法。

public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

		doScan(basePackages);

		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}

scan()方法调用了ClassPathMapperScanner的doscan方法。

  • ClassPathMapperScanner
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
//我们省了中间的属性和setget方式,直接看doscan方法

 /**
  * Calls the parent search that will search and register all the candidates. Then the registered objects are post
  * processed to set them as MapperFactoryBeans
  */
 @Override
 public Set<BeanDefinitionHolder> doScan(String... basePackages) {
   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 {
     processBeanDefinitions(beanDefinitions);
   }

   return beanDefinitions;
 }

 private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
   GenericBeanDefinition definition;
   for (BeanDefinitionHolder holder : beanDefinitions) {
     definition = (GenericBeanDefinition) holder.getBeanDefinition();
     String beanClassName = definition.getBeanClassName();
     LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
         + "' mapperInterface");

     // the mapper interface is the original class of the bean
     // but, the actual class of the bean is MapperFactoryBean
     definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
     definition.setBeanClass(this.mapperFactoryBeanClass);

     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.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) {
       LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
       definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
     }
     definition.setLazyInit(lazyInitialization);
   }
 }

doscan方法调用父类ClassPathBeanDefinitionScanner的doscan方法。

  • ClassPathBeanDefinitionScanner.class
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   	Assert.notEmpty(basePackages, "At least one base package must be specified");
   	Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   	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 definitionHolder = new BeanDefinitionHolder(candidate, beanName);
   				definitionHolder =
   						AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
   				beanDefinitions.add(definitionHolder);
   				registerBeanDefinition(definitionHolder, this.registry);
   			}
   		}
   	}
   	return beanDefinitions;
   }
  • 父类的doscan方法作用:获取basepackage下面的所有mapper的全类名生成注入beandefinition并注入ioc中并返回了。
  • 子类classPathMapperScanner的doscan方法后调用了processBeanDefinitions(Set beanDefinitions)方法,该方法把遍历父类生成的beandefinitions重新进行修改,把beanclass设置为mapperFactoryBeanclass,构造参数设置成beanClassname即mapper的类名。
  • processBeanDefinitions方法的作用就是所有的mapper实例都是通过mapperfactorybean的getobject方法来生成并注入ioc容器的。

至此mapper代理对象就注入了IOC容器中!

问题2、spring是如何管理SqlSession保证线程安全的呢?

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

重点在于SqlSessionTemplate中的SqlSessionProxy,所有的方法调用作用都是调用SqlSessionProxy,而它只是个代理类,具体方法执行是通过SqlSessionInterceptor的invoke方法。
SqlSessionInterceptor

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

1、SqlSessionInterceptor是SqlSessionTemplate的内部类,并实现了invocationhandler接口。
2、从mybatis的mapper生成流程我们知道,mapperproxy对象最终调用sqlsession执行方法,而到sqlsessiontemplate中就是SqlSessionInterceptor来具体执行了。
3、sqlsession通过sqlsessionutils的getsqlsession方法

  • sqlsessionutils

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

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

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

    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

1、该方法先获得了一个SqlSessionHolder,这个hodler其实内部持有Sqlsession,重点在于SqlSessionHolder的获得。在后面的调用关系比较长,我们省略说结论:这个holder是从threadlocal取出的!
2、Threadlocal中存放着一个对象:一个map,通过下面的方法获得holder。
3、threadLocal对象的作用就再赘述了,保证每个线程有一个自己的变量副本,所以不存在资源争抢和线程安全问题。

SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

4、继续往下,如果通过holder获取的session不为null,就返回,不然创建一个新的sqlsession并调用registrySessionHolder尝试把session放在holder并放在threadlocal中。

  • registrySessionHolder
  private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      Environment environment = sessionFactory.getConfiguration().getEnvironment();

      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
        LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");

        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager
            .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        holder.requested();
      } else {
        if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
          LOGGER.debug(() -> "SqlSession [" + session
              + "] was not registered for synchronization because DataSource is not transactional");
        } else {
          throw new TransientDataAccessResourceException(
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
        }
      }
    } else {
      LOGGER.debug(() -> "SqlSession [" + session
          + "] was not registered for synchronization because synchronization is not active");
    }

  }

1、registrySessionHolder先判断是否有事务,如果有事务就放入,不然就不放入!
2、所有每个线程中的同一个事务会使用同一个sqlsession经事务管理,而非事务的每次操作都会新建一个sqlsession。

继续会到SqlSessionInterceptor,在返回结果后,最后在finally中执行sqlsession.close方法。

 public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
    notNull(session, NO_SQL_SESSION_SPECIFIED);
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    if ((holder != null) && (holder.getSqlSession() == session)) {
      LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
      holder.released();
    } else {
      LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
      session.close();
    }
  }

1、该方法会判断是否在事务中,在事务中,只是把holder中的计数器-1,并没有关闭执行Session.close方法,所以在该事务中后续方法同一个SqlSession。
2、如果不在事务中,直接执行SqlSession.close()。

总结一句话:通过threadlocal保证sql的线程安全!有事务就把sqlsession放入threadlocal通过计数器减少引用,没有事务,所有操作都要创建一个新的sqlsession。

你可能感兴趣的:(Spring整合Mybatis的源码解读——@MapperScan注解与SqlSessionTemplate的线程安全实现)