下面是我的实现,但还是觉得有些复杂。
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) { } }
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来实现。