zoukankan      html  css  js  c++  java
  • 重构Mybatis与Spring集成的SqlSessionFactoryBean(1)

    一般来说,修改框架的源代码是极其有风险的,除非万不得已,否则不要去修改。但是今天却小心翼翼的重构了Mybatis官方提供的与Spring集成的SqlSessionFactoryBean类,一来是抱着试错的心态,二来也的确是有现实需要。

    先说明两点:

    • 通常来讲,重构是指不改变功能的情况下优化代码,但本文所说的重构也包括了添加功能
    • 本文使用的主要jar包(版本):spring-*-4.3.3.RELEASE.jar、mybatis-3.4.1.jar、mybatis-spring-1.3.0.jar

    下面从Mybatis与Spring集成谈起。

    一、集成Mybatis与Spring

    <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)。

    其中属性(使用p命名空间或property子元素配置):

    • dataSource是数据源,可以使用DBCP、C3P0、Druid、jndi-lookup等多种方式配置
    • configLocation是Mybatis引擎的全局配置,用于修饰Mybatis的行为
    • mapperLocations是Mybatis需要加载的SqlMapper脚本配置文件(模式)。

    当然还有很多其它的属性,这里不一一例举了。

    二、为什么要重构

    1、源码优化

    SqlSessionFactoryBean的作用是产生SqlSessionFactory,那我们看一下这个方法(SqlSessionFactoryBean.java 384-538行):

    /**
       * 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是一个优秀的持久层框架,但老实说,这段代码的确不怎么样,有很大的重构优化空间。

    2、功能扩展

    (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扩展的相关博客

    3、重构可行性

    (1)在代码影响范围上

    下面是SqlSessionFactoryBean的继承结构

    从中可以看出,SqlSessionFactoryBean继承体系并不复杂,没有继承其它的父类,只是实现了Spring中的三个接口(JDK中的EventListener只是一个标识)。并且SqlSessionFactoryBean是面向最终开发用户的,没有子类,也没有其它的类调用它,因此从代码影响范围上,是非常小的。

    (2)在重构实现上,可以在自己的包中新建一个SqlSessionFactoryBean,然后一开始代码完全复制Mybatis官方的SqlSessionFactoryBean,修改包名,然后以此作为重构的基础,这样比较简单,这一层只做代码重构,尽量保持功能相同,如果需要做功能重构,比如添加Schema校验,就再进一层,建一个SchemaSqlSessionFactoryBean,继承我们自己的SqlSessionFactoryBean。

    (3)在集成应用上,只需要修改和spring集成配置中的class属性即可。

  • 相关阅读:
    Blank page instead of the SharePoint Central Administration site
    BizTalk 2010 BAM Configure
    Use ODBA with Visio 2007
    Handling SOAP Exceptions in BizTalk Orchestrations
    BizTalk与WebMethods之间的EDI交换
    Append messages in BizTalk
    FTP protocol commands
    Using Dynamic Maps in BizTalk(From CodeProject)
    Synchronous To Asynchronous Flows Without An Orchestration的简单实现
    WSE3 and "Action for ultimate recipient is required but not present in the message."
  • 原文地址:https://www.cnblogs.com/linjisong/p/6022245.html
Copyright © 2011-2022 走看看