当前位置: 移动技术网 > IT编程>开发语言>Java > 深入浅出重构Mybatis与Spring集成的SqlSessionFactoryBean(上)

深入浅出重构Mybatis与Spring集成的SqlSessionFactoryBean(上)

2019年07月22日  | 移动技术网IT编程  | 我要评论

一般来说,修改框架的源代码是极其有风险的,除非万不得已,否则不要去修改。但是今天却小心翼翼的重构了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(上),希望对大家有所帮助

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网