千家信息网

Spring中如何整合Mybatis

发表于:2025-02-05 作者:千家信息网编辑
千家信息网最后更新 2025年02月05日,Spring中如何整合Mybatis,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。Spring整合Mybtais会进行如下的配置pr
千家信息网最后更新 2025年02月05日Spring中如何整合Mybatis

Spring中如何整合Mybatis,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

Spring整合Mybtais会进行如下的配置

private static final String ONE_MAPPER_BASE_PACKAGE = "com.XXX.dao.mapper.one";@Beanpublic 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分析

@Overridepublic 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() {  Map prcs =       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支持它自己的属性使用占位符的方式

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

  2. 使用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 Set doScan(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

类注释

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

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

  3. 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问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注行业资讯频道了解更多相关知识。

方法 接口 对象 处理 配置 关键 属性 注释 整合 分析 实际 就是 多个 文件 时候 源码 过滤器 问题 递归 搜索 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 sci数据库提供基本检索 大学毕业想学软件开发 小厂自己架构erp服务器 数据库恢复一般用多长时间 开展网络安全教育心得体会 长春博创网络技术有限公司 手机集成开发的软件开发 温州公司软件开发 网络安全与网络道德综合 除《网络安全法》第二十一条 计算机网络技术题库大学 软件开发过程中多种图形化工具 机房服务器问题讲解 关于网络安全小报的字体 六安市网络安全应急指挥中心在哪 数据库查询排名前几名 剑侠三 数据库 哪些是著名的学位论文数据库 慧族网络技术有限公司地址 上海交大网络安全硕士 周口软件开发操作 中国网络安全技术大会投稿 如何查看服务器有什么模块 软件开发服务专票开啥 皖事通app软件开发者是谁 南宁市网络安全技术大赛 数据库可靠性多少 全网服务器有卖吗 夏津软件开发自学课程哪个好 上海网络安全专家名单
0