读书人

mybatis分页兑现1

发布时间: 2012-09-12 09:21:30 作者: rapoo

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

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

StatementHandlerInterceptor.java 主要是负责修改sql,在rapid-framework,其实就只有这样一个Interceptor,但如果要设置参数,就不止了。

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }),@Signature(type = StatementHandler.class, method = "parameterize", args = { Statement.class }) })public class StatementHandlerInterceptor extendsAbstractStatementHandlerInterceptor 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;}@Overridepublic 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();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}}


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


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

ResultSetHandlerInterceptor.java 负责修正RowBounds
@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了一次。后面改进。

其他辅助类

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);}}

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);}

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.
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语句。

<sql id="findAllWhere">where mm = #{love}</sql><select id="findAll" resultMap="helloResultMap">select * from HELLO<include refid="findAllWhere"/>order by create_time</select><select id="findAllCount" resultType="long">select count(*) from HELLO<include refid="findAllWhere"/></select>


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

读书人网 >软件架构设计

热点推荐