• 重构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属性即可。

  • 相关阅读:
    PHP模拟 URL Rewrite
    FCKeditor在smarty中的使用一例
    PHP网站开发遇到的中文编码
    浪子的心情叶启田
    URL Rewrite 写在.htaccess和httpd.conf中,对php的$_SERVER变量的影响
    PHP模拟实现url rewrite
    smarty的简单分页
    PHP与WEB服务工作的三种方式
    smarty内部日期函数html_select_date()
    php读取文件:PHP读取COOKIES的实现方法
  • 原文地址:https://www.cnblogs.com/linjisong/p/6022245.html
Copyright © 2020-2023  润新知