深入浅出重构Mybatis与Spring集成的SqlSessionFactoryBean(上)
一般来说,修改框架的源代码是极其有风险的,除非万不得已,否则不要去修改。但是今天却小心翼翼的重构了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); }
虽然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)在重构实现上,可以新建一个schemasqlsessionfactorybean,然后一开始代码完全复制sqlsessionfactorybean,修改包名、类名,然后以此作为重构的基础,这样比较简单。
(3)在集成应用上,只需要修改和spring集成配置中的class属性即可。
以上所述是小编给大家介绍的重构mybatis与spring集成的sqlsessionfactorybean(上),希望对大家有所帮助