Spring中如何整合Mybatis
Spring中如何整合Mybatis,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
成都创新互联公司是一家专业提供德令哈企业网站建设,专注与网站制作、网站建设、HTML5、小程序制作等业务。10年已为德令哈众多企业、政府机构等服务。创新互联专业网站设计公司优惠进行中。
Spring整合Mybtais会进行如下的配置
private static final String ONE_MAPPER_BASE_PACKAGE = "com.XXX.dao.mapper.one"; @Bean public MapperScannerConfigurer oneMapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage(ONE_MAPPER_BASE_PACKAGE); mapperScannerConfigurer. setSqlSessionFactoryBeanName("oneSqlSessionFactoryBean"); return mapperScannerConfigurer; } @Primary @Bean(name="oneSqlSessionFactoryBean") public SqlSessionFactoryBean oneSqlSessionFactoryBean( @Qualifier("oneDataSource") DruidDataSource oneDataSource) { return getSqlSessionFactoryBeanDruid(oneDataSource,ONE_MAPPER_XML); }
短短不到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}这个种格式。
类图找关键方法
从类图上看MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware接口。各个接口具体含义如下:
ApplicationContextAware:当spring容器初始化后,会自动注入ApplicationContext
BeanNameAware :设置当前Bean在Spring中的名字
InitializingBean接口只包括afterPropertiesSet方法,在初始化bean的时候会执行
BeanDefinitionRegistryPostProcessor: 对BeanFactoryPostProcessor的扩展,允许在BeanFactoryPostProcessor执行前注册多个bean的定义。需要扩展的方法为postProcessBeanDefinitionRegistry。
查询,MapperScannerConfigurer的afterPropertiesSet方法如下,无具体扩展信息。
@Override public void afterPropertiesSet() throws Exception { notNull(this.basePackage, "Property 'basePackage' is required"); }
结合MapperScannerConfigurer的注释与类图分析,确定其核心方法为:postProcessBeanDefinitionRegistry
postProcessBeanDefinitionRegistry分析
@Override public void postProcessBeanDefinitionRegistry( BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { //1. 占位符属性处理 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); //2.设置过滤器 scanner.registerFilters(); //3.扫描java文件 scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
从源码中看到除了processPropertyPlaceHolders外,其他工作都委托了ClassPathMapperScanner
processPropertyPlaceHolders处理占位符
之前说BeanDefinitionRegistryPostProcessor在BeanFactoryPostProcessor执行前调用,
这就意味着Spring处理占位符的类PropertyResourceConfigurer还没有执行!
那MapperScannerConfigurer是如何支撑自己的属性使用占位符的呢?这一切的答案都在
processPropertyPlaceHolders这个方法中。
private void processPropertyPlaceHolders() { Mapprcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class); if (!prcs.isEmpty() && applicationContext instanceof GenericApplicationContext) { BeanDefinition mapperScannerBean = ((GenericApplicationContext) applicationContext) .getBeanFactory().getBeanDefinition(beanName); // PropertyResourceConfigurer 没有暴露方法直接替换占位符, // 创建一个 BeanFactory包含MapperScannerConfigurer // 然后执行BeanFactory后处理即可 DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); factory.registerBeanDefinition(beanName, mapperScannerBean); for (PropertyResourceConfigurer prc : prcs.values()) { prc.postProcessBeanFactory(factory); } PropertyValues values = mapperScannerBean.getPropertyValues(); this.basePackage = updatePropertyValue("basePackage", values); this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values); this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values); } }
看完processPropertyPlaceHolders,可以总结 MapperScannerConfigurer支持它自己的属性使用占位符的方式
找到所有已经注册的PropertyResourceConfigurer类型的Bean
使用new DefaultListableBeanFactory()来模拟Spring环境,将MapperScannerConfigurer注册到这个BeanFactory中,执行BeanFactory的后处理,来替换占位符。
ClassPathMapperScanner的registerFilters方法
MapperScannerConfigurer的类注释中有一条:
通过annotationClass或markerInterface,可以设置指定扫描的接口,默认情况下这个2个属性为空,basePackage下的所有接口将被扫描。 scanner.registerFilters(),就是对annotationClass和markerInterface的设置。
public void registerFilters() { boolean acceptAllInterfaces = true; // 如果指定了annotationClass, if (this.annotationClass != null) { addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; } // 重写AssignableTypeFilter以忽略实际标记接口上的匹配项 if (this.markerInterface != null) { addIncludeFilter(new AssignableTypeFilter(this.markerInterface) { @Override protected boolean matchClassName(String className) { return false; } }); acceptAllInterfaces = false; } if (acceptAllInterfaces) { // 默认处理所有接口 addIncludeFilter(new TypeFilter() { @Override public boolean match( MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return true; } }); } // 不包含以package-info结尾的java文件 // package-info.java包级文档和包级别注释 addExcludeFilter(new TypeFilter() { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); } }); }
虽然设置了过滤器,如何在扫描中起作用就要看scanner.scan方法了。
ClassPathMapperScanner的scan方法
public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); doScan(basePackages); // 注册注解配置处理器 if (this.includeAnnotationConfig) { AnnotationConfigUtils .registerAnnotationConfigProcessors(this.registry); } return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart); }
doScan方法如下:
public SetdoScan(String... basePackages) { Set 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; }
位于ClassPathMapperScanner的父类ClassPathBeanDefinitionScanner的doScan方法,就是
扫描包下的所有java文件转换为BeanDefinition(实际是ScannedGenericBeanDefinition)。
processBeanDefinitions就是将之前的BeanDefinition转换为MapperFactoryBean的BeanDefinition。
至于过滤器如何生效(即annotationClass或markerInterface)呢?我一路追踪源码
终于在ClassPathScanningCandidateComponentProvider的isCandidateComponent找到了对过滤器的处理
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return isConditionMatch(metadataReader); } } return false; }
总结MapperScannerConfigurer的作用
MapperScannerConfigurer实现了beanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法
从指定的 basePackage的目录递归搜索接口,将它们注册为MapperFactoryBean
SqlSessionFactoryBean
类注释
创建Mybatis的SqiSessionFactory,用于Spring上下文中进行共享。
SqiSessionFactory可以通过依赖注入到与mybatis的daos中。
datasourcetransactionmanager,jtatransactionmanager与sqlsessionfactory想结合实现事务。
类图找关键方法
SqlSessionFactoryBean实现了ApplicationListener ,InitializingBean,FactoryBean接口,各个接口的说明如下:
ApplicationListener 用于监听Spring的事件
InitializingBean接口只包括afterPropertiesSet方法,在初始化bean的时候会执行
FactoryBean:返回的对象不是指定类的一个实例,其返回的是该FactoryBean的getObject方法所返回的对象
应该重点关注afterPropertiesSet和getObject的方法。
关键方法分析
afterPropertiesSet方法
public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); }
buildSqlSessionFactory看方法名称就知道在这里进行了SqlSessionFactory的创建,具体源码不在赘述。
getObject方法
public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; }
总结SqlSessionFactoryBean
实现了InitializingBean的afterPropertiesSet,在其中创建了Mybatis的SqlSessionFactory
实现了FactoryBean的getObject 返回创建好的sqlSessionFactory。
疑问
看完这SqlSessionFactoryBean和MapperScannerConfigurer之后,不知道你是否有疑问!一般在Spring中使用Mybatis的方式如下:
ApplicationContext context=new AnnotationConfigApplicationContext(); UsrMapper usrMapper=context.getBean("usrMapper"); 实际上调用的是 sqlSession.getMapper(UsrMapper.class);
SqlSessionFactoryBean创建了Mybatis的SqlSessionFactory。MapperScannerConfigurer将接口转换为了MapperFactoryBean。那又哪里调用的sqlSession.getMapper(UsrMapper.class)呢???
MapperFactoryBean是这一切的答案(MapperFactoryBean:注意看我的名字---Mapper的工厂!!)
MapperFactoryBean说明
类注释
能够注入MyBatis映射接口的BeanFactory。它可以设置SqlSessionFactory或预配置的SqlSessionTemplate。
注意这个工厂仅仅注入接口不注入实现类
类图找关键方法
看类图,又看到了InitializingBean和FactoryBean!!!
InitializingBean接口只包括afterPropertiesSet方法,在初始化bean的时候会执行
FactoryBean:返回的对象不是指定类的一个实例,其返回的是该FactoryBean的getObject方法所返回的对象
再次重点关注afterPropertiesSet和getObject的实现!
关键方法分析
DaoSupport类中有afterPropertiesSet的实现如下:
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { this.checkDaoConfig(); try { this.initDao(); } catch (Exception var2) { throw new BeanInitializationException( "Initialization of DAO failed", var2); } }
initDao是个空实现,checkDaoConfig在MapperFactoryBean中有实现如下:
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(); } } }
关键的语句是configuration.addMapper(this.mapperInterface),将接口添加到Mybatis的配置中。
getObject方法超级简单,就是调用了sqlSession.getMapper(UsrMapper.class);
public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); }
总结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对象。
关于Spring中如何整合Mybatis问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注创新互联行业资讯频道了解更多相关知识。
分享名称:Spring中如何整合Mybatis
路径分享:http://scyanting.com/article/poocej.html