搜档网
当前位置:搜档网 › struts2 spring3 mybatis3拦截器分页技术

struts2 spring3 mybatis3拦截器分页技术

struts2 spring3 mybatis3拦截器分页技术
struts2 spring3 mybatis3拦截器分页技术

struts2 spring3.2.4 mybatis-3.2.3 通用分页(不同数据库)拦截器

mybatis没有根据数据库方言进行分页封装,但是提供了拦截器,我们可以在拦截其中获取数据库方言(数据库方言通过配置文件获取)和查询sql,根据数据库方言进行翻页分装,在拦截其中有两种实现方式:

1. 拦截器中计算总数,通过jdbc的方式,再封装翻页sql

2.计算总数在应用层计算,拦截器中只通过数据库方言进行查询sql的封装本人认为第一种方式可能会影响到性能,下面是通过第二种方法实现分页

1、拦截器类

[java]view plaincopyprint?

1.package com.zhou.bean;

2.

3.import https://www.sodocs.net/doc/ae11567884.html,ng.reflect.Field;

4.import java.sql.Connection;

5.import java.sql.PreparedStatement;

6.import java.sql.ResultSet;

7.import java.sql.SQLException;

8.import java.util.List;

9.import java.util.Properties;

10.

11.import org.apache.ibatis.executor.parameter.ParameterHandler;

12.import org.apache.ibatis.executor.statement.RoutingStatementHandler;

13.import org.apache.ibatis.executor.statement.StatementHandler;

14.import org.apache.ibatis.mapping.BoundSql;

15.import org.apache.ibatis.mapping.MappedStatement;

16.import org.apache.ibatis.mapping.ParameterMapping;

17.import org.apache.ibatis.plugin.Interceptor;

18.import org.apache.ibatis.plugin.Intercepts;

19.import org.apache.ibatis.plugin.Invocation;

20.import org.apache.ibatis.plugin.Plugin;

21.import org.apache.ibatis.plugin.Signature;

22.import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;

23.

24./**

25.* @author zhouzhenlong

26.* @date 2012-11-20 下午04:33:35

27.* @description 不在拦截器中计算总数,影响效率

28.* @version V1.0

29.*

30.* 分页拦截器,用于拦截需要进行分页查询的操作,然后对其进行分页处理。利用拦截器实

现Mybatis分页的原理:

31.* 要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象

32.* ,Mybatis在执行Sql语句前就会产生一个包含Sql语句的Statement对象,而且对应的

Sql语句

33.* 是在Statement之前产生的,所以我们就可以在它生成Statement之前对用来生成

Statement的Sql语句下手

34.* 。在Mybatis中Statement语句是通过RoutingStatementHandler对象的

35.* prepare方法生成的。所以利用拦截器实现Mybatis分页的一个思路就是拦截

StatementHandler接口的prepare方法

36.* ,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,之后再调用

37.* StatementHandler对象的prepare方法,即调用invocation.proceed()。

38.* 对于分页而言,在拦截器里面我们还需要做的一个操作就是统计满足当前条件的记录一共

有多少

39.* ,这是通过获取到了原始的Sql语句后,把它改为对应的统计语句再利用Mybatis封装好

的参数和设

40.* 置参数的功能把Sql语句中的参数进行替换,之后再执行查询记录数的Sql语句进行总记

录数的统计。

41.*

42.*/

43.@Intercepts({ @Signature(method = "prepare", type = StatementHandler.class,

args = { Connection.class }) })

44.public class PageInterceptor implements Interceptor {

45.

46.private String databaseType;// 数据库类型,不同的数据库有不同的分页方法

47.

48./**

49.* 拦截后要执行的方法

50.*/

51.public Object intercept(Invocation invocation) throws Throwable {

52.// 对于StatementHandler其实只有两个实现类,一个是RoutingStatementHandler,另

一个是抽象类BaseStatementHandler,

53.// BaseStatementHandler有三个子类,分别是SimpleStatementHandler,

PreparedStatementHandler和CallableStatementHandler,

54.// SimpleStatementHandler是用于处理Statement的,PreparedStatementHandler

是处理PreparedStatement的,而CallableStatementHandler是

55.// 处理CallableStatement的。Mybatis在进行Sql语句处理的时候都是建立的

RoutingStatementHandler,而在RoutingStatementHandler里面拥有一个

56.// StatementHandler类型的delegate属性,RoutingStatementHandler会依据

Statement的不同建立对应的BaseStatementHandler,即SimpleStatementHandler、57.// PreparedStatementHandler或CallableStatementHandler,在

RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的

delegate对应的方法。

58.// 我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截

StatementHandler接口的prepare方法,又因为Mybatis只有在建立

RoutingStatementHandler的时候

59.// 是通过Interceptor的plugin方法进行包裹的,所以我们这里拦截到的目标对象肯定是

RoutingStatementHandler对象。

60.final RoutingStatementHandler handler = (RoutingStatementHandler)

invocation.getTarget();

61.// 通过反射获取到当前RoutingStatementHandler对象的delegate属性

62.final StatementHandler delegate = (StatementHandler)

ReflectUtil.getFieldValue(handler, "delegate");

63.// 获取到当前StatementHandler的

64.// boundSql,这里不管是调用handler.getBoundSql()还是直接调用

delegate.getBoundSql()结果是一样的,因为之前已经说过了

65.// RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的

delegate对应的方法。

66.final BoundSql boundSql = delegate.getBoundSql();

67.// 拿到当前绑定Sql的参数对象,就是我们在调用对应的Mapper映射语句时所传入的参数

对象

68.final Object obj = boundSql.getParameterObject();

69.// 这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。

70.if (obj instanceof SearchPageUtil) {

71.final SearchPageUtil page = (SearchPageUtil) obj;

72.// 通过反射获取delegate父类BaseStatementHandler的mappedStatement属性

73.// MappedStatement mappedStatement = (MappedStatement)

74.// ReflectUtil.getFieldValue(delegate, "mappedStatement");

75.// 拦截到的prepare方法参数是一个Connection对象

76.// Connection connection = (Connection) invocation.getArgs()[0];

77.// 获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句

78.final String sql = boundSql.getSql();

79.// 给当前的page参数对象设置总记录数影响性能

80.// this.setTotalRecord(page, mappedStatement, connection);

81.// 获取分页Sql语句

82.final String pageSql = this.getPageSql(page, sql);

83.// 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句

84.ReflectUtil.setFieldValue(boundSql, "sql", pageSql);

85.}

86.return invocation.proceed();

87.}

88.

89./**

90.* 拦截器对应的封装原始对象的方法

91.*/

92.public Object plugin(Object target) {

93.return Plugin.wrap(target, this);

94.}

95.

96./**

97.* 设置注册拦截器时设定的属性

98.*/

99.public void setProperties(Properties properties) {

100.this.databaseType = properties.getProperty("databaseType"); 101.}

102.

103./**

104.* 根据page对象获取对应的分页查询Sql语句,这里只做了两种数据库类型,Mysql和Oracle 其它的数据库都没有进行分页

105.*

106.* @param page

107.* 分页对象

108.* @param sql

109.* 原sql语句

110.* @return

111.*/

112.private String getPageSql(SearchPageUtil page, String sql) {

113.final StringBuffer sqlBuffer = new StringBuffer(sql);

114.if ("mysql".equalsIgnoreCase(databaseType)) {

115.return getMysqlPageSql(page, sqlBuffer);

116.} else if ("oracle".equalsIgnoreCase(databaseType)) {

117.return getOraclePageSql(page, sqlBuffer);

118.}

119.return sqlBuffer.toString();

120.}

121.

122./**

123.* 获取Mysql数据库的分页查询语句

124.*

125.* @param page

126.* 分页对象

127.* @param sqlBuffer

128.* 包含原sql语句的StringBuffer对象

129.* @return Mysql数据库分页语句

130.*/

131.private String getMysqlPageSql(SearchPageUtil page, StringBuffer sqlBuffer) {

132.// 计算第一条记录的位置,Mysql中记录的位置是从0开始的。

133.// int offset = (page.getPage().getPageIndex() - 1) *

134.// page.getPageSize();

135.sqlBuffer.append(" limit

").append(page.getStartRow()).append(",").append(page.getPageSize());

136.return sqlBuffer.toString();

137.}

138.

139./**

140.* 获取Oracle数据库的分页查询语句

141.*

142.* @param page

143.* 分页对象

144.* @param sqlBuffer

145.* 包含原sql语句的StringBuffer对象

146.* @return Oracle数据库的分页查询语句

147.*/

148.private String getOraclePageSql(SearchPageUtil page, StringBuffer sqlBuffer) {

149.// 计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的

150.final int offset = (page.getPage().getPageIndex() - 1) * page.getPageSize() + 1;

151.sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ")

152..append(offset + page.getPageSize());

153.sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);

154.// 上面的Sql语句拼接之后大概是这个样子:

155.// select * from (select u.*, rownum r from (select * from t_user) u 156.// where rownum < 31) where r >= 16

157.return sqlBuffer.toString();

158.}

159.

160./**

161.* 给当前的参数对象page设置总记录数

162.*

163.* @param page

164.* Mapper映射语句对应的参数对象

165.* @param mappedStatement

166.* Mapper映射语句

167.* @param connection

168.* 当前的数据库连接

169.*/

170.private void setTotalRecord(SearchPageUtil page, MappedStatement mappedStatement, Connection connection) {

171.// 获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。

172.// delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。

173.final BoundSql boundSql = mappedStatement.getBoundSql(page);

174.// 获取到我们自己写在Mapper映射语句中对应的Sql语句

175.final String sql = boundSql.getSql();

176.// 通过查询Sql语句获取到对应的计算总记录数的sql语句

177.final String countSql = this.getCountSql(sql);

178.// 通过BoundSql获取对应的参数映射

179.final List parameterMappings =

boundSql.getParameterMappings();

180.// 利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。

181.final BoundSql countBoundSql = new

BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, 182.page);

183.// 通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象

184.final ParameterHandler parameterHandler = new

DefaultParameterHandler(mappedStatement, page, countBoundSql);

185.// 通过connection建立一个countSql对应的PreparedStatement对象。

186.PreparedStatement pstmt = null;

187.ResultSet rs = null;

188.try {

189.pstmt = connection.prepareStatement(countSql);

190.// 通过parameterHandler给PreparedStatement对象设置参数

191.parameterHandler.setParameters(pstmt);

192.// 之后就是执行获取总记录数的Sql语句和获取结果了。

193.rs = pstmt.executeQuery();

194.if (rs.next()) {

195.final int totalRecord = rs.getInt(1);

196.// 给当前的参数page对象设置总记录数

197.page.getPage().setRowTotal(totalRecord);

198.}

199.} catch (SQLException e) {

200. e.printStackTrace();

201.} finally {

202.try {

203.if (rs != null)

204.rs.close();

205.if (pstmt != null)

206.pstmt.close();

207.} catch (SQLException e) {

208. e.printStackTrace();

209.}

210.}

211.}

212.

213./**

214.* 根据原Sql语句获取对应的查询总记录数的Sql语句

215.*

216.* @param sql

217.* @return

218.*/

219.private String getCountSql(String sql) {

220.final int index = sql.indexOf("from");

221.return "select count(*) " + sql.substring(index); 222.}

223.

224./**

225.* 利用反射进行操作的一个工具类

226.*

227.*/

228.private static class ReflectUtil {

229./**

230.* 利用反射获取指定对象的指定属性

231.*

232.* @param obj

233.* 目标对象

234.* @param fieldName

235.* 目标属性

236.* @return 目标属性的值

237.*/

238.public static Object getFieldValue(Object obj, String fieldName) { 239.Object result = null;

240.final Field field = ReflectUtil.getField(obj, fieldName);

241.if (field != null) {

242.field.setAccessible(true);

243.try {

244.result = field.get(obj);

245.} catch (IllegalArgumentException e) {

246.// TODO Auto-generated catch block

247. e.printStackTrace();

248.} catch (IllegalAccessException e) {

249.// TODO Auto-generated catch block

250. e.printStackTrace();

251.}

252.}

253.return result;

254.}

255.

256./**

257.* 利用反射获取指定对象里面的指定属性

258.*

259.* @param obj

260.* 目标对象

261.* @param fieldName

262.* 目标属性

263.* @return 目标字段

264.*/

265.private static Field getField(Object obj, String fieldName) {

266.Field field = null;

267.for (Class clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {

268.try {

269.field = clazz.getDeclaredField(fieldName);

270.break;

271.} catch (NoSuchFieldException e) {

272.// 这里不用做处理,子类没有该字段可能对应的父类有,都没有就返回null。273.}

274.}

275.return field;

276.}

277.

278./**

279.* 利用反射设置指定对象的指定属性为指定的值

280.*

281.* @param obj

282.* 目标对象

283.* @param fieldName

284.* 目标属性

285.* @param fieldValue

286.* 目标值

287.*/

288.public static void setFieldValue(Object obj, String fieldName, String fieldValue) {

289.final Field field = ReflectUtil.getField(obj, fieldName);

290.if (field != null) {

291.try {

292.field.setAccessible(true);

293.field.set(obj, fieldValue);

294.} catch (IllegalArgumentException e) {

295.// TODO Auto-generated catch block

296. e.printStackTrace();

297.} catch (IllegalAccessException e) {

298.// TODO Auto-generated catch block

299. e.printStackTrace();

300.}

301.}

302.}

303.}

304.

305.}

2、拦截器配置

在配置文件mybatis-config.xml中增加

[html]view plaincopyprint?

1.

2.

3.

4.

5.

6.

3、应用

在配置文件test-mapper.xml去掉查询方法getList的翻页语句limit #{startRow},#{pageSize},部署到tomcat下访问http://localhost:8080/Test/test.jsp点击列表页面按钮,进入列表页面,翻页功能正常使用。

SCME_STRUTS2试卷

--------------------------------------装--------------------------------------订------------------------------线---------------------------------------- **学院课程考试试卷 课程名称:《使用Struts2开发基于MVC设计模式的企业级应用》(A)卷 年级:班级: 姓名:_______________ 学号:_________________ 考试(考查) 闭卷 选择题(每题2分,共计100分) 1.在控制器类中一般需要添加相应属性的( A )和(C )。(选两项) A.setter方法 B.as方法 C.getter方法 D.is方法 2.业务控制器需要在( B )配置文件中进行配置 A.web.xml B.struts.xml C.struts2.xml D.webwork.xml 3.不属于Struts 2表单标签库的是( D )。 A. B. C. D.