根據下面分頁的思想,很容易實現Mybitas的多租戶設計。
使用Mybatis提供的攔截器。對分頁的SQL語句通過封裝處理,處理成不同的分頁sql。
本例已經實現了對Mysql和Oracle的分頁功能。注意下面的引用包,不要引用錯了。
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
|
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.Properties; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.statement.RoutingStatementHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; import com.yidao.utils.Page; import com.yidao.utils.ReflectHelper; /** * * 分頁攔截器,用于攔截需要進行分頁查詢的操作,然后對其進行分頁處理。 * 利用攔截器實現Mybatis分頁的原理: * 要利用JDBC對數據庫進行操作就必須要有一個對應的Statement對象,Mybatis在執行Sql語句前就會產生一個包含Sql語句的Statement對象,而且對應的Sql語句 * 是在Statement之前產生的,所以我們就可以在它生成Statement之前對用來生成Statement的Sql語句下手。在Mybatis中Statement語句是通過RoutingStatementHandler對象的 * prepare方法生成的。所以利用攔截器實現Mybatis分頁的一個思路就是攔截StatementHandler接口的prepare方法,然后在攔截器方法中把Sql語句改成對應的分頁查詢Sql語句,之后再調用 * StatementHandler對象的prepare方法,即調用invocation.proceed()。 * 對于分頁而言,在攔截器里面我們還需要做的一個操作就是統計滿足當前條件的記錄一共有多少,這是通過獲取到了原始的Sql語句后,把它改為對應的統計語句再利用Mybatis封裝好的參數和設 * 置參數的功能把Sql語句中的參數進行替換,之后再執行查詢記錄數的Sql語句進行總記錄數的統計。 * */ @Intercepts ({ @Signature (type=StatementHandler. class ,method= "prepare" ,args={Connection. class })}) public class PageInterceptor implements Interceptor { private String dialect = "" ; //數據庫方言 private String pageSqlId = "" ; //mapper.xml中需要攔截的ID(正則匹配) public Object intercept(Invocation invocation) throws Throwable { //對于StatementHandler其實只有兩個實現類,一個是RoutingStatementHandler,另一個是抽象類BaseStatementHandler, //BaseStatementHandler有三個子類,分別是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler, //SimpleStatementHandler是用于處理Statement的,PreparedStatementHandler是處理PreparedStatement的,而CallableStatementHandler是 //處理CallableStatement的。Mybatis在進行Sql語句處理的時候都是建立的RoutingStatementHandler,而在RoutingStatementHandler里面擁有一個 //StatementHandler類型的delegate屬性,RoutingStatementHandler會依據Statement的不同建立對應的BaseStatementHandler,即SimpleStatementHandler、 //PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler里面所有StatementHandler接口方法的實現都是調用的delegate對應的方法。 //我們在PageInterceptor類上已經用@Signature標記了該Interceptor只攔截StatementHandler接口的prepare方法,又因為Mybatis只有在建立RoutingStatementHandler的時候 //是通過Interceptor的plugin方法進行包裹的,所以我們這里攔截到的目標對象肯定是RoutingStatementHandler對象。 if (invocation.getTarget() instanceof RoutingStatementHandler){ RoutingStatementHandler statementHandler = (RoutingStatementHandler)invocation.getTarget(); StatementHandler delegate = (StatementHandler) ReflectHelper.getFieldValue(statementHandler, "delegate" ); BoundSql boundSql = delegate.getBoundSql(); Object obj = boundSql.getParameterObject(); if (obj instanceof Page<?>) { Page<?> page = (Page<?>) obj; //通過反射獲取delegate父類BaseStatementHandler的mappedStatement屬性 MappedStatement mappedStatement = (MappedStatement)ReflectHelper.getFieldValue(delegate, "mappedStatement" ); //攔截到的prepare方法參數是一個Connection對象 Connection connection = (Connection)invocation.getArgs()[ 0 ]; //獲取當前要執行的Sql語句,也就是我們直接在Mapper映射語句中寫的Sql語句 String sql = boundSql.getSql(); //給當前的page參數對象設置總記錄數 this .setTotalRecord(page, mappedStatement, connection); //獲取分頁Sql語句 String pageSql = this .getPageSql(page, sql); //利用反射設置當前BoundSql對應的sql屬性為我們建立好的分頁Sql語句 ReflectHelper.setFieldValue(boundSql, "sql" , pageSql); } } return invocation.proceed(); } /** * 給當前的參數對象page設置總記錄數 * * @param page Mapper映射語句對應的參數對象 * @param mappedStatement Mapper映射語句 * @param connection 當前的數據庫連接 */ private void setTotalRecord(Page<?> page, MappedStatement mappedStatement, Connection connection) { //獲取對應的BoundSql,這個BoundSql其實跟我們利用StatementHandler獲取到的BoundSql是同一個對象。 //delegate里面的boundSql也是通過mappedStatement.getBoundSql(paramObj)方法獲取到的。 BoundSql boundSql = mappedStatement.getBoundSql(page); //獲取到我們自己寫在Mapper映射語句中對應的Sql語句 String sql = boundSql.getSql(); //通過查詢Sql語句獲取到對應的計算總記錄數的sql語句 String countSql = this .getCountSql(sql); //通過BoundSql獲取對應的參數映射 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); //利用Configuration、查詢記錄數的Sql語句countSql、參數映射關系parameterMappings和參數對象page建立查詢記錄數對應的BoundSql對象。 BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page); //通過mappedStatement、參數對象page和BoundSql對象countBoundSql建立一個用于設定參數的ParameterHandler對象 ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql); //通過connection建立一個countSql對應的PreparedStatement對象。 PreparedStatement pstmt = null ; ResultSet rs = null ; try { pstmt = connection.prepareStatement(countSql); //通過parameterHandler給PreparedStatement對象設置參數 parameterHandler.setParameters(pstmt); //之后就是執行獲取總記錄數的Sql語句和獲取結果了。 rs = pstmt.executeQuery(); if (rs.next()) { int totalRecord = rs.getInt( 1 ); //給當前的參數page對象設置總記錄數 page.setTotalRecord(totalRecord); } } catch (SQLException e) { e.printStackTrace(); } finally { try { if (rs != null ) rs.close(); if (pstmt != null ) pstmt.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * 根據原Sql語句獲取對應的查詢總記錄數的Sql語句 * @param sql * @return */ private String getCountSql(String sql) { int index = sql.indexOf( "from" ); return "select count(*) " + sql.substring(index); } /** * 根據page對象獲取對應的分頁查詢Sql語句,這里只做了兩種數據庫類型,Mysql和Oracle * 其它的數據庫都 沒有進行分頁 * * @param page 分頁對象 * @param sql 原sql語句 * @return */ private String getPageSql(Page<?> page, String sql) { StringBuffer sqlBuffer = new StringBuffer(sql); if ( "mysql" .equalsIgnoreCase(dialect)) { return getMysqlPageSql(page, sqlBuffer); } else if ( "oracle" .equalsIgnoreCase(dialect)) { return getOraclePageSql(page, sqlBuffer); } return sqlBuffer.toString(); } /** * 獲取Mysql數據庫的分頁查詢語句 * @param page 分頁對象 * @param sqlBuffer 包含原sql語句的StringBuffer對象 * @return Mysql數據庫分頁語句 */ private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) { //計算第一條記錄的位置,Mysql中記錄的位置是從0開始的。 // System.out.println("page:"+page.getPage()+"-------"+page.getRows()); int offset = (page.getPage() - 1 ) * page.getRows(); sqlBuffer.append( " limit " ).append(offset).append( "," ).append(page.getRows()); return sqlBuffer.toString(); } /** * 獲取Oracle數據庫的分頁查詢語句 * @param page 分頁對象 * @param sqlBuffer 包含原sql語句的StringBuffer對象 * @return Oracle數據庫的分頁查詢語句 */ private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) { //計算第一條記錄的位置,Oracle分頁是通過rownum進行的,而rownum是從1開始的 int offset = (page.getPage() - 1 ) * page.getRows() + 1 ; sqlBuffer.insert( 0 , "select u.*, rownum r from (" ).append( ") u where rownum < " ).append(offset + page.getRows()); sqlBuffer.insert( 0 , "select * from (" ).append( ") where r >= " ).append(offset); //上面的Sql語句拼接之后大概是這個樣子: //select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16 return sqlBuffer.toString(); } /** * 攔截器對應的封裝原始對象的方法 */ public Object plugin(Object arg0) { // TODO Auto-generated method stub if (arg0 instanceof StatementHandler) { return Plugin.wrap(arg0, this ); } else { return arg0; } } /** * 設置注冊攔截器時設定的屬性 */ public void setProperties(Properties p) { } public String getDialect() { return dialect; } public void setDialect(String dialect) { this .dialect = dialect; } public String getPageSqlId() { return pageSqlId; } public void setPageSqlId(String pageSqlId) { this .pageSqlId = pageSqlId; } } |
xml配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!-- MyBatis 接口編程配置 --> < bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer" > <!-- basePackage指定要掃描的包,在此包之下的映射器都會被搜索到,可指定多個包,包與包之間用逗號或分號分隔--> < property name = "basePackage" value = "com.yidao.mybatis.dao" /> < property name = "sqlSessionFactoryBeanName" value = "sqlSessionFactory" /> </ bean > <!-- MyBatis 分頁攔截器--> < bean id = "paginationInterceptor" class = "com.mybatis.interceptor.PageInterceptor" > < property name = "dialect" value = "mysql" /> <!-- 攔截Mapper.xml文件中,id包含query字符的語句 --> < property name = "pageSqlId" value = ".*query$" /> </ bean > |
Page類
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
|
package com.yidao.utils; /**自己看看,需要什么字段加什么字段吧*/ public class Page { private Integer rows; private Integer page = 1 ; private Integer totalRecord; public Integer getRows() { return rows; } public void setRows(Integer rows) { this .rows = rows; } public Integer getPage() { return page; } public void setPage(Integer page) { this .page = page; } public Integer getTotalRecord() { return totalRecord; } public void setTotalRecord(Integer totalRecord) { this .totalRecord = totalRecord; } } |
ReflectHelper類
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
|
package com.yidao.utils; import java.lang.reflect.Field; import org.apache.commons.lang3.reflect.FieldUtils; public class ReflectHelper { public static Object getFieldValue(Object obj , String fieldName ){ if (obj == null ){ return null ; } Field targetField = getTargetField(obj.getClass(), fieldName); try { return FieldUtils.readField(targetField, obj, true ) ; } catch (IllegalAccessException e) { e.printStackTrace(); } return null ; } public static Field getTargetField(Class<?> targetClass, String fieldName) { Field field = null ; try { if (targetClass == null ) { return field; } if (Object. class .equals(targetClass)) { return field; } field = FieldUtils.getDeclaredField(targetClass, fieldName, true ); if (field == null ) { field = getTargetField(targetClass.getSuperclass(), fieldName); } } catch (Exception e) { } return field; } public static void setFieldValue(Object obj , String fieldName , Object value ){ if ( null == obj){ return ;} Field targetField = getTargetField(obj.getClass(), fieldName); try { FieldUtils.writeField(targetField, obj, value) ; } catch (IllegalAccessException e) { e.printStackTrace(); } } } |
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。