mybatis分页实现1
- 博客分类:
- Java Web
最近开始尝试用mybatis,比较郁闷其对分页的实现,看了网上一些例子,还是不满意。最好的应该是rapid-framework里的实现了,但没实现分页参数的参数化,参数都是硬编码在sql里,在oracle这样的数据库里,性能影响还是有的。
下面是我的实现,但还是觉得有些复杂。
StatementHandlerInterceptor.java 主要是负责修改sql,在rapid-framework,其实就只有这样一个Interceptor,但如果要设置参数,就不止了。
prepare和parameterize是在两个不同点调用的,一个负责生成Statement,一个负责设置参数。本来是想分开写成两个Interceptor,但后来发现多次针对同一个类进行拦截,是无法获取其内部属性的,Plugin.wrap(target, this)是生成一个代理类,下一个拦截器就是在这个基础上继续做代理。这个和struts2之类的拦截器是不一样的。
好像这样是可以了,但实际还是不够的。因为StatementHandler和FastResultSetHandler是同时生成,而且都接引用了RowBounds参数,在FastResultSetHandler还是分页形式存在,如果不修正,会导致mybatis用游标移动后取数据。
ResultSetHandlerInterceptor.java 负责修正RowBounds
不过现在想想,用反射直接修改RowBounds里的属性,不就可以不用ResultSetHandlerInterceptor了?哎!又SB了一次。后面改进。
其他辅助类
ReflectionUtils是spring里的。
spring 集成用到的。增加interceptors集合,直接通过spring注入interceptor.
下一次改进方向,现在目前来讲,只是实现了sql能分页,但缺少count的集成。mybatis中两条语句是避免不了,虽然可以用include来避免重复的where语句。
下次希望是,能在调用findAll的时候,自动调用findAllCount,遵守命名约定或者用注释来提示。由于我打算完全不写dao层,只写xml文件和接口,需要用到SqlSession.getMapper自动生成代理。由于期望是自动调用count方法和返回Page这样的对象。 这个可能就要看,是再代理一层,还是修改代理的实现了。入手点可以是继承Configuration,然后覆盖mapperRegistry来实现。
下面是我的实现,但还是觉得有些复杂。
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 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) {
- }
- }
@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
- @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) {
- }
- }
@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 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 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);
- }
- }
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;
- }
- }
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来实现。