一般來說,修改框架的源代碼是極其有風險的,除非萬不得已,否則不要去修改。但是今天卻小心翼翼的重構了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 xmlstry {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(上),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對武林網網站的支持!
新聞熱點
疑難解答