通常来讲,修改框架的源代码是极其有风险的,除非万不得已,不然不要去修改。可是今天却当心翼翼的重构了Mybatis官方提供的与Spring集成的SqlSessionFactoryBean类,一来是抱着试错的心态,二来也的确是有现实须要。java
先说明两点:spring
下面从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子元素配置):框架
固然还有不少其它的属性,这里不一一例举了。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); }
虽然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属性便可。