7-基于Spring的框架-MyBatis——7-2 熟悉Spring對MyBatis的包裝思路

概要

過度

我們上面介紹了 MyBatis 的使用方法和 Spring MyBatis 的使用方法。本文著重介紹 Spring MyBatis 的封裝橘忱。當然,這里只是著重介紹針對 MyBatis API 的封裝,不介紹包的掃描邏輯。

我們要介紹的主要有兩個地方徙瓶,在xml中的位置分別如下:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="configLocation" value="classpath:MyBatis-config.xml"/>
  <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="mapper.UserMapper"/>
  <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

其實就是兩個類毛雇。

內(nèi)容簡介

本文主要介紹SqlSessionFactoryBeanMapperFactoryBean兩個類的內(nèi)部邏輯侦镇。

所屬環(huán)節(jié)

對 spring-mybatis 的實現(xiàn)詳情的介紹灵疮。

上下環(huán)節(jié)

上文: spring-mybatis 的引入

下文: 對 spring-mybatis 中涉及的高級功能——包掃描及BD注冊的探索

SqlSessionFactoryBean源碼

入口

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="configLocation" value="classpath:MyBatis-config.xml"/>
  <property name="dataSource" ref="dataSource"/>
</bean>

在Spring中注冊了這個BD,沒有額外做其他操作壳繁,我們看一下SqlSessionFactoryBean的繼承關(guān)系:

1.png

我們發(fā)現(xiàn)它實現(xiàn)了三個比較有意思的接口:

  • InitializingBean:此函數(shù)有初始化鉤子震捣,應(yīng)該是用于配置加載和整合
  • FactoryBean:他是FactoryBean,猜測生成目標類型的實例可能存在一些邏輯
  • ApplicationListener:他監(jiān)聽著ApplicationContext的事件闹炉,猜測可能根據(jù)事件做出一些變化蒿赢,如更新配置、控制一些生命周期之類的吧

InitializingBean

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

這里僅僅做了一些非空判斷渣触,然后將邏輯繼續(xù)委托羡棵,和Spring的代碼邏輯很像。注意記住這里是configurationconfigLocation不能同時配置昵观。

我們繼續(xù)看核心邏輯的方法:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

  Configuration configuration;

  XMLConfigBuilder xmlConfigBuilder = null;
  // 如果配置的 Configuration 不為空晾腔,就把我們在 Spring 的xml中配置的屬性填進去
  if (this.configuration != null) {
    configuration = this.configuration;
    if (configuration.getVariables() == null) {
      configuration.setVariables(this.configurationProperties);
    } else if (this.configurationProperties != null) {
      configuration.getVariables().putAll(this.configurationProperties);
    }
  } else if (this.configLocation != null) {
    // 否則如果你配置了 MyBatis 的配置文件地址,就從那里找值啊犬、加載
    xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    configuration = xmlConfigBuilder.getConfiguration();
  } else {
    // 如果都沒有配置,那就自己new一個配置對象壁查,然后把你在 Spring 的xml中配置的屬性填進去
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
    }
    configuration = new Configuration();
    if (this.configurationProperties != null) {
      configuration.setVariables(this.configurationProperties);
    }
  }

  // 到此為止我們設(shè)置了一些基本屬性觉至,如果配置了MyBatis的配置文件,就從文件中加載了所有的配置
  // 但是Spring允許從它的xml中重復(fù)配置睡腿,而且這里的配置優(yōu)先級會更高
  if (this.objectFactory != null) {
    configuration.setObjectFactory(this.objectFactory);
  }

  if (this.objectWrapperFactory != null) {
    configuration.setObjectWrapperFactory(this.objectWrapperFactory);
  }

  if (this.vfs != null) {
    configuration.setVfsImpl(this.vfs);
  }

  if (hasLength(this.typeAliasesPackage)) {
    String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                                                           ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    for (String packageToScan : typeAliasPackageArray) {
      configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                                                           typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
      }
    }
  }

  if (!isEmpty(this.typeAliases)) {
    for (Class<?> typeAlias : this.typeAliases) {
      configuration.getTypeAliasRegistry().registerAlias(typeAlias);
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Registered type alias: '" + typeAlias + "'");
      }
    }
  }

  if (!isEmpty(this.plugins)) {
    for (Interceptor plugin : this.plugins) {
      configuration.addInterceptor(plugin);
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Registered plugin: '" + plugin + "'");
      }
    }
  }

  if (hasLength(this.typeHandlersPackage)) {
    String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
                                                              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    for (String packageToScan : typeHandlersPackageArray) {
      configuration.getTypeHandlerRegistry().register(packageToScan);
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
      }
    }
  }

  if (!isEmpty(this.typeHandlers)) {
    for (TypeHandler<?> typeHandler : this.typeHandlers) {
      configuration.getTypeHandlerRegistry().register(typeHandler);
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Registered type handler: '" + typeHandler + "'");
      }
    }
  }

  if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
    try {
      configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
    } catch (SQLException e) {
      throw new NestedIOException("Failed getting a databaseId", e);
    }
  }

  if (this.cache != null) {
    configuration.addCache(this.cache);
  }

  if (xmlConfigBuilder != null) {
    try {
      xmlConfigBuilder.parse();

      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
      }
    } catch (Exception ex) {
      throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  // 設(shè)置事務(wù)管理
  if (this.transactionFactory == null) {
    this.transactionFactory = new SpringManagedTransactionFactory();
  }

  configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

  // 這里配置了mapper的xml的具體位置
  if (!isEmpty(this.mapperLocations)) {
    for (Resource mapperLocation : this.mapperLocations) {
      if (mapperLocation == null) {
        continue;
      }

      try {
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                                                                 configuration, mapperLocation.toString(), configuration.getSqlFragments());
        xmlMapperBuilder.parse();
      } catch (Exception e) {
        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
      } finally {
        ErrorContext.instance().reset();
      }

      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
      }
    }
  } else {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
    }
  }

  // 配置完成语御,這里構(gòu)建 SqlSessionFactory 并返回
  return this.sqlSessionFactoryBuilder.build(configuration);
}

FactoryBean

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

  return this.sqlSessionFactory;
}

基本沒有邏輯。

ApplicationListener

public void onApplicationEvent(ApplicationEvent event) {
  if (failFast && event instanceof ContextRefreshedEvent) {
    // fail-fast -> check all statements are completed
    this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
  }
}

如果是ApplicationContext的刷新操作席怪,就刷一遍Statement应闯。(這里我也不太懂是做些啥)

總結(jié)

這里完成了SqlSessionFactory的生成。

MapperFactoryBean源碼

入口

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="mapper.UserMapper"/>
  <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

我們指定的類型是MapperFactoryBean挂捻,看樣子是個FactoryBean碉纺,要根據(jù)內(nèi)部邏輯來判斷它返回BD 的類型。

我們看一下這個類的繼承關(guān)系:

2.png

個人感覺InitializingBean可能涉及一些初始化的問題

FactoryBean中涉及一些對BD的處理刻撒,還有對Mapper實例的構(gòu)建

InitializingBean

@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
  // Let abstract subclasses check their configuration.
  checkDaoConfig();

  // Let concrete implementations initialize themselves.
  try {
    initDao();
  }
  catch (Exception ex) {
    throw new BeanInitializationException("Initialization of DAO failed", ex);
  }
}

@Override
protected void checkDaoConfig() {
  notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}

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

protected void initDao() throws Exception {
}

我們發(fā)現(xiàn)初始化沒搞什么特別的東西吧骨田,校驗什么的也不是是因為在configuration中維護了ClassMapper實例的映射。具體為什么要做這個声怔,如何維護的映射關(guān)系态贤,這里涉及MyBatis內(nèi)部邏輯了,先不盲目深入醋火。

想想也對悠汽,所有的在SqlSessionFactoryBean都做好了箱吕,這里就等生成Mapper實例了。

FactoryBean


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


@Override
public Class<T> getObjectType() {
  return this.mapperInterface;
}

總結(jié)

到這里柿冲,完成了對 Mapper 實例的生成茬高。

總結(jié)

整體來說思路比較清晰。沒什么說的姻采。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末雅采,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子慨亲,更是在濱河造成了極大的恐慌婚瓜,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刑棵,死亡現(xiàn)場離奇詭異巴刻,居然都是意外死亡,警方通過查閱死者的電腦和手機蛉签,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門胡陪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人碍舍,你說我怎么就攤上這事柠座。” “怎么了片橡?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵妈经,是天一觀的道長。 經(jīng)常有香客問我捧书,道長吹泡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任经瓷,我火速辦了婚禮爆哑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舆吮。我一直安慰自己揭朝,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布歪泳。 她就那樣靜靜地躺著萝勤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪呐伞。 梳的紋絲不亂的頭發(fā)上敌卓,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音伶氢,去河邊找鬼趟径。 笑死瘪吏,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蜗巧。 我是一名探鬼主播掌眠,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼幕屹!你這毒婦竟也來了蓝丙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤望拖,失蹤者是張志新(化名)和其女友劉穎渺尘,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體说敏,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡鸥跟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了盔沫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片医咨。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖架诞,靈堂內(nèi)的尸體忽然破棺而出拟淮,到底是詐尸還是另有隱情,我是刑警寧澤谴忧,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布惩歉,位于F島的核電站,受9級特大地震影響俏蛮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜上遥,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一搏屑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粉楚,春花似錦辣恋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至燃异,卻和暖如春携狭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背回俐。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工逛腿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留稀并,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓单默,卻偏偏與公主長得像碘举,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子搁廓,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容