Spring如何整合Mybatis,源码不难嘛!

Spring整合Mybtais会进行如下的配置(条条大路通罗马,方式不唯一)。

1private static final String ONE_MAPPER_BASE_PACKAGE = "com.XXX.dao.mapper.one"; 2@Bean 3public MapperScannerConfigurer oneMapperScannerConfigurer() { 4 MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); 5 mapperScannerConfigurer.setBasePackage(ONE_MAPPER_BASE_PACKAGE); 6 mapperScannerConfigurer. 7 setSqlSessionFactoryBeanName("oneSqlSessionFactoryBean"); 8 return mapperScannerConfigurer; 9} 10@Primary 11@Bean(name="oneSqlSessionFactoryBean") 12public SqlSessionFactoryBean oneSqlSessionFactoryBean( @Qualifier("oneDataSource") DruidDataSource oneDataSource) { 13 return getSqlSessionFactoryBeanDruid(oneDataSource,ONE_MAPPER_XML); 14}

短短不到20行代码,就完成了Spring整合Mybatis。

Amazing!!! 这背后到底发生了什么?

还要从MapperScannerConfigurer 和SqlSessionFactoryBean 着手。

MapperScannerConfigurer

类注释

  • beanDefinitionRegistryPostProcessor从 base package递归搜索接口,将它们注册为MapperFactoryBean。注意接口必须包含至少一个方法,其实现类将被忽略。

  • 1.0.1以前是对BeanFactoryPostProcessor进行扩展,1.0.2以后是对 BeanDefinitionRegistryPostProcessor进行扩展,具体原因请查阅https://jira.springsource.org/browse/SPR-8269

  • basePackage可以配置多个,使用逗号或者分号分割。

  • 通过annotationClass或markerInterface,可以设置指定扫描的接口。默认情况下这个2个属性为空,basePackage下的所有接口将被扫描。

  • MapperScannerConfigurer为它创建的bean自动注入SqlSessionFactory或SqlSessionTemplate如果存在多个SqlSessionFactory,需要设置sqlSessionFactoryBeanName或sqlSessionTemplateBeanName来指定具体注入的sqlSessionFactory或sqlSessionTemplate。

  • 不能传入有占位符的对象(例如: 包含数据库的用户名和密码占位符的对象)。可以使用beanName,将实际的对象创建推迟到所有占位符替换完成后。注意MapperScannerConfigurer支持它自己的属性使用占位符,使用${property}这个种格式。

类图找关键方法

MapperScanConfigurer

从类图上看MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware接口。各个接口具体含义如下:

  • ApplicationContextAware:当spring容器初始化后,会自动注入ApplicationContext
  • BeanNameAware :设置当前Bean在Spring中的名字
  • InitializingBean接口只包括afterPropertiesSet方法,在初始化bean的时候会执行
  • BeanDefinitionRegistryPostProcessor: 对BeanFactoryPostProcessor的扩展,允许在BeanFactoryPostProcessor执行前注册多个bean的定义。需要扩展的方法为postProcessBeanDefinitionRegistry。

查询,MapperScannerConfigurer的afterPropertiesSet方法如下,无具体扩展信息。

1@Override public void afterPropertiesSet() throws Exception { 2notNull(this.basePackage, "Property 'basePackage' is required"); 3}

结合MapperScannerConfigurer的注释与类图分析,确定其核心方法为:postProcessBeanDefinitionRegistry

postProcessBeanDefinitionRegistry分析

1@Override 2public void postProcessBeanDefinitionRegistry( 3 BeanDefinitionRegistry registry) { 4 if (this.processPropertyPlaceHolders) { 5 //1. 占位符属性处理 6 processPropertyPlaceHolders(); 7 } 8 9 ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); 10 scanner.setAddToConfig(this.addToConfig); 11 scanner.setAnnotationClass(this.annotationClass); 12 scanner.setMarkerInterface(this.markerInterface); 13 scanner.setSqlSessionFactory(this.sqlSessionFactory); 14 scanner.setSqlSessionTemplate(this.sqlSessionTemplate); 15 scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); 16 scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); 17 scanner.setResourceLoader(this.applicationContext); 18 scanner.setBeanNameGenerator(this.nameGenerator); 19 //2.设置过滤器 20 scanner.registerFilters(); 21 //3.扫描java文件 22 scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, 23 ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); 24}

从源码中看到除了processPropertyPlaceHolders外,其他工作都委托了ClassPathMapperScanner

processPropertyPlaceHolders处理占位符

之前说BeanDefinitionRegistryPostProcessor在BeanFactoryPostProcessor执行前调用,

这就意味着Spring处理占位符的类PropertyResourceConfigurer还没有执行!

那MapperScannerConfigurer是如何支撑自己的属性使用占位符的呢?这一切的答案都在

processPropertyPlaceHolders这个方法中。

1private void processPropertyPlaceHolders() { 2 Map<String, PropertyResourceConfigurer> prcs = 3 applicationContext.getBeansOfType(PropertyResourceConfigurer.class); 4 if (!prcs.isEmpty() && applicationContext 5 instanceof GenericApplicationContext) { 6 BeanDefinition mapperScannerBean = 7 ((GenericApplicationContext) applicationContext) 8 .getBeanFactory().getBeanDefinition(beanName); 9 // PropertyResourceConfigurer 没有暴露方法直接替换占位符, 10 // 创建一个 BeanFactory包含MapperScannerConfigurer 11 // 然后执行BeanFactory后处理即可 12 DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); 13 factory.registerBeanDefinition(beanName, mapperScannerBean); 14 15 for (PropertyResourceConfigurer prc : prcs.values()) { 16 prc.postProcessBeanFactory(factory); 17 } 18 PropertyValues values = mapperScannerBean.getPropertyValues(); 19 this.basePackage = updatePropertyValue("basePackage", values); 20 this.sqlSessionFactoryBeanName = 21 updatePropertyValue("sqlSessionFactoryBeanName", values); 22 this.sqlSessionTemplateBeanName = 23 updatePropertyValue("sqlSessionTemplateBeanName", values); 24 } 25}

看完processPropertyPlaceHolders,可以总结 MapperScannerConfigurer支持它自己的属性使用占位符的方式

  1. 找到所有已经注册的PropertyResourceConfigurer类型的Bean

  2. 使用new DefaultListableBeanFactory()来模拟Spring环境,将MapperScannerConfigurer注册到这个BeanFactory中,执行BeanFactory的后处理,来替换占位符。

ClassPathMapperScanner的registerFilters方法

MapperScannerConfigurer的类注释中有一条:

通过annotationClass或markerInterface,可以设置指定扫描的接口,默认情况下这个2个属性为空,basePackage下的所有接口将被扫描。 scanner.registerFilters(),就是对annotationClass和markerInterface的设置。

1public void registerFilters() { 2 boolean acceptAllInterfaces = true; 3 4 // 如果指定了annotationClass, 5 if (this.annotationClass != null) { 6 addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); 7 acceptAllInterfaces = false; 8 } 9 // 重写AssignableTypeFilter以忽略实际标记接口上的匹配项 10 if (this.markerInterface != null) { 11 addIncludeFilter(new AssignableTypeFilter(this.markerInterface) { 12 @Override 13 protected boolean matchClassName(String className) { 14 return false; 15 } 16 }); 17 acceptAllInterfaces = false; 18 } 19 20 if (acceptAllInterfaces) { 21 // 默认处理所有接口 22 addIncludeFilter(new TypeFilter() { 23 @Override 24 public boolean match( 25 MetadataReader metadataReader, 26 MetadataReaderFactory metadataReaderFactory) throws IOException { 27 return true; 28 } 29 }); 30 } 31 32 // 不包含以package-info结尾的java文件 33 // package-info.java包级文档和包级别注释 34 addExcludeFilter(new TypeFilter() { 35 @Override 36 public boolean match(MetadataReader metadataReader, 37 MetadataReaderFactory metadataReaderFactory) throws IOException { 38 String className = metadataReader.getClassMetadata().getClassName(); 39 return className.endsWith("package-info"); 40 } 41 }); 42}

虽然设置了过滤器,如何在扫描中起作用就要看scanner.scan方法了。

ClassPathMapperScanner的scan方法

1public int scan(String... basePackages) { 2 int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); 3 doScan(basePackages); 4 // 注册注解配置处理器 5 if (this.includeAnnotationConfig) { 6 AnnotationConfigUtils 7 .registerAnnotationConfigProcessors(this.registry); 8 } 9 return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); 10}

doScan方法如下:

1public Set<BeanDefinitionHolder> doScan(String... basePackages) { 2 Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); 3 if (beanDefinitions.isEmpty()) { 4 logger.warn("No MyBatis mapper was found in '" 5 + Arrays.toString(basePackages) 6 + "' package. Please check your configuration."); 7 } else { 8 processBeanDefinitions(beanDefinitions); 9 } 10 return beanDefinitions; 11}

位于ClassPathMapperScanner的父类ClassPathBeanDefinitionScanner的doScan方法,就是

扫描包下的所有java文件转换为BeanDefinition(实际是ScannedGenericBeanDefinition)。

processBeanDefinitions就是将之前的BeanDefinition转换为MapperFactoryBean的BeanDefinition。

至于过滤器如何生效(即annotationClass或markerInterface)呢?我一路追踪源码

终于在ClassPathScanningCandidateComponentProvider的isCandidateComponent找到了对过滤器的处理

1protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { 2 for (TypeFilter tf : this.excludeFilters) { 3 if (tf.match(metadataReader, this.metadataReaderFactory)) { 4 return false; 5 } 6 } 7 for (TypeFilter tf : this.includeFilters) { 8 if (tf.match(metadataReader, this.metadataReaderFactory)) { 9 return isConditionMatch(metadataReader); 10 } 11 } 12 return false; 13}

总结MapperScannerConfigurer的作用

MapperScannerConfigurer实现了beanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法

从指定的 basePackage的目录递归搜索接口,将它们注册为MapperFactoryBean

SqlSessionFactoryBean

类注释

  1. 创建Mybatis的SqiSessionFactory,用于Spring上下文中进行共享。

  2. SqiSessionFactory可以通过依赖注入到与mybatis的daos中。

  3. datasourcetransactionmanager,jtatransactionmanager与sqlsessionfactory想结合实现事务。

类图找关键方法

sqlSessionFactoryBean

SqlSessionFactoryBean实现了ApplicationListener ,InitializingBean,FactoryBean接口,各个接口的说明如下:

  • ApplicationListener 用于监听Spring的事件
  • InitializingBean接口只包括afterPropertiesSet方法,在初始化bean的时候会执行
  • FactoryBean:返回的对象不是指定类的一个实例,其返回的是该FactoryBean的getObject方法所返回的对象

应该重点关注afterPropertiesSet和getObject的方法。

关键方法分析

afterPropertiesSet方法

1public void afterPropertiesSet() throws Exception { 2 notNull(dataSource, "Property 'dataSource' is required"); 3 notNull(sqlSessionFactoryBuilder, 4 "Property 'sqlSessionFactoryBuilder' is required"); 5 state((configuration == null && configLocation == null) 6 || !(configuration != null && configLocation != null), 7 "Property 'configuration' and 'configLocation' can not specified with together"); 8 this.sqlSessionFactory = buildSqlSessionFactory(); 9}

buildSqlSessionFactory看方法名称就知道在这里进行了SqlSessionFactory的创建,具体源码不在赘述。

getObject方法

1public SqlSessionFactory getObject() throws Exception { 2 if (this.sqlSessionFactory == null) { 3 afterPropertiesSet(); 4 } 5 return this.sqlSessionFactory; 6}

总结SqlSessionFactoryBean

实现了InitializingBean的afterPropertiesSet,在其中创建了Mybatis的SqlSessionFactory

实现了FactoryBean的getObject 返回创建好的sqlSessionFactory。

疑问

看完这SqlSessionFactoryBean和MapperScannerConfigurer之后,不知道你是否有疑问!一般在Spring中使用Mybatis的方式如下:

1ApplicationContext context=new AnnotationConfigApplicationContext(); 2UsrMapper usrMapper=context.getBean("usrMapper"); 3实际上调用的是 4sqlSession.getMapper(UsrMapper.class);

SqlSessionFactoryBean创建了Mybatis的SqlSessionFactory。MapperScannerConfigurer将接口转换为了MapperFactoryBean。那又哪里调用的sqlSession.getMapper(UsrMapper.class)呢???

MapperFactoryBean是这一切的答案(MapperFactoryBean:注意看我的名字---Mapper的工厂!!)

MapperFactoryBean说明

类注释

能够注入MyBatis映射接口的BeanFactory。它可以设置SqlSessionFactory或预配置的SqlSessionTemplate。
注意这个工厂仅仅注入接口不注入实现类

类图找关键方法

MapperFactoryBean

看类图,又看到了InitializingBean和FactoryBean!!!

  • InitializingBean接口只包括afterPropertiesSet方法,在初始化bean的时候会执行
  • FactoryBean:返回的对象不是指定类的一个实例,其返回的是该FactoryBean的getObject方法所返回的对象

再次重点关注afterPropertiesSet和getObject的实现!

关键方法分析

DaoSupport类中有afterPropertiesSet的实现如下:

1public final void afterPropertiesSet() 2 throws IllegalArgumentException, BeanInitializationException { 3 this.checkDaoConfig(); 4 try { 5 this.initDao(); 6 } catch (Exception var2) { 7 throw 8 new BeanInitializationException( 9 "Initialization of DAO failed", var2); 10 } 11}

initDao是个空实现,checkDaoConfig在MapperFactoryBean中有实现如下:

1protected void checkDaoConfig() { 2 super.checkDaoConfig(); 3 4 notNull(this.mapperInterface, "Property 'mapperInterface' is required"); 5 6 Configuration configuration = getSqlSession().getConfiguration(); 7 if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { 8 try { 9 configuration.addMapper(this.mapperInterface); 10 } catch (Exception e) { 11 logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e); 12 throw new IllegalArgumentException(e); 13 } finally { 14 ErrorContext.instance().reset(); 15 } 16 } 17}

关键的语句是configuration.addMapper(this.mapperInterface),将接口添加到Mybatis的配置中。

getObject方法超级简单,就是调用了sqlSession.getMapper(UsrMapper.class);

1public T getObject() throws Exception { 2return getSqlSession().getMapper(this.mapperInterface); 3}

总结MapperFactoryBean

实现了InitializingBean的afterPropertiesSet方法,在其中将mapper接口设置到mybatis的配置中。

实现了FactoryBean的getObject 方法,调用了sqlSession.getMapper,返回mapper对象。

总结

Spring整合Mybatis核心3类:

MapperScannerConfigurer

实现了beanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,在其中从指定的 basePackage的目录递归搜索接口,将它们注册为MapperFactoryBean类型的BeanDefinition

SqlSessionFactoryBean

实现了InitializingBean的afterPropertiesSet,在其中创建了Mybatis的SqlSessionFactory。

实现了FactoryBean的getObject 返回创建好的sqlSessionFactory。

MapperFactoryBean

实现了InitializingBean的afterPropertiesSet方法,将mapper接口设置到mybatis的配置中。

实现了FactoryBean的getObject 方法,调用了sqlSession.getMapper,返回mapper对象。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )