奮斗了好幾個晚上調試程序,寫了好幾篇博客,終于建立起了Mybatis配置的擴展機制。雖然擴展機制是重要的,然而如果沒有真正實用的擴展功能,那也至少是不那么鼓舞人心的,這篇博客就來舉幾個擴展的例子。
這次研讀源碼的起因是Oracle和MySQL數(shù)據(jù)庫的兼容性,比如在Oracle中使用雙豎線作為連接符,而MySQL中使用CONCAT函數(shù);比如Oracle中可以使用DECODE函數(shù),而MySQL中只能使用標準的CASE WHEN;又比如Oracle中可以執(zhí)行DELETE FORM TABLE WHERE FIELD1 IN (SELECT FIELD1 FORM TABLE WHERE FIELD2=?),但是MySQL中會拋出異常,等等。
下面就從解決這些兼容性問題開始,首先需要在配置中添加數(shù)據(jù)庫標識相關的配置:
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
|
<!-- 自行構建Configuration對象 --> <bean id= "mybatisConfig" class = "org.dysd.dao.mybatis.schema.SchemaConfiguration" /> <bean id= "sqlSessionFactory" p:dataSource-ref= "dataSource" class = "org.dysd.dao.mybatis.schema.SchemaSqlSessionFactoryBean" > <!-- 注入mybatis配置對象 --> <property name= "configuration" ref= "mybatisConfig" /> <!-- 自動掃描SqlMapper配置文件 --> <property name= "mapperLocations" > <array> <value>classpath*:**/*.sqlmapper.xml</value> </array> </property> <!-- 數(shù)據(jù)庫產品標識配置 --> <property name= "databaseIdProvider" > <bean class = "org.apache.ibatis.mapping.VendorDatabaseIdProvider" > <property name= "properties" > <props> <!-- 意思是如果數(shù)據(jù)庫產品描述中包含關鍵字MYSQL,則使用mysql作為Configuration中的databaseId,mybatis原生的實現(xiàn)關鍵字區(qū)分大小寫,我沒有測試Oracle和DB2 --> <prop key= "MySQL" >mysql</prop> <prop key= "oracle" >oracle</prop> <prop key= "H2" >h2</prop> <prop key= "db2" >db2</prop> </props> </property> </bean> </property> </bean> |
一、連接符問題
1、編寫SQL配置函數(shù)實現(xiàn)類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class ConcatSqlConfigFunction extends AbstractSqlConfigFunction{ //抽象父類中設定了默認的order級別 @Override public String getName() { return "concat" ; } @Override public String eval(String databaseId, String[] args) { if (args.length < 2 ){ Throw.throwException( "the concat function require at least two arguments." ); } if ( "mysql" .equalsIgnoreCase(databaseId)){ return "CONCAT(" +Tool.STRING.join(args, "," )+ ")" ; } else { return Tool.STRING.join(args, "||" ); } } } |
2、在SchemaHandlers類的靜態(tài)代碼塊中注冊,或者在啟動初始化類中調用SchemaHandlers的方法注冊
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
|
static { //注冊默認命名空間的StatementHandler register( "cache-ref" , new CacheRefStatementHandler()); register( "cache" , new CacheStatementHandler()); register( "parameterMap" , new ParameterMapStatementHandler()); register( "resultMap" , new ResultMapStatementHandler()); register( "sql" , new SqlStatementHandler()); register( "select|insert|update|delete" , new CRUDStatementHandler()); //注冊默認命名空間的ScriptHandler register( "trim" , new TrimScriptHandler()); register( "where" , new WhereScriptHandler()); register( "set" , new SetScriptHandler()); register( "foreach" , new ForEachScriptHandler()); register( "if|when" , new IfScriptHandler()); register( "choose" , new ChooseScriptHandler()); //register("when", new IfScriptHandler()); register( "otherwise" , new OtherwiseScriptHandler()); register( "bind" , new BindScriptHandler()); // 注冊自定義命名空間的處理器 registerExtend( "db" , new DbStatementHandler(), new DbScriptHandler()); // 注冊SqlConfigFunction register( new DecodeSqlConfigFunction()); register( new ConcatSqlConfigFunction()); // 注冊SqlConfigFunctionFactory register( new LikeSqlConfigFunctionFactory()); } |
上面代碼除了注冊ConcatSQLConfigFunction外,還有一些其它的注冊代碼,這里一并給出,下文將省略。
3、修改SqlMapper配置
1
2
3
4
5
6
7
|
<select id= "selectString" resultType= "string" > select PARAM_NAME, $concat{PARAM_CODE, PARAM_NAME} AS CODE_NAME from BF_PARAM_ENUM_DEF < if test= "null != paramName and '' != paramName" > where PARAM_NAME LIKE $CONCAT{ '%' , #{paramName, jdbcType=VARCHAR}, '%' } </ if > </select> |
4、編寫dao接口類
1
2
3
4
|
@Repository public interface IExampleDao { public String selectString( @Param ( "paramName" )String paramName); } |
5、編寫測試類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@RunWith (SpringJUnit4ClassRunner. class ) @ContextConfiguration (locations={ "classpath:spring/applicationContext.xml" }) @Component public class ExampleDaoTest { @Resource private IExampleDao dao; @Test public void testSelectString(){ String a = dao.selectString( "顯示" ); Assert.assertEquals( "顯示區(qū)域" , a); } } |
6、分別在MySQL和H2中運行如下(將mybatis日志級別調整為TRACE)
(1)MySQL
1
2
3
4
5
|
20161108 00 : 12 : 55 , 235 [main]-[DEBUG] ==> Preparing: select PARAM_NAME, CONCAT(PARAM_CODE,PARAM_NAME) AS CODE_NAME from BF_PARAM_ENUM_DEF where PARAM_NAME LIKE CONCAT( '%' ,?, '%' ) 20161108 00 : 12 : 55 , 269 [main]-[DEBUG] ==> Parameters: 顯示(String) 20161108 00 : 12 : 55 , 287 [main]-[TRACE] <== Columns: PARAM_NAME, CODE_NAME 20161108 00 : 12 : 55 , 287 [main]-[TRACE] <== Row: 顯示區(qū)域, DISPLAY_AREA顯示區(qū)域 20161108 00 : 12 : 55 , 289 [main]-[DEBUG] <== Total: 1 |
(2)H2
1
2
3
4
5
|
20161108 00 : 23 : 08 , 348 [main]-[DEBUG] ==> Preparing: select PARAM_NAME, PARAM_CODE||PARAM_NAME AS CODE_NAME from BF_PARAM_ENUM_DEF where PARAM_NAME LIKE '%' ||?|| '%' 20161108 00 : 23 : 08 , 364 [main]-[DEBUG] ==> Parameters: 顯示(String) 20161108 00 : 23 : 08 , 411 [main]-[TRACE] <== Columns: PARAM_NAME, CODE_NAME 20161108 00 : 23 : 08 , 411 [main]-[TRACE] <== Row: 顯示區(qū)域, DISPLAY_AREA顯示區(qū)域 20161108 00 : 23 : 08 , 411 [main]-[DEBUG] <== Total: 1 |
可以看到,已經解決連接符的兼容性問題了。
另外,我們也發(fā)現(xiàn),使用LIKE關鍵字時,寫起來比較麻煩,那我們就給它一組新的SQL配置函數(shù)吧:
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
|
public class LikeSqlConfigFunctionFactory implements ISqlConfigFunctionFactory{ @Override public Collection<ISqlConfigFunction> getSqlConfigFunctions() { return Arrays.asList(getLeftLikeSqlConfigFunction(),getRightLikeSqlConfigFunction(),getLikeSqlConfigFunction()); } private ISqlConfigFunction getLeftLikeSqlConfigFunction(){ return new AbstractLikeSqlConfigFunction(){ @Override public String getName() { return "llike" ; } @Override protected String eval(String arg) { return "LIKE $concat{'%'," +arg+ "}" ; } }; } private ISqlConfigFunction getRightLikeSqlConfigFunction(){ return new AbstractLikeSqlConfigFunction(){ @Override public String getName() { return "rlike" ; } @Override protected String eval(String arg) { return "LIKE $concat{" +arg+ ", '%'}" ; } }; } private ISqlConfigFunction getLikeSqlConfigFunction(){ return new AbstractLikeSqlConfigFunction(){ @Override public String getName() { return "like" ; } @Override protected String eval(String arg) { return "LIKE $concat{'%'," +arg+ ", '%'}" ; } }; } private abstract class AbstractLikeSqlConfigFunction extends AbstractSqlConfigFunction{ @Override public String eval(String databaseId, String[] args) { if (args.length != 1 ){ Throw.throwException( "the like function require one and only one argument." ); } return eval(args[ 0 ]); } protected abstract String eval(String arg); } } |
這里,定義了一組SQL配置函數(shù),左相似,右相似以及中間相似匹配,并且SQL配置函數(shù)還可以嵌套。于是,SqlMapper的配置文件簡化為:
1
2
3
4
5
6
7
|
<select id= "selectString" resultType= "string" > select PARAM_NAME, $concat{PARAM_CODE, PARAM_NAME} AS CODE_NAME from BF_PARAM_ENUM_DEF < if test= "null != paramName and '' != paramName" > where PARAM_NAME $like{#{paramName, jdbcType=VARCHAR}} </ if > </select> |
運行結果完全相同。
如果還覺得麻煩,因為PARAM_NAME和paramName是駝峰式對應,甚至還可以添加一個fieldLike函數(shù),并將配置修改為
1
|
where $fieldLike{#{PARAM_NAME, jdbcType=VARCHAR}} |
如果再結合數(shù)據(jù)字典,jdbcType的配置也可自動生成:
1
|
where $fieldLike{#{PARAM_NAME}} |
這種情形下,如果有多個參數(shù),也不會出現(xiàn)歧義(或者新定義一個配置函數(shù)$likes{}消除歧義),于是可將多個條件簡化成:
1
|
where $likes{#{PARAM_NAME, PARAM_NAME2, PARAM_NAME3}} |
當然,還有更多可挖掘的簡化,已經不止是兼容性的范疇了,這里就不再進一步展開了。
二、DECODE函數(shù)/CASE ... WHEN
Oracle中的DECODE函數(shù)非常方便,語法如下:
DECODE(條件,值1,返回值1,值2,返回值2,...值n,返回值n[,缺省值])
等價的標準寫法:
1
2
3
4
5
6
7
|
CASE 條件 WHEN 值 1 THEN 返回值 1 WHEN 值 2 THEN 返回值 2 ... WHEN 值n THEN 返回值n [ELSE 缺省值] END |
現(xiàn)在我們來實現(xiàn)一個$decode配置函數(shù):
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
|
public class DecodeSqlConfigFunction extends AbstractSqlConfigFunction{ @Override public String getName() { return "decode" ; } @Override public String eval(String databaseId, String[] args) { if (args.length < 3 ){ Throw.throwException( "the decode function require at least three arguments." ); } if ( "h2" .equalsIgnoreCase(databaseId)){ //測試時,使用h2代替oracle,正式程序中修改為oracle return "DECODE(" +Tool.STRING.join(args, "," )+ ")" ; } else { StringBuffer sb = new StringBuffer(); sb.append( "CASE " ).append(args[ 0 ]); int i= 2 , l = args.length; for (; i < l; i= i+ 2 ){ sb.append( " WHEN " ).append(args[i- 1 ]).append( " THEN " ).append(args[i]); } if (i == l){ //結束循環(huán)時,兩者相等說明最后一個參數(shù)未使用 sb.append( " ELSE " ).append(args[l- 1 ]); } sb.append( " END" ); return sb.toString(); } } } |
然后使用SchemaHandlers注冊,修改SqlMapper中配置:
1
2
3
4
5
6
7
|
<select id= "selectString" resultType= "string" > select PARAM_NAME, $decode{#{paramName}, '1' , 'A' , '2' , 'B' , 'C' } AS DECODE_TEST from BF_PARAM_ENUM_DEF < if test= "null != paramName and '' != paramName" > where PARAM_NAME $like{#{paramName, jdbcType=VARCHAR}} </ if > </select> |
測試如下:
(1)H2中(以H2代替Oracle)
1
|
20161108 06 : 53 : 29 , 747 [main]-[DEBUG] ==> Preparing: select PARAM_NAME, DECODE(?, '1' , 'A' , '2' , 'B' , 'C' ) AS DECODE_TEST from BF_PARAM_ENUM_DEF where PARAM_NAME LIKE '%' ||?|| '%' |
(2)MySQL中
1
|
20161108 06 : 50 : 55 , 998 [main]-[DEBUG] ==> Preparing: select PARAM_NAME, CASE ? WHEN '1' THEN 'A' WHEN '2' THEN 'B' ELSE 'C' END AS DECODE_TEST from BF_PARAM_ENUM_DEF where PARAM_NAME LIKE '%' ||?|| '%' |
以上所述是小編給大家介紹的Mybatis中SqlMapper配置的擴展與應用詳細介紹(1),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!
原文鏈接:http://www.cnblogs.com/linjisong/p/6041239.html