一般来说,修改框架的源代码是极其有风险的,除非万不得已,否则不要去修改。但是今天却小心翼翼的重构了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)在重构实现上,可以在自己的包中新建一个SqlSessionFactoryBean,然后一开始代码完全复制Mybatis官方的SqlSessionFactoryBean,修改包名,然后以此作为重构的基础,这样比较简单,这一层只做代码重构,尽量保持功能相同,如果需要做功能重构,比如添加Schema校验,就再进一层,建一个SchemaSqlSessionFactoryBean,继承我们自己的SqlSessionFactoryBean。

(3)在集成应用上,只需要修改和spring集成配置中的class属性即可。

重构Mybatis与Spring集成的SqlSessionFactoryBean(1)的更多相关文章

  1. 重构Mybatis与Spring集成的SqlSessionFactoryBean(2)

    三.代码重构 1.先使用Eclipse把buildSqlSessionFactory()方法中众多的if换成小函数 protected SqlSessionFactory buildSqlSessio ...

  2. Mybatis与Spring集成时都做了什么?

    Mybatis是java开发者非常熟悉的ORM框架,Spring集成Mybatis更是我们的日常开发姿势. 本篇主要讲Mybatis与Spring集成所做的事情,让读过本文的开发者对Mybatis和S ...

  3. mybatis与Spring集成(Aop整合PagerAspect插件)

    目的: Mybatis与spring集成 Aop整合pagehelper插件 Mybatis与spring集成 导入pom依赖 <?xml version="1.0" enc ...

  4. Mybatis与Spring集成(易百教程)

    整个Mybatis与Spring集成示例要完成的步骤如下: 1.示例功能描述 2.创建工程 3.数据库表结构及数据记录 4.实例对象 5.配置文件 6.测试执行,输出结果 1.示例功能描述 在本示例中 ...

  5. MyBatis与Spring集成

    beans.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="htt ...

  6. 深入浅出mybatis之与spring集成

    目录 写在前面 详细配置 1.dataSource(数据源) 2.sqlSessionFactory(Session工厂) 3.Mapper(映射器) 4.TransactionManager(事务管 ...

  7. MyBatis从入门到精通(第9章):Spring集成MyBatis(中)

    MyBatis从入门到精通(第9章):Spring集成MyBatis(中) 框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法.应该将应用自身的设计和具体 ...

  8. 由“单独搭建Mybatis”到“Mybatis与Spring的整合/集成”

    在J2EE领域,Hibernate与Mybatis是大家常用的持久层框架,它们各有特点,在持久层框架中处于领导地位. 本文主要介绍Mybatis(对于较小型的系统,特别是报表较多的系统,个人偏向Myb ...

  9. Java Persistence with MyBatis 3(中国版) 第五章 与Spring集成

    MyBatis-Spring它是MyBatis子模块框.它用来提供流行的依赖注入框架Spring无缝集成. Spring框架是一个基于依赖注入(Dependency Injection)和面向切面编程 ...

随机推荐

  1. Linux命令之awk数组使用范例

    目录 取ifconfig bond0的IP地址    1 命令如下:    2 统计apache日志单IP访问请求数排名    2 第一种方法    2 第二种方法    2 统计域名访问量    3 ...

  2. Auto_increment详解

    Auto_increment Mysql AUTO_INCREMENT 1.Innodb表的自动增长列可以手工插入,但是插入的值如果是空或者0,则实际插入的将是自动增长后的值 mysql> cr ...

  3. Linux下Chrome浏览器不支持WebGL的解决方式。

    今天使用Chrome浏览器,总是报这样一个错误: Uncaught TypeError: Cannot read property 'canvas' of null. 细看之下是无法获取WebGL上下 ...

  4. CSS盒模型

    CSS盒模型是CSS 可视化格式化系统的基石,它是理解样式表如何工作的核心概念.盒模型用于元素定位和页面布局.元素框的最内部分是实际的内容,直接包围内容的是内边距.内边距呈现了元素的背景.内边距的边缘 ...

  5. 利用nodejs实现登录并转码视频(原创)

    nodejs的出现让前端人员可以使用js打造后台,也许哪天就真的摆脱了对java或者php的依赖了. 今天跟大家分享一个利用nodejs接受前端post请求,并实现视频转码的这样一个例子.视频转码用到 ...

  6. Anciroid的IPC机制-Binder概述

    在Linux系统中,是以进程为单位分配和管理资源的.出于保护机制,一个进程不能直接访问另一个进程的资源,也就是说,进程之间互相封闭.但是,在一个复杂的应用系统中,通常会使用多个相关的进程来共同完成一项 ...

  7. EmberJs之数组绑定@each&[]

    写在前面 好长时间没有写博客了,昨天花了些时间又整理了下之前发布过的<Ember.js之computed Property>文章,并创建了一个测试代码库,花了些时间,希望能使用测试代码的方 ...

  8. RCP: MANIFEST.MF, plugin.xml, build.properties三种文件的区别

    在Eclipse插件开发中, MANIFEST.MF, plugin.xml, build.properties是三种最常见的文件,由于它们共享同一个编辑器(Plug-in Manifest Edit ...

  9. java 多线程(daemon)

    package com.example; public class App { public static void main(String[] args) { DoDaemon d1 = new D ...

  10. Newtonsoft.Json中的时间格式详解.

    Newtonsoft.json是一款不错的序列化反序列化第三方组件,具体如何使用属于基础知识,此处不再讲解.看以下代码: public class OutgameEntity { public str ...