比较适用于在分页时候进行拦截。对分页的SQL语句通过封装处理,处理成不同的分页sql。
实用性比较强。
- 
      
 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配置:
- 
      
 <!– 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类
- 
      
 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类
    
   
- 
      
 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();
- }
- }
- 
      }
 
 

