從Springboot集成Mybatis看Mybatis到底是怎么集成到Spring中的

疑惑

我們知道Mybatis單獨使用時,需要如下步驟:

@Autowired
    public SqlSessionFactory sqlSessionFactory;
    @Test
    public void testMybatis() {
        //步驟1 拿到數(shù)據(jù)庫連接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //步驟2 獲取接口的代理股缸,也就是將sqlSession與接口包裝成一個proxy對象
        BikeMapper mapper = sqlSession.getMapper(BikeMapper.class);
        //步驟3 通過代理調用指定方法验游,并返回結果
        Bike bike = mapper.selectByPrimaryKey(1L);
        System.out.println(bike);
    }

注釋寫的很清楚了,就不贅述了泄隔。
但是從springboot集成Mybatis時可以非常簡單配置一些參數(shù)就可以了拒贱,而且使用時直接調用接口方法就可以完成上面的步驟。好奇的我就想知道到底spring做了哪些工作佛嬉,使得我們不用獲取sqlSession并且不用從sqlSession獲取接口代理逻澳,也就是上面的1和2步。

揭秘

springboot集成Mybatis時暖呕,只需要引入對應的starter即可

<dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis-spring.version}</version>
            </dependency>

而使用JavaConfig配置集成Mybatis數(shù)據(jù)源時斜做,可以使用一個注解來完成對Mybatis功能接口的檢索

@Configuration
@MapperScan(basePackages = {"com.demo.test.mapper.master"}, sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataBaseConfig {
    
    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.master")
    public DataSource dataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "masterTransactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "masterSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/master/*.xml"));
        factoryBean.setConfigLocation(new ClassPathResource("mybatis.xml"));
        factoryBean.setTypeAliasesPackage("com.demo.test.po");
        return factoryBean.getObject();
    }
}

通過代碼來看,通過向spring容器注入sqlSessionFactory的Bean湾揽,讓容器管理這個Mybatis中重要的類瓤逼。除此之外還有個注解@MapperScan,這個注解指定了檢索的目錄和SqlSessionFactory類名库物,這里就是答案所在霸旗。

@MapperScan

這個注解除了上面提到的兩個參數(shù)之外還包括了一個重要的參數(shù)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
  String[] basePackages() default {};
  String sqlSessionFactoryRef() default "";
  //mapper的工廠bean
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}

這里沒有列舉出所有參數(shù),而factoryBean參數(shù)的默認MapperFactoryBean.class是我們關注的重點戚揭,下面還會提到诱告。

檢索過程

@MapperScan通過spring的@Import標簽引入了實際檢索接口的類MapperScannerRegistrar。@Import是Spring自動裝配的基礎毫目,提供了向spring容器注入Bean的功能蔬啡。但是mybatis提供的MapperScannerRegistrar類不只是將檢索到的接口注入到spring容器這么簡單诲侮,接著往下看。

源碼很長箱蟆,我這里只給出關鍵位置沟绪。

  • MapperScannerRegistrar.registerBeanDefinitions:在@Import被解析時會調用這個方法。里面創(chuàng)建一個檢索對象ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);并對scanner進行參數(shù)賦值空猜,最后調用scanner.doScan方法绽慈,我們跟到這里
  • ClassPathMapperScanner.doScan:方法一共兩步,第一步是檢索給定目錄文件夾中類并轉成BeanDefinition辈毯,第二步是對BeanDefinition進行處理(關鍵)坝疼。
  @Override
  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.processBeanDefinitions:給出縮略代碼
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      //Bean的Class指定為工廠類
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      //自動裝配sqlSessionFactory
      definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
  }

這里就真正的學到了,還有這種操作:

  1. 使用代碼的方式將一個普通的spring的beanDefinition通過setBeanClass的方式變成一個工廠Bean谆沃。
  2. 還添加了屬性sqlSessionFactory钝凶,并在實例化時通過自動注入到bean中。

既然將bean轉成的工廠bean唁影,那么就要看mapperFactoryBean耕陷,也就是上面提到的@MapperScan中MapperFactoryBean.class這個類。

  • MapperFactoryBean
    上一步提到了將Bean的class指定為MapperFactoryBean据沈,并且添加了參數(shù)sqlSessionFactory哟沫,在實例化時需要自動注入sqlSessionFactory。那么 MapperFactoryBean中就會有setSqlSessionFactory方法锌介。我們在MapperFactoryBean的父類SqlSessionDaoSupport中發(fā)現(xiàn)了:
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

new SqlSessionTemplate方法最終調用代碼(中間忽略了一些調用過程):

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

這里就可以發(fā)現(xiàn)嗜诀,在工廠類實例化時,通過注入SqlSessionFactory時孔祸,通過SqlSessionFactory創(chuàng)建了sqlSession隆敢。這就揭開疑惑中第一步的獲取SqlSession的過程

既然是工廠Bean融击,那在spring實例化時就一定會調用getObject方法來創(chuàng)建真正的Bean筑公。我們來揭開最終的謎底雳窟。

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

getSqlSession()是上一步創(chuàng)建的SqlSession尊浪。而getMapper就是我們苦苦尋找的疑惑中第二步的獲取Mapper代理過程。

總結

從疑惑到揭秘封救,這里過程有點長拇涤,但是還算清晰的解釋了Spring繼承MyBatis之后便捷之處:在項目啟動時,檢索Mybatis業(yè)務接口并注入到Spring時誉结,就已經將接口在Bean初始化時轉換成最終的接口代理類了鹅士。

有問題的地方,可以在留言中指出惩坑,希望對看到的人有幫助掉盅。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末也拜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子趾痘,更是在濱河造成了極大的恐慌慢哈,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件永票,死亡現(xiàn)場離奇詭異卵贱,居然都是意外死亡,警方通過查閱死者的電腦和手機侣集,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門键俱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人世分,你說我怎么就攤上這事编振。” “怎么了臭埋?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵党觅,是天一觀的道長。 經常有香客問我斋泄,道長杯瞻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任炫掐,我火速辦了婚禮魁莉,結果婚禮上,老公的妹妹穿的比我還像新娘募胃。我一直安慰自己旗唁,他們只是感情好,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布痹束。 她就那樣靜靜地躺著检疫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祷嘶。 梳的紋絲不亂的頭發(fā)上屎媳,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音论巍,去河邊找鬼烛谊。 笑死,一個胖子當著我的面吹牛嘉汰,可吹牛的內容都是我干的丹禀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼双泪!你這毒婦竟也來了持搜?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤焙矛,失蹤者是張志新(化名)和其女友劉穎朵诫,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薄扁,經...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡剪返,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了邓梅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脱盲。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖日缨,靈堂內的尸體忽然破棺而出钱反,到底是詐尸還是另有隱情,我是刑警寧澤匣距,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布面哥,位于F島的核電站,受9級特大地震影響毅待,放射性物質發(fā)生泄漏尚卫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一尸红、第九天 我趴在偏房一處隱蔽的房頂上張望吱涉。 院中可真熱鬧,春花似錦外里、人聲如沸怎爵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鳖链。三九已至,卻和暖如春墩莫,著一層夾襖步出監(jiān)牢的瞬間芙委,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工贼穆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留题山,地道東北人兰粉。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓故痊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親玖姑。 傳聞我的和親對象是個殘疾皇子愕秫,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354