深入浅出重构Mybatis与Spring集成的SqlSessionFactoryBean(上)
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
深⼊浅出重构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 "-////DTD Mapper 3.0//EN" "/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="/2001/XMLSchema-instance"
xmlns="/schema/sqlmapper"
xsi:schemaLocation="/schema/sqlmapper /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(上),希望对⼤家有所帮助,如果⼤家有任何疑问请给我留⾔,⼩编会及时回复⼤家的。
在此也⾮常感谢⼤家对⽹站的⽀持!。