【Mybatis分析】MyBatis与Spring的集成过程
简单写一篇文章分析下Myabtis是如何整合到Spring容器中的以及我们编写的Mapper又是如何运行的。
从@MapperScan注解开始
使用MapperScan注解指定mapper的位置。
1 2 3 4 5 |
@Slf4j @SpringBootApplication @MapperScan(value = {"com.example.mybatisstartertest"}) public class MybatisStarterTestApplication implements CommandLineRunner { } |
我们看下MapperScan注解的作用。
1 2 3 4 5 6 7 |
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan } |
从上面我们可以看到一个关键信息,使用@Import导入了MapperScannerRegistrar这个类。
MapperScaneerRegistrar是ImportBeanDefinitionRegistrar的子类。
使用@Import修饰ImportBeanDefinitionRegistrar的子类,会被Spring在启动中载入到容器,并会在初始化阶段调用registerBeanDefinitions方法,这个对于第三方框架来说是一个不错的扩展点。
MyBatis可以将自定义的BeanDefinition导入到Spring 管理。这个自定义的BeanDefinition就是开发者编写的Mapper。
我们看看这个MapperScannerRegistrar具体做了些什么工作。
MapperScannerRegistrar
首先实现下registerBeanDefinitions
,用于监听Spring容器初始化。
1 2 3 4 5 6 7 8 9 10 |
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 解析MapperScan的所有属性 AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0)); } } |
之后调用到registerBeanDefinitions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { // 创建一个MapperScannerConfigurer的BeanDefinitionBuilder // 这个MapperScannerConfigurer的职责是扫描指定包下的Mapper BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("processPropertyPlaceHolders", true); // 解析MapperScan注解中定义的属性,设置给Builder... 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)); } //... builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); // 注册到容器中 registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); } |
这个过程结束后,容器中会多了一个MapperScannerConfigurer的BeanDefinition。
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor
接口,在注册完BeanDefinition后会自动回调postProcessBeanDefinitionRegistry
。
我们看下注册后的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } // 创建ClassPathMapperScanner 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)); } if (StringUtils.hasText(defaultScope)) { scanner.setDefaultScope(defaultScope); } scanner.registerFilters(); // 扫描basePackage scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); } |
看到代码就可以猜到,扫描basePackage的工作是由ClassPathMapperScanner
类进行完成的。
在doScan方法中,先调用super获取到包下面所有扫描到的BeanDefinitionHolder。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { // ClassPathBeanDefinitionScanner提供的扫描功能 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; } |
在Mybatis的自定义实现中,会将BeanDefinition的class设置为MapperBeanFactory,然后注册到容器中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { AbstractBeanDefinition definition; BeanDefinitionRegistry registry = getRegistry(); // 处理所有扫描到的bean for (BeanDefinitionHolder holder : beanDefinitions) { definition = (AbstractBeanDefinition) holder.getBeanDefinition(); boolean scopedProxy = false; // 重要:设置MapperFactoryBean的构造器参数 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 // 重要:设置BeanClass为MapperFacatoryBean类型 definition.setBeanClass(this.mapperFactoryBeanClass); definition.getPropertyValues().add("addToConfig", this.addToConfig); // Attribute for MockitoPostProcessor // https://github.com/mybatis/spring-boot-starter/issues/475 definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName); //把sqlSessionFactory的一些参数也设置进去 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; } //.... } } |
我们看到,所有扫描到的BeanDefinition的BeanClass都被设置成了MapperFacatoryBean,这一步至关重要,当初始化bean时,会调用到BeanFactory的getObject方法来初始化对象。
MapperFactoryBean
我们看下MapperFactoryBean的getObject方法
1 2 3 4 |
@Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } |
这里getSqlSession返回的是SqlSessionTemplate
实例,SqlSessionTemplate也是一个单例,由spring-boot-starter-mybatis进行配置。
MapperFactoryBean在构造完成后会调用checkDaoConfig将MapperClass设置进MyBatis的Configuration,这样之后就可以调动getMapper获取mapper使用了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@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 { // 添加当前mapper 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事务
下面我们介绍下Mybatis是如何将事务整合仅Spring的事务体系的。
我们直接使用的Mapper是MyBatis通过Configuration创建的动态代理MapperProxy。
MapperProxy中出来CRUD的关键代码是cacheInvoker
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { try { return MapUtil.computeIfAbsent(methodCache, method, m -> { if (m.isDefault()) { try { if (privateLookupInMethod == null) { return new DefaultMethodInvoker(getMethodHandleJava8(method)); } else { return new DefaultMethodInvoker(getMethodHandleJava9(method)); } } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } } else { return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } }); } catch (RuntimeException re) { Throwable cause = re.getCause(); throw cause == null ? re : cause; } } |
Mapper的这些方法最终还是会转移到SqlSession中进行执行,而Mybatis在Spring中的SqlSession是SqlSessionTemplate。我们只需要分析SqlSessionTemplate的CRUD方法即可看出对Spring的相关整合。
我们看下SqlSessionTemplate是如何构造的
1 2 3 4 5 6 7 8 9 10 11 12 |
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()); } |
其中最关键的元素是sqlSessionProxy,Mapper相关的操作是由sqlSessionProxy间接完成的。
1 2 |
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); |
sqlSessionProxy
我们可以看出,SqlSessionProxy其实是一个动态代理,真正的实现是SqlSessionInterceptor。
相当于每次调用sqlSession的CRUD方法,都是直接调用到这里面来的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 重要:调用openSession获取一个sqlSession SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { // 直接调用 Object result = method.invoke(sqlSession, args); // 如果是在事务中,则调用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 { // 调用完成后,关闭session if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } } |
这一部分逻辑我们梳理下,分为几个步骤:
- getSqlSession获取sqlSession,这一部分又分为是否在事务中两处逻辑
- 在事务中, 直接返回事务中的sqlSession
- 不在事务中,调用opepSession重新获取一个
- 使用这个获取到的sqlSession执行对应的CRUD方法
- 在事务中的话,执行完调用commit
- 关闭sqlSession
Mybatis与Spring的整合大概就是这样,后面会分析Mybatis中的其他设计。
总结
最后总结下上面的过程。
我们通过@MapperScan注解导入MyBatis的ClassPathMapperScanner。Scanner扫描指定basePackage路径下的所有BeanDefinition,并将BeanDefinition的class设置为MapperFactoryBean。
MapperFactory是构造Mapper的工厂,构造Mapper会通过SqlSessionTemplate进行。
SqlSessionTemplate将获取session的过程与spring的事务整合,使一个事务内的操作均由一个SqlSession完成。
Mybatis与Spring的整合大概就是这样,后面会分析Mybatis中的其他设计。