深入淺出重構(gòu)Mybatis與Spring集成的SqlSessionFactoryBean(上)
一般來說,修改框架的源代碼是極其有風(fēng)險(xiǎn)的,除非萬不得已,否則不要去修改。但是今天卻小心翼翼的重構(gòu)了Mybatis官方提供的與Spring集成的SqlSessionFactoryBean類,一來是抱著試錯(cuò)的心態(tài),二來也的確是有現(xiàn)實(shí)需要。
先說明兩點(diǎn):
通常來講,重構(gòu)是指不改變功能的情況下優(yōu)化代碼,但本文所說的重構(gòu)也包括了添加功能
本文使用的主要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>
集成的關(guān)鍵類為org.mybatis.spring.SqlSessionFactoryBean,是一個(gè)工廠Bean,用于產(chǎn)生Mybatis全局性的會(huì)話工廠SqlSessionFactory(也就是產(chǎn)生會(huì)話工廠的工廠Bean),而SqlSessionFactory用于產(chǎn)生會(huì)話SqlSession對(duì)象(SqlSessionFactory相當(dāng)于DataSource,SqlSession相當(dāng)于Connection)。
其中屬性(使用p命名空間或property子元素配置):
dataSource是數(shù)據(jù)源,可以使用DBCP、C3P0、Druid、jndi-lookup等多種方式配置
configLocation是Mybatis引擎的全局配置,用于修飾Mybatis的行為
mapperLocations是Mybatis需要加載的SqlMapper腳本配置文件(模式)。
當(dāng)然還有很多其它的屬性,這里不一一例舉了。
二、為什么要重構(gòu)
1、源碼優(yōu)化
SqlSessionFactoryBean的作用是產(chǎn)生SqlSessionFactory,那我們看一下這個(gè)方法(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是一個(gè)優(yōu)秀的持久層框架,但老實(shí)說,這段代碼的確不怎么樣,有很大的重構(gòu)優(yōu)化空間。
2、功能擴(kuò)展
(1)使用Schema來校驗(yàn)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更復(fù)雜,但如果配合IDE,使用Schema的自動(dòng)提示更加友好,校驗(yàn)信息也更加清晰,同時(shí)還給其他開發(fā)人員打開了一扇窗口,允許他們?cè)谝延忻臻g基礎(chǔ)之上自定義命名空間,比如可以引入<ognl>標(biāo)簽,使用OGNL表達(dá)式來配置SQL語句等等。
(2)定制配置,SqlSessionFactoryBean已經(jīng)提供了較多的參數(shù)用于定制配置,但仍然有可能需要更加個(gè)性化的設(shè)置,比如:
A、設(shè)置默認(rèn)的結(jié)果類型,對(duì)于沒有設(shè)置resultType和resultMap的<select>元素,解析后可以為其設(shè)置默認(rèn)的返回類型為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、擴(kuò)展Mybatis原有的參數(shù)解析,原生解析實(shí)現(xiàn)是DefaultParameterHandler,可以繼承并擴(kuò)展這個(gè)實(shí)現(xiàn),比如對(duì)于spel:為前綴的屬性表達(dá)式,使用SpEL去求值
(3)其它擴(kuò)展,可參考筆者前面關(guān)于Mybatis擴(kuò)展的相關(guān)博客
3、重構(gòu)可行性
(1)在代碼影響范圍上
下面是SqlSessionFactoryBean的繼承結(jié)構(gòu)
從中可以看出,SqlSessionFactoryBean繼承體系并不復(fù)雜,沒有繼承其它的父類,只是實(shí)現(xiàn)了Spring中的三個(gè)接口(JDK中的EventListener只是一個(gè)標(biāo)識(shí))。并且SqlSessionFactoryBean是面向最終開發(fā)用戶的,沒有子類,也沒有其它的類調(diào)用它,因此從代碼影響范圍上,是非常小的。
(2)在重構(gòu)實(shí)現(xiàn)上,可以新建一個(gè)SchemaSqlSessionFactoryBean,然后一開始代碼完全復(fù)制SqlSessionFactoryBean,修改包名、類名,然后以此作為重構(gòu)的基礎(chǔ),這樣比較簡單。
(3)在集成應(yīng)用上,只需要修改和spring集成配置中的class屬性即可。
以上所述是小編給大家介紹的重構(gòu)Mybatis與Spring集成的SqlSessionFactoryBean(上),希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
jdk17?SpringBoot?JPA集成多數(shù)據(jù)庫的示例詳解
這篇文章主要介紹了jdk17?SpringBoot?JPA集成多數(shù)據(jù)庫的示例代碼,包括配置類、請(qǐng)求攔截器、線程上下文等相關(guān)知識(shí),代碼簡單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08SpringBoot Mybatis Plus公共字段自動(dòng)填充功能
這篇文章主要介紹了SpringBoot Mybatis Plus公共字段自動(dòng)填充功能的相關(guān)資料,需要的朋友可以參考下2017-04-04SpringBoot Admin 如何實(shí)現(xiàn)Actuator端點(diǎn)可視化監(jiān)控
這篇文章主要介紹了SpringBoot Admin 如何實(shí)現(xiàn)Actuator端點(diǎn)可視化監(jiān)控,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Java 實(shí)戰(zhàn)項(xiàng)目之畢業(yè)設(shè)計(jì)管理系統(tǒng)的實(shí)現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實(shí)現(xiàn)畢業(yè)設(shè)計(jì)管理系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11Java與SpringBoot對(duì)redis的使用方式
這篇文章主要介紹了Java與SpringBoot對(duì)redis的使用方式,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-08-08Springboot集成Jasypt實(shí)現(xiàn)配置文件加密的方法
Jasypt是一個(gè)java庫,它允許開發(fā)員以最少的努力為他/她的項(xiàng)目添加基本的加密功能,并且不需要對(duì)加密工作原理有深入的了解,這篇文章主要介紹了Springboot集成Jasypt實(shí)現(xiàn)配置文件加密,需要的朋友可以參考下2023-04-04springboot整合RabbitMQ發(fā)送短信的實(shí)現(xiàn)
本文會(huì)和SpringBoot做整合,實(shí)現(xiàn)RabbitMQ發(fā)送短信,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05