一般來說,修改框架的源代碼是極其有風(fēng)險的,除非萬不得已,否則不要去修改。但是今天卻小心翼翼的重構(gòu)了Mybatis官方提供的與Spring集成的SqlSessionFactoryBean類,一來是抱著試錯的心態(tài),二來也的確是有現(xià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
1
2
3
4
5
6
7
|
<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,是一個工廠Bean,用于產(chǎn)生Mybatis全局性的會話工廠SqlSessionFactory(也就是產(chǎn)生會話工廠的工廠Bean),而SqlSessionFactory用于產(chǎn)生會話SqlSession對象(SqlSessionFactory相當于DataSource,SqlSession相當于Connection)。
其中屬性(使用p命名空間或property子元素配置):
dataSource是數(shù)據(jù)源,可以使用DBCP、C3P0、Druid、jndi-lookup等多種方式配置
configLocation是Mybatis引擎的全局配置,用于修飾Mybatis的行為
mapperLocations是Mybatis需要加載的SqlMapper腳本配置文件(模式)。
當然還有很多其它的屬性,這里不一一例舉了。
二、為什么要重構(gòu)
1、源碼優(yōu)化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
SqlSessionFactoryBean的作用是產(chǎn)生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是一個優(yōu)秀的持久層框架,但老實說,這段代碼的確不怎么樣,有很大的重構(gòu)優(yōu)化空間。
2、功能擴展
(1)使用Schema來校驗SqlMapper
1
2
3
4
5
6
7
8
9
10
11
12
|
<!-- 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的自動提示更加友好,校驗信息也更加清晰,同時還給其他開發(fā)人員打開了一扇窗口,允許他們在已有命名空間基礎(chǔ)之上自定義命名空間,比如可以引入<ognl>標簽,使用OGNL表達式來配置SQL語句等等。
(2)定制配置,SqlSessionFactoryBean已經(jīng)提供了較多的參數(shù)用于定制配置,但仍然有可能需要更加個性化的設(shè)置,比如:
A、設(shè)置默認的結(jié)果類型,對于沒有設(shè)置resultType和resultMap的<select>元素,解析后可以為其設(shè)置默認的返回類型為Map,從而簡化SqlMapper的配置
1
2
3
4
5
6
7
8
|
<!--簡化前--> <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原有的參數(shù)解析,原生解析實現(xiàn)是DefaultParameterHandler,可以繼承并擴展這個實現(xiàn),比如對于spel:為前綴的屬性表達式,使用SpEL去求值
(3)其它擴展,可參考筆者前面關(guān)于Mybatis擴展的相關(guān)博客
3、重構(gòu)可行性
(1)在代碼影響范圍上
下面是SqlSessionFactoryBean的繼承結(jié)構(gòu)
從中可以看出,SqlSessionFactoryBean繼承體系并不復(fù)雜,沒有繼承其它的父類,只是實現(xiàn)了Spring中的三個接口(JDK中的EventListener只是一個標識)。并且SqlSessionFactoryBean是面向最終開發(fā)用戶的,沒有子類,也沒有其它的類調(diào)用它,因此從代碼影響范圍上,是非常小的。
(2)在重構(gòu)實現(xiàn)上,可以新建一個SchemaSqlSessionFactoryBean,然后一開始代碼完全復(fù)制SqlSessionFactoryBean,修改包名、類名,然后以此作為重構(gòu)的基礎(chǔ),這樣比較簡單。
(3)在集成應(yīng)用上,只需要修改和spring集成配置中的class屬性即可。
以上所述是小編給大家介紹的重構(gòu)Mybatis與Spring集成的SqlSessionFactoryBean(上),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對服務(wù)器之家網(wǎng)站的支持!
原文鏈接:http://www.cnblogs.com/linjisong/p/6022245.html