重構Mybatis與Spring集成的SqlSessionFactoryBean(1)

通常來講,修改框架的源代碼是極其有風險的,除非萬不得已,不然不要去修改。可是今天卻當心翼翼的重構了Mybatis官方提供的與Spring集成的SqlSessionFactoryBean類,一來是抱着試錯的心態,二來也的確是有現實須要。java

先說明兩點:spring

  • 一般來說,重構是指不改變功能的狀況下優化代碼,但本文所說的重構也包括了添加功能
  • 本文使用的主要jar包(版本):spring-*-4.3.3.RELEASE.jar、mybatis-3.4.1.jar、mybatis-spring-1.3.0.jar

下面從Mybatis與Spring集成談起。sql

1、集成Mybatis與Springmybatis

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

集成的關鍵類爲org.mybatis.spring.SqlSessionFactoryBean,是一個工廠Bean,用於產生Mybatis全局性的會話工廠SqlSessionFactory(也就是產生會話工廠的工廠Bean),而SqlSessionFactory用於產生會話SqlSession對象(SqlSessionFactory至關於DataSource,SqlSession至關於Connection)。app

其中屬性(使用p命名空間或property子元素配置):框架

  • dataSource是數據源,可使用DBCP、C3P0、Druid、jndi-lookup等多種方式配置
  • configLocation是Mybatis引擎的全局配置,用於修飾Mybatis的行爲
  • mapperLocations是Mybatis須要加載的SqlMapper腳本配置文件(模式)。

固然還有不少其它的屬性,這裏不一一例舉了。ide

2、爲何要重構優化

一、源碼優化ui

SqlSessionFactoryBean的做用是產生SqlSessionFactory,那咱們看一下這個方法(SqlSessionFactoryBean.java 384-538行):this

/**
   * Build a {@code SqlSessionFactory} instance.
   *
   * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
   * {@code SqlSessionFactory} instance based on an Reader.
   * Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file).
   *
   * @return SqlSessionFactory
   * @throws IOException if loading the config file failed
   */
  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    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) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      configuration.setVariables(this.configurationProperties);
    }

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

    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }

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

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

    return this.sqlSessionFactoryBuilder.build(configuration);
  }
View Code

雖然Mybatis是一個優秀的持久層框架,但老實說,這段代碼的確不怎麼樣,有很大的重構優化空間。

二、功能擴展

(1)使用Schema來校驗SqlMapper

<!-- DTD方式 -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dysd.dao.mybatis.config.IExampleDao">
</mapper>
<!-- SCHEMA方式 -->
<?xml version="1.0" encoding="UTF-8" ?>
<mapper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://dysd.org/schema/sqlmapper"
    xsi:schemaLocation="http://dysd.org/schema/sqlmapper http://dysd.org/schema/sqlmapper.xsd"
    namespace="org.dysd.dao.mybatis.config.IExampleDao">
</mapper>

初看上去使用Schema更復雜,但若是配合IDE,使用Schema的自動提示更加友好,校驗信息也更加清晰,同時還給其餘開發人員打開了一扇窗口,容許他們在已有命名空間基礎之上自定義命名空間,好比能夠引入<ognl>標籤,使用OGNL表達式來配置SQL語句等等。

(2)定製配置,SqlSessionFactoryBean已經提供了較多的參數用於定製配置,但仍然有可能須要更加個性化的設置,好比:

A、設置默認的結果類型,對於沒有設置resultType和resultMap的<select>元素,解析後能夠爲其設置默認的返回類型爲Map,從而簡化SqlMapper的配置

<!--簡化前--> <select id="select" resultType="map"> SELECT * FROM TABLE_NAME WHERE FIELD1 = #{field1, jdbcType=VARCHAR} </select> <!--簡化後--> <select id="select"> SELECT * FROM TABLE_NAME WHERE FIELD1 = #{field1, jdbcType=VARCHAR} </select>

B、擴展Mybatis原有的參數解析,原生解析實現是DefaultParameterHandler,能夠繼承並擴展這個實現,好比對於spel:爲前綴的屬性表達式,使用SpEL去求值

(3)其它擴展,可參考筆者前面關於Mybatis擴展的相關博客

三、重構可行性

(1)在代碼影響範圍上

下面是SqlSessionFactoryBean的繼承結構

從中能夠看出,SqlSessionFactoryBean繼承體系並不複雜,沒有繼承其它的父類,只是實現了Spring中的三個接口(JDK中的EventListener只是一個標識)。而且SqlSessionFactoryBean是面向最終開發用戶的,沒有子類,也沒有其它的類調用它,所以從代碼影響範圍上,是很是小的。

(2)在重構實現上,能夠在本身的包中新建一個SqlSessionFactoryBean,而後一開始代碼徹底複製Mybatis官方的SqlSessionFactoryBean,修改包名,而後以此做爲重構的基礎,這樣比較簡單,這一層只作代碼重構,儘可能保持功能相同,若是須要作功能重構,好比添加Schema校驗,就再進一層,建一個SchemaSqlSessionFactoryBean,繼承咱們本身的SqlSessionFactoryBean。

(3)在集成應用上,只須要修改和spring集成配置中的class屬性便可。

相關文章
相關標籤/搜索