物理分页
这次我们运用Mybatis拦截器来实现物理分页,后面会运用动态sql来实现,或者运用Map/CollectionUtils/StringUtils编写工具类来实现。oracle是运用的rownum,mysql是运用的limit offset,pagesize。代码中有大量注释,可以参考Mybatis基本原理一起阅读。后面,我们会根据一些实际开发需要,把物理分页功能的代码封装成jar包,以后直接调用就好了,比如Mybatis+Spring3的运行环境,可以采用Mybatis动态sql来实现分页,也可以采用Mybatis插件。
1、项目备份。一定要勤于备份。我们永远不知道我们什么时候,会把早上还跑得通的一个项目,到晚上都还找不到哪里被玩坏了。是的,我们在编写控制层的时候,交互使用了struts2和springmvc,想看看他们之间的差异,然后,就迷糊了,由于忘记备份struts2版本的,到了晚上就只好还原到昨天的去了。很晕很晕。
2、Mybatis插件类,实现拦截器,实现sql重写。每一个拦截器必须实现三个方法。
1 package com.dyl.util; 2 3 import java.lang.reflect.Field; 4 import java.sql.Connection; 5 import java.sql.PreparedStatement; 6 import java.sql.ResultSet; 7 import java.sql.SQLException; 8 import java.util.List; 9 import java.util.Map; 10 import java.util.Properties; 11 12 import javax.xml.bind.PropertyException; 13 14 import org.apache.ibatis.builder.xml.dynamic.ForEachSqlNode; 15 import org.apache.ibatis.executor.ErrorContext; 16 import org.apache.ibatis.executor.ExecutorException; 17 import org.apache.ibatis.executor.statement.BaseStatementHandler; 18 import org.apache.ibatis.executor.statement.RoutingStatementHandler; 19 import org.apache.ibatis.executor.statement.StatementHandler; 20 import org.apache.ibatis.mapping.BoundSql; 21 import org.apache.ibatis.mapping.MappedStatement; 22 import org.apache.ibatis.mapping.ParameterMapping; 23 import org.apache.ibatis.mapping.ParameterMode; 24 import org.apache.ibatis.plugin.Interceptor; 25 import org.apache.ibatis.plugin.Intercepts; 26 import org.apache.ibatis.plugin.Invocation; 27 import org.apache.ibatis.plugin.Plugin; 28 import org.apache.ibatis.plugin.Signature; 29 import org.apache.ibatis.reflection.MetaObject; 30 import org.apache.ibatis.reflection.property.PropertyTokenizer; 31 import org.apache.ibatis.session.Configuration; 32 import org.apache.ibatis.type.TypeHandler; 33 import org.apache.ibatis.type.TypeHandlerRegistry; 34 /** 35 * 拦截器签名 36 * 要拦截的目标类型是StatementHandler(注意:type只能配置成接口类型),拦截的方法是,名称为prepare,参数为Connection类型的方法。 37 * @author dyl 38 * @date 2014-7-12 39 */ 40 @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) }) 41 public class PagePlugin implements Interceptor { 42 /** 43 * Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的)。 44 * 45 * 每一个拦截器都必须实现的三个方法。 46 * (1)Object intercept(Invocation ivk),实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进, 47 * 也就是调用下一个拦截器拦截目标方法。 48 * (2)Object plugin(Object target),用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this)来完成的, 49 * 把目标target和拦截器this传给了包装函数。 50 * (3)void setProperties(Properties p),设置额外的参数,参数配置在拦截器的Properties节点里。 51 * 52 * Mybatis配置的插件,运行时发生。 53 * (1)所有可能被拦截的处理类都会生成一个代理。 54 * (2)处理类代理在执行对应方法时,判断要不要执行插件中的拦截方法。 55 * (3)执行插接中的拦截方法后,推进目标的执行。 56 */ 57 private static String dialect = ""; 58 private static String pageSqlId = ""; 59 /* 60 * intercept的实现 61 * 实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。 62 * @see org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin.Invocation) 63 */ 64 @SuppressWarnings("unchecked") 65 public Object intercept(Invocation ivk) throws Throwable { 66 /** 67 * StatementHandler的默认实现类是RoutingStatementHandler,因此拦截的实际对象是它。 68 * RoutingStatementHandler的主要功能是分发,它根据配置Statement类型创建真正执行数据库操作的StatementHandler,并将其保存到delegate属性里。 69 * 由于delegate是一个私有属性并且没有提供访问它的方法,因此需要借助ReflectHelper的帮忙。 70 * 通过ReflectHelper的封装后我们可以轻易的获得想要的属性。 71 */ 72 if (ivk.getTarget() instanceof RoutingStatementHandler) { 73 RoutingStatementHandler statementHandler = (RoutingStatementHandler) ivk.getTarget(); 74 BaseStatementHandler delegate = (BaseStatementHandler) ReflectHelper.getValueByFieldName(statementHandler, "delegate"); 75 MappedStatement mappedStatement = (MappedStatement) ReflectHelper.getValueByFieldName(delegate, "mappedStatement"); 76 77 // 只重写需要分页的sql语句。通过MappedStatement的ID匹配,默认重写以Page结尾的MappedStatement的sql。 78 if (mappedStatement.getId().matches(pageSqlId)) { 79 BoundSql boundSql = delegate.getBoundSql(); 80 Object parameterObject = boundSql.getParameterObject(); 81 if (parameterObject == null) { 82 throw new NullPointerException("parameterObject error"); 83 } else { 84 Connection connection = (Connection) ivk.getArgs()[0]; 85 String sql = boundSql.getSql(); 86 // 记录总记录数 87 String countSql = "select count(0) from (" + sql + ") myCount"; 88 // System.out.println("总数sql 语句:" + countSql); 89 PreparedStatement countStmt = connection.prepareStatement(countSql); 90 BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, 91 boundSql.getParameterMappings(), parameterObject); 92 // 重设分页参数里的总页数等 93 setParameters(countStmt, mappedStatement, countBS, parameterObject); 94 95 ResultSet rs = countStmt.executeQuery(); 96 int count = 0; 97 if (rs.next()) { 98 count = rs.getInt(1); 99 } 100 rs.close(); 101 countStmt.close(); 102 103 // 分页参数作为参数对象parameterObject的一个属性 104 PageInfo page = null; 105 if (parameterObject instanceof PageInfo) { 106 page = (PageInfo) parameterObject; 107 page.setTotalResult(count); 108 } else if (parameterObject instanceof Map) { 109 Map<String, Object> map = (Map<String, Object>) parameterObject; 110 page = (PageInfo) map.get("page"); 111 if (page == null) 112 page = new PageInfo(); 113 page.setTotalResult(count); 114 } else { 115 Field pageField = ReflectHelper.getFieldByFieldName(parameterObject, "page"); 116 if (pageField != null) { 117 page = (PageInfo) ReflectHelper.getValueByFieldName(parameterObject, "page"); 118 if (page == null) 119 page = new PageInfo(); 120 page.setTotalResult(count); 121 ReflectHelper.setValueByFieldName(parameterObject, "page", page); 122 } else { 123 throw new NoSuchFieldException(parameterObject.getClass().getName()); 124 } 125 } 126 // 重写sql 127 String pageSql = generatePageSql(sql, page); 128 System.out.println("page sql:" + pageSql); 129 ReflectHelper.setValueByFieldName(boundSql, "sql", pageSql); 130 } 131 } 132 } 133 // 将执行权交给下一个拦截器,完成调用链的推进。 134 return ivk.proceed(); 135 } 136 /** 137 * 重设分页参数里的总页数等 138 * @param ps 139 * @param mappedStatement 140 * @param boundSql 141 * @param parameterObject 142 * @throws SQLException 143 */ 144 private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) 145 throws SQLException { 146 ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); 147 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); 148 if (parameterMappings != null) { 149 Configuration configuration = mappedStatement.getConfiguration(); 150 TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); 151 MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject); 152 for (int i = 0; i < parameterMappings.size(); i++) { 153 ParameterMapping parameterMapping = parameterMappings.get(i); 154 if (parameterMapping.getMode() != ParameterMode.OUT) { 155 Object value; 156 String propertyName = parameterMapping.getProperty(); 157 PropertyTokenizer prop = new PropertyTokenizer(propertyName); 158 if (parameterObject == null) { 159 value = null; 160 } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { 161 value = parameterObject; 162 } else if (boundSql.hasAdditionalParameter(propertyName)) { 163 value = boundSql.getAdditionalParameter(propertyName); 164 } else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) 165 && boundSql.hasAdditionalParameter(prop.getName())) { 166 value = boundSql.getAdditionalParameter(prop.getName()); 167 if (value != null) { 168 value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length())); 169 } 170 } else { 171 value = metaObject == null ? null : metaObject.getValue(propertyName); 172 } 173 TypeHandler typeHandler = parameterMapping.getTypeHandler(); 174 if (typeHandler == null) { 175 throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " 176 + mappedStatement.getId()); 177 } 178 typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType()); 179 } 180 } 181 } 182 } 183 /** 184 * sql重写,在原始的sql语句上加入分页的参数,目前支持mysql和oracle两种数据库的分页。 185 * @param sql 186 * @param page 187 * @return 188 */ 189 private String generatePageSql(String sql, PageInfo page) { 190 if (page != null && (dialect != null || !dialect.equals(""))) { 191 StringBuffer pageSql = new StringBuffer(); 192 //StringBuilder pageSql = new StringBuilder(); 193 if ("mysql".equals(dialect)) { 194 //pageSql=generatePageSqlForMysql(sql,page); 195 pageSql.append(sql); 196 pageSql.append(" limit " + page.getCurrentResult() + "," + page.getShowCount()); 197 } else if ("oracle".equals(dialect)) { 198 //pageSql = generatePageSqlForOracle(sql, page); 199 pageSql.append("select * from (select tmp_tb.*,ROWNUM row_id from ("); 200 pageSql.append(sql); 201 pageSql.append(") tmp_tb where ROWNUM<="); 202 //pageSql.append(page.getCurrentResult() + page.getShowCount()); 203 pageSql.append(page.getCurrentPage()*page.getShowCount()); 204 pageSql.append(") where row_id>"); 205 //pageSql.append(page.getCurrentResult()); 206 pageSql.append((page.getCurrentPage() - 1) * page.getShowCount()); 207 } 208 return pageSql.toString(); 209 } else { 210 return sql; 211 } 212 } 213 /** 214 * mysql的分页实现 215 * @param sql 216 * @param page 217 * @return 218 */ 219 public StringBuilder generatePageSqlForMysql(String sql, PageInfo page) { 220 StringBuilder pageSql = new StringBuilder(100); 221 String beginrow = String.valueOf((page.getCurrentPage() - 1) * page.getShowCount()); 222 pageSql.append(sql); 223 pageSql.append(" limit " + beginrow + "," + page.getShowCount()); 224 return pageSql; 225 } 226 /** 227 * oracle的分页实现 228 * @param sql 229 * @param page 230 * @return 231 */ 232 public StringBuilder generatePageSqlForOracle(String sql, PageInfo page) { 233 StringBuilder pageSql = new StringBuilder(100); 234 String beginrow = String.valueOf((page.getCurrentPage() - 1) * page.getShowCount()); 235 String endrow = String.valueOf(page.getCurrentPage() * page.getShowCount()); 236 pageSql.append("select * from ( select temp.*, rownum row_id from ( "); 237 pageSql.append(sql); 238 pageSql.append(" ) temp where rownum <= ").append(endrow); 239 pageSql.append(") where row_id > ").append(beginrow); 240 return pageSql; 241 } 242 /** 243 * plugin的实现 244 * 用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this)来完成的,把目标target和拦截器this传给了包装函数。 245 */ 246 public Object plugin(Object target) { 247 // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数。 248 if (target instanceof StatementHandler) { 249 return Plugin.wrap(target, this); 250 } else { 251 return target; 252 } 253 } 254 /** 255 * 设置额外的参数,参数配置在拦截器的Properties节点里。 256 */ 257 public void setProperties(Properties p) { 258 dialect = p.getProperty("dialect"); 259 if (dialect == null || dialect.equals("")) { 260 try { 261 throw new PropertyException("dialect property is not found!"); 262 } catch (PropertyException e) { 263 e.printStackTrace(); 264 } 265 } 266 pageSqlId = p.getProperty("pageSqlId"); 267 if (dialect == null || dialect.equals("")) { 268 try { 269 throw new PropertyException("pageSqlId property is not found!"); 270 } catch (PropertyException e) { 271 e.printStackTrace(); 272 } 273 } 274 } 275 }