溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Spring中如何整合Mybatis

發(fā)布時(shí)間:2021-06-18 16:42:21 來源:億速云 閱讀:172 作者:Leah 欄目:大數(shù)據(jù)

Spring中如何整合Mybatis,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎn)單易行的方法。

Spring整合Mybtais會(huì)進(jìn)行如下的配置

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!?。?這背后到底發(fā)生了什么?

還要從MapperScannerConfigurer 和SqlSessionFactoryBean 著手。

MapperScannerConfigurer

類注釋

  • beanDefinitionRegistryPostProcessor從 base package遞歸搜索接口,將它們注冊(cè)為MapperFactoryBean。注意接口必須包含至少一個(gè)方法,其實(shí)現(xiàn)類將被忽略。

  • 1.0.1以前是對(duì)BeanFactoryPostProcessor進(jìn)行擴(kuò)展,1.0.2以后是對(duì) BeanDefinitionRegistryPostProcessor進(jìn)行擴(kuò)展,具體原因請(qǐng)查閱https://jira.springsource.org/browse/SPR-8269

  • basePackage可以配置多個(gè),使用逗號(hào)或者分號(hào)分割。

  • 通過annotationClass或markerInterface,可以設(shè)置指定掃描的接口。默認(rèn)情況下這個(gè)2個(gè)屬性為空,basePackage下的所有接口將被掃描。

  • MapperScannerConfigurer為它創(chuàng)建的bean自動(dòng)注入SqlSessionFactory或SqlSessionTemplate如果存在多個(gè)SqlSessionFactory,需要設(shè)置sqlSessionFactoryBeanName或sqlSessionTemplateBeanName來指定具體注入的sqlSessionFactory或sqlSessionTemplate。

  • 不能傳入有占位符的對(duì)象(例如: 包含數(shù)據(jù)庫的用戶名和密碼占位符的對(duì)象)。可以使用beanName,將實(shí)際的對(duì)象創(chuàng)建推遲到所有占位符替換完成后。注意MapperScannerConfigurer支持它自己的屬性使用占位符,使用${property}這個(gè)種格式。

類圖找關(guān)鍵方法

Spring中如何整合Mybatis

從類圖上看MapperScannerConfigurer實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware接口。各個(gè)接口具體含義如下:

  • ApplicationContextAware:當(dāng)spring容器初始化后,會(huì)自動(dòng)注入ApplicationContext

  • BeanNameAware :設(shè)置當(dāng)前Bean在Spring中的名字

  • InitializingBean接口只包括afterPropertiesSet方法,在初始化bean的時(shí)候會(huì)執(zhí)行

  • BeanDefinitionRegistryPostProcessor: 對(duì)BeanFactoryPostProcessor的擴(kuò)展,允許在BeanFactoryPostProcessor執(zhí)行前注冊(cè)多個(gè)bean的定義。需要擴(kuò)展的方法為postProcessBeanDefinitionRegistry。

查詢,MapperScannerConfigurer的afterPropertiesSet方法如下,無具體擴(kuò)展信息。

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

結(jié)合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.設(shè)置過濾器
  scanner.registerFilters();
  //3.掃描java文件
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, 
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

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

processPropertyPlaceHolders處理占位符

之前說BeanDefinitionRegistryPostProcessor在BeanFactoryPostProcessor執(zhí)行前調(diào)用,

這就意味著Spring處理占位符的類PropertyResourceConfigurer還沒有執(zhí)行!

那MapperScannerConfigurer是如何支撐自己的屬性使用占位符的呢?這一切的答案都在

processPropertyPlaceHolders這個(gè)方法中。

private void processPropertyPlaceHolders() {
  Map<String, PropertyResourceConfigurer> prcs =
       applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
  if (!prcs.isEmpty() && applicationContext 
                      instanceof GenericApplicationContext) {
    BeanDefinition mapperScannerBean = 
            ((GenericApplicationContext) applicationContext)
                        .getBeanFactory().getBeanDefinition(beanName);
    // PropertyResourceConfigurer 沒有暴露方法直接替換占位符,
    // 創(chuàng)建一個(gè) BeanFactory包含MapperScannerConfigurer
    // 然后執(zhí)行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,可以總結(jié) MapperScannerConfigurer支持它自己的屬性使用占位符的方式

  1. 找到所有已經(jīng)注冊(cè)的PropertyResourceConfigurer類型的Bean

  2. 使用new DefaultListableBeanFactory()來模擬Spring環(huán)境,將MapperScannerConfigurer注冊(cè)到這個(gè)BeanFactory中,執(zhí)行BeanFactory的后處理,來替換占位符。

ClassPathMapperScanner的registerFilters方法

MapperScannerConfigurer的類注釋中有一條:

通過annotationClass或markerInterface,可以設(shè)置指定掃描的接口,默認(rèn)情況下這個(gè)2個(gè)屬性為空,basePackage下的所有接口將被掃描。 scanner.registerFilters(),就是對(duì)annotationClass和markerInterface的設(shè)置。

public void registerFilters() {
  boolean acceptAllInterfaces = true;

  // 如果指定了annotationClass,
  if (this.annotationClass != null) {
    addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
    acceptAllInterfaces = false;
  }
  // 重寫AssignableTypeFilter以忽略實(shí)際標(biāo)記接口上的匹配項(xiàng)
  if (this.markerInterface != null) {
    addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
      @Override
      protected boolean matchClassName(String className) {
        return false;
      }
    });
    acceptAllInterfaces = false;
  }

  if (acceptAllInterfaces) {
    // 默認(rèn)處理所有接口
    addIncludeFilter(new TypeFilter() {
      @Override
      public boolean match(
      MetadataReader metadataReader, 
      MetadataReaderFactory metadataReaderFactory) throws IOException {
        return true;
      }
    });
  }

  // 不包含以package-info結(jié)尾的java文件
  // package-info.java包級(jí)文檔和包級(jí)別注釋
  addExcludeFilter(new TypeFilter() {
    @Override
    public boolean match(MetadataReader metadataReader, 
    MetadataReaderFactory metadataReaderFactory) throws IOException {
      String className = metadataReader.getClassMetadata().getClassName();
      return className.endsWith("package-info");
    }
  });
}

雖然設(shè)置了過濾器,如何在掃描中起作用就要看scanner.scan方法了。

ClassPathMapperScanner的scan方法

public int scan(String... basePackages) {
   int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
   doScan(basePackages);
   // 注冊(cè)注解配置處理器
   if (this.includeAnnotationConfig) {
      AnnotationConfigUtils
                  .registerAnnotationConfigProcessors(this.registry);
   }
   return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

doScan方法如下:

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

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

掃描包下的所有java文件轉(zhuǎn)換為BeanDefinition(實(shí)際是ScannedGenericBeanDefinition)。

processBeanDefinitions就是將之前的BeanDefinition轉(zhuǎn)換為MapperFactoryBean的BeanDefinition。

至于過濾器如何生效(即annotationClass或markerInterface)呢?我一路追蹤源碼

Spring中如何整合Mybatis

終于在ClassPathScanningCandidateComponentProvider的isCandidateComponent找到了對(duì)過濾器的處理

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

總結(jié)MapperScannerConfigurer的作用

MapperScannerConfigurer實(shí)現(xiàn)了beanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法

從指定的 basePackage的目錄遞歸搜索接口,將它們注冊(cè)為MapperFactoryBean

SqlSessionFactoryBean

類注釋

  1. 創(chuàng)建Mybatis的SqiSessionFactory,用于Spring上下文中進(jìn)行共享。

  2. SqiSessionFactory可以通過依賴注入到與mybatis的daos中。

  3. datasourcetransactionmanager,jtatransactionmanager與sqlsessionfactory想結(jié)合實(shí)現(xiàn)事務(wù)。

類圖找關(guān)鍵方法

Spring中如何整合Mybatis

SqlSessionFactoryBean實(shí)現(xiàn)了ApplicationListener ,InitializingBean,F(xiàn)actoryBean接口,各個(gè)接口的說明如下:

  • ApplicationListener 用于監(jiān)聽Spring的事件

  • InitializingBean接口只包括afterPropertiesSet方法,在初始化bean的時(shí)候會(huì)執(zhí)行

  • FactoryBean:返回的對(duì)象不是指定類的一個(gè)實(shí)例,其返回的是該FactoryBean的getObject方法所返回的對(duì)象

應(yīng)該重點(diǎn)關(guān)注afterPropertiesSet和getObject的方法。

關(guān)鍵方法分析

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看方法名稱就知道在這里進(jìn)行了SqlSessionFactory的創(chuàng)建,具體源碼不在贅述。

getObject方法

public SqlSessionFactory getObject() throws Exception {
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }
  return this.sqlSessionFactory;
}

總結(jié)SqlSessionFactoryBean

實(shí)現(xiàn)了InitializingBean的afterPropertiesSet,在其中創(chuàng)建了Mybatis的SqlSessionFactory

實(shí)現(xiàn)了FactoryBean的getObject 返回創(chuàng)建好的sqlSessionFactory。

疑問

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

ApplicationContext context=new AnnotationConfigApplicationContext();
UsrMapper  usrMapper=context.getBean("usrMapper");
實(shí)際上調(diào)用的是
sqlSession.getMapper(UsrMapper.class);

SqlSessionFactoryBean創(chuàng)建了Mybatis的SqlSessionFactory。MapperScannerConfigurer將接口轉(zhuǎn)換為了MapperFactoryBean。那又哪里調(diào)用的sqlSession.getMapper(UsrMapper.class)呢???

MapperFactoryBean是這一切的答案(MapperFactoryBean:注意看我的名字---Mapper的工廠!?。?/p>

MapperFactoryBean說明

類注釋

能夠注入MyBatis映射接口的BeanFactory。它可以設(shè)置SqlSessionFactory或預(yù)配置的SqlSessionTemplate。
注意這個(gè)工廠僅僅注入接口不注入實(shí)現(xiàn)類

類圖找關(guān)鍵方法

Spring中如何整合Mybatis

看類圖,又看到了InitializingBean和FactoryBean?。?!

  • InitializingBean接口只包括afterPropertiesSet方法,在初始化bean的時(shí)候會(huì)執(zhí)行

  • FactoryBean:返回的對(duì)象不是指定類的一個(gè)實(shí)例,其返回的是該FactoryBean的getObject方法所返回的對(duì)象

再次重點(diǎn)關(guān)注afterPropertiesSet和getObject的實(shí)現(xiàn)!

關(guān)鍵方法分析

DaoSupport類中有afterPropertiesSet的實(shí)現(xiàn)如下:

public final void afterPropertiesSet()
     throws IllegalArgumentException, BeanInitializationException {
    this.checkDaoConfig();
    try {
        this.initDao();
    } catch (Exception var2) {
        throw
         new BeanInitializationException(
                             "Initialization of DAO failed",  var2);
    }
}

initDao是個(gè)空實(shí)現(xiàn),checkDaoConfig在MapperFactoryBean中有實(shí)現(xiàn)如下:

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

關(guān)鍵的語句是configuration.addMapper(this.mapperInterface),將接口添加到Mybatis的配置中。

getObject方法超級(jí)簡(jiǎn)單,就是調(diào)用了sqlSession.getMapper(UsrMapper.class);

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

總結(jié)MapperFactoryBean

實(shí)現(xiàn)了InitializingBean的afterPropertiesSet方法,在其中將mapper接口設(shè)置到mybatis的配置中。

實(shí)現(xiàn)了FactoryBean的getObject 方法,調(diào)用了sqlSession.getMapper,返回mapper對(duì)象。

總結(jié)

Spring整合Mybatis核心3類:

MapperScannerConfigurer

實(shí)現(xiàn)了beanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,在其中從指定的 basePackage的目錄遞歸搜索接口,將它們注冊(cè)為MapperFactoryBean類型的BeanDefinition

SqlSessionFactoryBean

實(shí)現(xiàn)了InitializingBean的afterPropertiesSet,在其中創(chuàng)建了Mybatis的SqlSessionFactory。

實(shí)現(xiàn)了FactoryBean的getObject 返回創(chuàng)建好的sqlSessionFactory。

MapperFactoryBean

實(shí)現(xiàn)了InitializingBean的afterPropertiesSet方法,將mapper接口設(shè)置到mybatis的配置中。

實(shí)現(xiàn)了FactoryBean的getObject 方法,調(diào)用了sqlSession.getMapper,返回mapper對(duì)象。

關(guān)于Spring中如何整合Mybatis問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI