mybatis分页实现1

mybatis分页实现1
最近开始尝试用mybatis,比较郁闷其对分页的实现,看了网上一些例子,还是不满意。最好的应该是rapid-framework里的实现了,但没实现分页参数的参数化,参数都是硬编码在sql里,在oracle这样的数据库里,性能影响还是有的。

下面是我的实现,但还是觉得有些复杂。

StatementHandlerInterceptor.java 主要是负责修改sql,在rapid-framework,其实就只有这样一个Interceptor,但如果要设置参数,就不止了。
Java代码 复制代码 收藏代码
  1. @Intercepts({ 
  2.         @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }), 
  3.         @Signature(type = StatementHandler.class, method = "parameterize", args = { Statement.class }) }) 
  4. public class StatementHandlerInterceptor extends 
  5.         AbstractStatementHandlerInterceptor implements Interceptor { 
  6.      
  7.     private Object prepare(Invocation invocation) throws Throwable { 
  8.         StatementHandler statement = getStatementHandler(invocation); 
  9.          
  10.         if (statement instanceof SimpleStatementHandler  
  11.                 || statement instanceof PreparedStatementHandler) { 
  12.  
  13.             RowBounds rowBounds = getRowBounds(statement); 
  14.          
  15.             if (hasBounds(rowBounds)) { 
  16.                 BoundSql boundSql = statement.getBoundSql(); 
  17.                 String sql = boundSql.getSql(); 
  18.                  
  19.                 if (statement instanceof SimpleStatementHandler) { 
  20.                     sql = dialect.getLimitString(sql, rowBounds.getOffset(), 
  21.                             rowBounds.getLimit()); 
  22.                 } 
  23.                 else if (statement instanceof PreparedStatementHandler) { 
  24.                     sql = dialect.getLimitString(sql, rowBounds.getOffset() > 0); 
  25.                 } 
  26.                 FieldUtils.setFieldValue(boundSql, "sql", sql); 
  27.             } 
  28.         } 
  29.  
  30.         return invocation.proceed(); 
  31.     } 
  32.      
  33.     private Object parameterize(Invocation invocation) throws Throwable { 
  34.         Statement statement = (Statement) invocation.getArgs()[0]; 
  35.  
  36.         Object rtn = invocation.proceed(); 
  37.  
  38.         if (statement instanceof PreparedStatement) { 
  39.             PreparedStatement ps = (PreparedStatement) statement; 
  40.  
  41.             StatementHandler statementHandler = getStatementHandler(invocation); 
  42.             RowBounds rowBounds = getRowBounds(statementHandler); 
  43.  
  44.             if (hasBounds(rowBounds)) { 
  45.                 BoundSql boundSql = statementHandler.getBoundSql(); 
  46.                 int parameterSize = boundSql.getParameterMappings().size(); 
  47.                 dialect.setLimitParamters(ps, parameterSize, 
  48.                         rowBounds.getOffset(), rowBounds.getLimit()); 
  49.             } 
  50.         } 
  51.         return rtn; 
  52.     } 
  53.  
  54.     @Override 
  55.     public Object intercept(Invocation invocation) throws Throwable { 
  56.         Method m = invocation.getMethod(); 
  57.         if ("prepare".equals(m.getName())) { 
  58.             return prepare(invocation); 
  59.         } 
  60.         else if ("parameterize".equals(m.getName())) { 
  61.             return parameterize(invocation); 
  62.         } 
  63.         return invocation.proceed(); 
  64.     } 
  65.  
  66.     @Override 
  67.     public Object plugin(Object target) { 
  68.         return Plugin.wrap(target, this); 
  69.     } 
  70.  
  71.     @Override 
  72.     public void setProperties(Properties properties) { 
  73.     } 
  74.  
@Intercepts({
		@Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }),
		@Signature(type = StatementHandler.class, method = "parameterize", args = { Statement.class }) })
public class StatementHandlerInterceptor extends
		AbstractStatementHandlerInterceptor implements Interceptor {
	
	private Object prepare(Invocation invocation) throws Throwable {
		StatementHandler statement = getStatementHandler(invocation);
		
		if (statement instanceof SimpleStatementHandler 
				|| statement instanceof PreparedStatementHandler) {

			RowBounds rowBounds = getRowBounds(statement);
		
			if (hasBounds(rowBounds)) {
				BoundSql boundSql = statement.getBoundSql();
				String sql = boundSql.getSql();
				
				if (statement instanceof SimpleStatementHandler) {
					sql = dialect.getLimitString(sql, rowBounds.getOffset(),
							rowBounds.getLimit());
				}
				else if (statement instanceof PreparedStatementHandler) {
					sql = dialect.getLimitString(sql, rowBounds.getOffset() > 0);
				}
				FieldUtils.setFieldValue(boundSql, "sql", sql);
			}
		}

		return invocation.proceed();
	}
	
	private Object parameterize(Invocation invocation) throws Throwable {
		Statement statement = (Statement) invocation.getArgs()[0];

		Object rtn = invocation.proceed();

		if (statement instanceof PreparedStatement) {
			PreparedStatement ps = (PreparedStatement) statement;

			StatementHandler statementHandler = getStatementHandler(invocation);
			RowBounds rowBounds = getRowBounds(statementHandler);

			if (hasBounds(rowBounds)) {
				BoundSql boundSql = statementHandler.getBoundSql();
				int parameterSize = boundSql.getParameterMappings().size();
				dialect.setLimitParamters(ps, parameterSize,
						rowBounds.getOffset(), rowBounds.getLimit());
			}
		}
		return rtn;
	}

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		Method m = invocation.getMethod();
		if ("prepare".equals(m.getName())) {
			return prepare(invocation);
		}
		else if ("parameterize".equals(m.getName())) {
			return parameterize(invocation);
		}
		return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
	}

}


prepare和parameterize是在两个不同点调用的,一个负责生成Statement,一个负责设置参数。本来是想分开写成两个Interceptor,但后来发现多次针对同一个类进行拦截,是无法获取其内部属性的,Plugin.wrap(target, this)是生成一个代理类,下一个拦截器就是在这个基础上继续做代理。这个和struts2之类的拦截器是不一样的。


好像这样是可以了,但实际还是不够的。因为StatementHandler和FastResultSetHandler是同时生成,而且都接引用了RowBounds参数,在FastResultSetHandler还是分页形式存在,如果不修正,会导致mybatis用游标移动后取数据。

ResultSetHandlerInterceptor.java 负责修正RowBounds
Java代码 复制代码 收藏代码
  1. @Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }) }) 
  2. public class ResultSetHandlerInterceptor implements Interceptor { 
  3.  
  4.     public Object intercept(Invocation invocation) throws Throwable { 
  5.         ResultSetHandler resultSet = (ResultSetHandler) invocation.getTarget(); 
  6.  
  7.         // 不用浪费性能做属性存在判断 
  8.         RowBounds rowBounds = (RowBounds) FieldUtils.getFieldValue(resultSet, 
  9.                 "rowBounds"); 
  10.  
  11.         if (rowBounds.getLimit() > 0 
  12.                 && rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT) { 
  13.             // 强制不允许游标分页 
  14.             FieldUtils.setFieldValue(resultSet, "rowBounds", RowBounds.DEFAULT); 
  15.         } 
  16.         return invocation.proceed(); 
  17.     } 
  18.  
  19.     public Object plugin(Object target) { 
  20.         return Plugin.wrap(target, this); 
  21.     } 
  22.  
  23.     public void setProperties(Properties properties) { 
  24.     } 
@Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }) })
public class ResultSetHandlerInterceptor implements Interceptor {

	public Object intercept(Invocation invocation) throws Throwable {
		ResultSetHandler resultSet = (ResultSetHandler) invocation.getTarget();

		// 不用浪费性能做属性存在判断
		RowBounds rowBounds = (RowBounds) FieldUtils.getFieldValue(resultSet,
				"rowBounds");

		if (rowBounds.getLimit() > 0
				&& rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT) {
			// 强制不允许游标分页
			FieldUtils.setFieldValue(resultSet, "rowBounds", RowBounds.DEFAULT);
		}
		return invocation.proceed();
	}

	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	public void setProperties(Properties properties) {
	}
}


不过现在想想,用反射直接修改RowBounds里的属性,不就可以不用ResultSetHandlerInterceptor了?哎!又SB了一次。后面改进。

其他辅助类

Java代码 复制代码 收藏代码
  1. public abstract class AbstractStatementHandlerInterceptor implements Interceptor, InitializingBean { 
  2.      
  3.     private Class<Dialect> dialectClass; 
  4.  
  5.     public void setDialectClass(Class<Dialect> dialectClass) { 
  6.         this.dialectClass = dialectClass; 
  7.     } 
  8.      
  9.     protected Dialect dialect; 
  10.  
  11.     public void setDialect(Dialect dialect) { 
  12.         this.dialect = dialect; 
  13.     } 
  14.      
  15.     public void afterPropertiesSet() throws Exception { 
  16.         setDialect(dialectClass.newInstance()); 
  17.     } 
  18.  
  19.     protected StatementHandler getStatementHandler(Invocation invocation) { 
  20.         StatementHandler statement = (StatementHandler) invocation.getTarget(); 
  21.         if (statement instanceof RoutingStatementHandler) { 
  22.             statement = (StatementHandler) FieldUtils.getFieldValue(statement, 
  23.                     "delegate"); 
  24.         } 
  25.         return statement; 
  26.     } 
  27.      
  28.     protected RowBounds getRowBounds(StatementHandler statement) { 
  29.         return (RowBounds) FieldUtils.getFieldValue(statement, "rowBounds"); 
  30.     } 
  31.      
  32.     protected boolean hasBounds(RowBounds rowBounds) { 
  33.         return (rowBounds != null  
  34.                 && rowBounds.getLimit() > 0  
  35.                 && rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT); 
  36.     } 
  37.  
public abstract class AbstractStatementHandlerInterceptor implements Interceptor, InitializingBean {
	
	private Class<Dialect> dialectClass;

	public void setDialectClass(Class<Dialect> dialectClass) {
		this.dialectClass = dialectClass;
	}
	
	protected Dialect dialect;

	public void setDialect(Dialect dialect) {
		this.dialect = dialect;
	}
	
	public void afterPropertiesSet() throws Exception {
		setDialect(dialectClass.newInstance());
	}

	protected StatementHandler getStatementHandler(Invocation invocation) {
		StatementHandler statement = (StatementHandler) invocation.getTarget();
		if (statement instanceof RoutingStatementHandler) {
			statement = (StatementHandler) FieldUtils.getFieldValue(statement,
					"delegate");
		}
		return statement;
	}
	
	protected RowBounds getRowBounds(StatementHandler statement) {
		return (RowBounds) FieldUtils.getFieldValue(statement, "rowBounds");
	}
	
	protected boolean hasBounds(RowBounds rowBounds) {
		return (rowBounds != null 
				&& rowBounds.getLimit() > 0 
				&& rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT);
	}

}

Java代码 复制代码 收藏代码
  1. public interface Dialect { 
  2.      
  3.     public void setLimitParamters(PreparedStatement ps, int parameterSize, int offset, int limit) throws SQLException; 
  4.      
  5.     public String getLimitString(String sql, boolean hasOffset); 
  6.  
  7.     public String getLimitString(String sql, int offset, int limit); 
public interface Dialect {
	
	public void setLimitParamters(PreparedStatement ps, int parameterSize, int offset, int limit) throws SQLException;
	
	public String getLimitString(String sql, boolean hasOffset);

	public String getLimitString(String sql, int offset, int limit);
}

Java代码 复制代码 收藏代码
  1. public abstract class FieldUtils { 
  2.      
  3.     public static boolean hasField(Object target, String fieldName, Class<?> type) { 
  4.         return ReflectionUtils.findField(target.getClass(), fieldName, type) == null
  5.     } 
  6.  
  7.     public static Object getFieldValue(Object target, String fieldName) { 
  8.         Field field = ReflectionUtils.findField(target.getClass(), fieldName); 
  9.         ReflectionUtils.makeAccessible(field); 
  10.         return ReflectionUtils.getField(field, target); 
  11.     } 
  12.      
  13.     public static void setFieldValue(Object target, String fieldName, Object value) { 
  14.         Field field = ReflectionUtils.findField(target.getClass(), fieldName); 
  15.         ReflectionUtils.makeAccessible(field); 
  16.         ReflectionUtils.setField(field, target, value); 
  17.     } 
public abstract class FieldUtils {
	
	public static boolean hasField(Object target, String fieldName, Class<?> type) {
		return ReflectionUtils.findField(target.getClass(), fieldName, type) == null;
	}

	public static Object getFieldValue(Object target, String fieldName) {
		Field field = ReflectionUtils.findField(target.getClass(), fieldName);
		ReflectionUtils.makeAccessible(field);
		return ReflectionUtils.getField(field, target);
	}
	
	public static void setFieldValue(Object target, String fieldName, Object value) {
		Field field = ReflectionUtils.findField(target.getClass(), fieldName);
		ReflectionUtils.makeAccessible(field);
		ReflectionUtils.setField(field, target, value);
	}
}

ReflectionUtils是spring里的。

spring 集成用到的。增加interceptors集合,直接通过spring注入interceptor.
Java代码 复制代码 收藏代码
  1. public class MyBatisSessionFactoryBean extends SqlSessionFactoryBean { 
  2.      
  3.     private List<Interceptor> interceptors = Collections.emptyList(); 
  4.  
  5.     public void setInterceptors(List<Interceptor> interceptors) { 
  6.         this.interceptors = interceptors; 
  7.     } 
  8.  
  9.     protected SqlSessionFactory buildSqlSessionFactory() throws IOException, 
  10.             IllegalAccessException, InstantiationException { 
  11.         SqlSessionFactory factory = super.buildSqlSessionFactory(); 
  12.         Configuration config = factory.getConfiguration(); 
  13.         for (Interceptor interceptor : interceptors) { 
  14.             config.addInterceptor(interceptor); 
  15.         } 
  16.         return factory; 
  17.     } 
public class MyBatisSessionFactoryBean extends SqlSessionFactoryBean {
	
	private List<Interceptor> interceptors = Collections.emptyList();

	public void setInterceptors(List<Interceptor> interceptors) {
		this.interceptors = interceptors;
	}

	protected SqlSessionFactory buildSqlSessionFactory() throws IOException,
			IllegalAccessException, InstantiationException {
		SqlSessionFactory factory = super.buildSqlSessionFactory();
		Configuration config = factory.getConfiguration();
		for (Interceptor interceptor : interceptors) {
			config.addInterceptor(interceptor);
		}
		return factory;
	}
}


下一次改进方向,现在目前来讲,只是实现了sql能分页,但缺少count的集成。mybatis中两条语句是避免不了,虽然可以用include来避免重复的where语句。

Xml代码 复制代码 收藏代码
  1. <sql id="findAllWhere"> 
  2.     where mm = #{love} 
  3. </sql> 
  4.  
  5. <select id="findAll" resultMap="helloResultMap"> 
  6.     select * from HELLO 
  7.     <include refid="findAllWhere"/> 
  8.     order by create_time 
  9. </select> 
  10.  
  11. <select id="findAllCount" resultType="long"> 
  12.     select count(*) from HELLO 
  13.     <include refid="findAllWhere"/> 
  14. </select> 


下次希望是,能在调用findAll的时候,自动调用findAllCount,遵守命名约定或者用注释来提示。由于我打算完全不写dao层,只写xml文件和接口,需要用到SqlSession.getMapper自动生成代理。由于期望是自动调用count方法和返回Page这样的对象。 这个可能就要看,是再代理一层,还是修改代理的实现了。入手点可以是继承Configuration,然后覆盖mapperRegistry来实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值