[ECSide文档] ECSide基于数据库的分页、排序、过滤的实现
fins
2007-06-01
首先ecside展现列表、排序、过滤(该三种操作以下简称为 RSF )的实现原理完全和原版EC一样,
如果您对原版EC的retrieveRowsCallback、sortRowsCallback、filterRowsCallback 非常熟悉,那么可以忽略此文. 先来简单介绍一下RSF操作方式. ecside对数据的展现操作有三种:分页展现,按列排序(目前只支持单列),按列过滤(支持多列联合过滤) (该三种操作以下简称为 RSF ) ecside提供了两种方式来实现RSF操作 : 基于java collection层 和 基于数据库层,下面先简单介绍一下原理: ========================================================= 一:基于java collection层: 这是ec的默认实现方式, 最简单易用. 您要做的就是将整个列表所要展现的全部数据放入collection 内,并交给EC来处理. 其中RSF操作,全部由EC在内存中完成,由于您已经将全部数据放入了collection中, 所以排序 过滤都是基于全部数据的. 您要在DAO中做的就是一个 查询操作,SQL语句中不需要加入 关于排序 分页 过滤的代码. 这种方式的优点非常明显:实现简单. 缺点同样明显,而且在很大程度上是致命的: 数据量大的时候速度慢,而且很可能outofmemery. 这时候我们就需要第二种方式了: 方式一您所要做的工作: 1 通过DAO,查询出所有的数据,放入collection内 2 将collection传给列表页面 二:基于数据库层: 在这种方式下,EC的角色发生了一点点变化. 此时,EC负责把 collection 里的内容展现出来, 同时会向您提供RSF相关的参数. 而这些参数需要您自己手动取得 并传入到DAO中(当然EC提供了很多方便的方法来帮助您取得这些参数), 具体功能的实现需要您自己在DAO中组织相应的SQL语句. 方式二您所要做的工作: 1 查询出所有的数据的总数 2 取得一个ECSide提供的Limit对象 3 在该对象的帮助下取得RSF操作的相关信息(如 数据的起止行数,排序的列和顺序,过滤的列和内容) 4 将RSF操作的相关信息传入DAO内,来进行SQL语句的拼装(或者其他的操作,如使用ORM工具时) 5 通过DAO,查询出当前页所要显示的数据,放入collection内 6 将collection传给列表页面 这种方式的优缺点正好和方式一相反. 关于两种方式的配置可以看一下 ecside.properties 文件中的下列内容 table.filterRowsCallback.process=org.extremecomponents.table.callback.ProcessRowsCallback table.filterRowsCallback.limit=org.extremecomponents.table.callback.LimitCallback table.sortRowsCallback.process=org.extremecomponents.table.callback.ProcessRowsCallback table.sortRowsCallback.limit=org.extremecomponents.table.callback.LimitCallback table.retrieveRowsCallback.process=org.extremecomponents.table.callback.ProcessRowsCallback table.retrieveRowsCallback.limit=org.extremecomponents.table.callback.LimitCallback 其中 ProcessRowsCallback 采用了方式一 ,LimitCallback采用了方式二 而table.retrieveRowsCallback.default会告诉EC您默认使用的是哪个. 同样您也可以在 ec:table 标签里 指定 <ec:table filterRowsCallback="process/limit" sortRowsCallback="process/limit" retrieveRowsCallback="process/limit" ... > 大家可能还看到了 org.ecside.table.callback.CommonLimitCallback 这个是我自己随意组合出来的一个rowcallback 他在执行分页的时候,使用了方式二,而排序 过滤的时候使用了方式一 所以,大家可能会发现,DEMO中的排序 过滤方法只对当前页有效. 我这么做其实是一种偷懒的方式,但多数情况下,正如网友所说"只排当页数据毫无意义",您们说的没错,的确如此:) ========================================== 下面看一个简单的例子: (以 struts + dao 的方式演示,例子中采用的是ECSide 2.0 RC1版本) jsp代码 <ec:table retrieveRowsCallback="limit" sortRowsCallback="limit" filterRowsCallback="limit" ...... > ...... </ec:table> 在上面的JSP代码中,指定了所有的RSF操作都使用基于数据库层的. action的代码 //默认每页显示的记录条数 protected static int DEFAULT_PAGE_SIZE = 20; // 数据库端分页,适合数据量较大的情况 public ActionForward doQuery(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { UserInfoDAO userInfoDAO=(UserInfoDAO)getBean("userInfoDAO"); // 当列表的分页 过滤 排序等操作是基于数据库时,必须要用到Limit对象. // 注意,当页面有多个ec的时候,需要使用带 tableId参数的同名方法. //Limit limit=RequestUtils.getLimit(request,"ecGird的Id"); Limit limit=RequestUtils.getLimit(request); // 基于数据库的排序. // ECSide会帮助开发人员取得排序的相关信息:当前是按哪个(目前只支持单列排序)column排序的,以及排序的方式desc或asc, // 这个信息以key-value方式存放在map里. // 但是至于如果处理这些信息(如组装成sql语句),则是由开发人员自己在DAO里完成的. Sort sort=limit.getSort(); Map sortValueMap = sort.getSortValueMap(); // 基于数据库过滤. // ECSide会帮助开发人员取得过滤的相关信息:当前是对哪些column进行过滤,以及过滤的内容是什么,这个信息以key-value方式存放在map里. // 但是至于如果处理这些信息(如组装成sql语句),则是由开发人员自己在DAO里完成的. FilterSet filterSet =limit.getFilterSet(); Map filterPropertyMap=filterSet.getPropertyValueMap(); // 在本例中, sort 和 filter 相关信息将被传入 DAO,用于拼装sql语句. // 其实,此时的排序 过滤就和我们以前的传统的查询非常类似:从查询页面取得查询条件,传入DAO. // RequestUtils.getTotalRowsFromRequest(request);是一个工具类,用来从ECSIDE的列表中取得上次计算出的总行数 // 如果您不希望每次翻页都重新计算总行数,那么建议参考下面几行代码的做法. int totalRows = RequestUtils.getTotalRowsFromRequest(request); if (totalRows < 0) { // TODO : userInfoDAO.getUserInfoNumber()为能够取得总行数的方法,请替换为实际的实现。 // 取得记录总条数时,不要忘了把filter作为参数传入,因为要取得的总行数也是要接受条件限制的. totalRows = userInfoDAO.getUserInfoNumber(filterPropertyMap); } // DEFAULT_PAGE_SIZE ==0 时, 每页记录数会使用 properties文件内的默认设置 // DEFAULT_PAGE_SIZE <0 时, 每页记录数会等于全部记录数 limit.setRowAttributes(totalRows, DEFAULT_PAGE_SIZE); //取得当前要查询的页面的记录起止行号 // offset表示数据编号的起始号. ORACLE数据库一般是从1开始的,HSQLDB是从0开始,默认是从0开始计数,在这里我们使用从0开始. int offset=0; int[] rowStartEnd =new int[] { limit.getRowStart() + offset, limit.getRowEnd() + offset }; // TODO : userInfoDAO.getSomeUserInfo(rowStartEnd[0], rowStartEnd[1]) // 为查询记录的方法,请替换为实际的实现。rowStartEnd[0], rowStartEnd[1]为起止行 // rowStartEnd[0], rowStartEnd[1] 左闭 右开 List rslist = userInfoDAO.getSomeUserInfo(rowStartEnd[0], rowStartEnd[1],sortValueMap,filterPropertyMap); request.setAttribute("recordList", rslist); // 字典数据. 一个Map,存放的是 "编号" 到 "显示的文字" 的映射 request.setAttribute("GENDER_MAP", CommonDictionary.GENDER); request.setAttribute("USERROLE_MAP", CommonDictionary.USERROLE); return mapping.findForward("listPage"); } 其实上面的代码和代码里的注释已经说的比较明白了. DAO里的代码 public List getSomeUserInfo(int startRow,int endRow,Map sortValueMap,Map filterPropertyMap){ StringBuffer bufSql = new StringBuffer(); int size=endRow-startRow; // 使用传统JDBC时,根据不同条件拼装不同的SQL一向是非常恼人的事情. // ECSide当然不能够帮助您解决这个问题. // 再次重申一遍,当翻页 过滤 排序 均基于数据库时,ECSide只是能够帮助开发者在后台更方便的取得操作相关的数据. // 而如何使用他们,仍然需要开发人员自己来决定. bufSql.append("SELECT * FROM "); StringBuffer whereSql = new StringBuffer(" WHERE 1=1 "); if (filterPropertyMap!=null && !filterPropertyMap.isEmpty()){ // 根据过滤条件进行sql语句的拼装. // 在本例中,只有 USERROLE USERNAME GENDER 是可以进行过滤的. // 在这里偷下懒,就不用 PreparedStatement 的方式了,而是直接把过滤项拼装进sql语句内. String filterProperty; String filterValue; filterProperty="USERROLE"; filterValue=(String)filterPropertyMap.get(filterProperty); if (filterValue!=null){ whereSql.append(" AND ").append(filterProperty).append(" = '").append(StringEscapeUtils.escapeSql(filterValue)).append("' "); } filterProperty="USERNAME"; filterValue=(String)filterPropertyMap.get(filterProperty); if (filterValue!=null){ whereSql.append(" AND ").append(filterProperty).append(" like '").append(StringEscapeUtils.escapeSql(filterValue)).append("' "); } filterProperty="GENDER"; filterValue=(String)filterPropertyMap.get(filterProperty); if (filterValue!=null){ whereSql.append(" AND ").append(filterProperty).append(" = '").append(StringEscapeUtils.escapeSql(filterValue)).append("' "); } } if (sortValueMap!=null && !sortValueMap.isEmpty()){ bufSql.append("( SELECT * FROM USER_INFO "); bufSql.append(whereSql); bufSql.append(ECSideUtils.getDefaultSortSQL(sortValueMap)); bufSql.append(" ) "); }else{ bufSql.append(" USER_INFO "); bufSql.append(whereSql); } bufSql.append(" LIMIT ? OFFSET ?"); Connection conn=null; PreparedStatement pstmt = null; ResultSet rest = null; List userList=null; try { conn = getConnection(); pstmt = ConnectionUtils.prepareStatement(conn,bufSql.toString()); int prarameterIndex=0; pstmt.setInt(++prarameterIndex, size); pstmt.setInt(++prarameterIndex, startRow); rest = pstmt.executeQuery(); String[] columnName=getColumnName(rest); userList=new ArrayList(); Map userInfo=null; while (rest.next()) { userInfo=new HashMap(); buildRecord(rest,columnName,userInfo); userList.add(userInfo); } } catch (Exception e) { LogHandler.errorLog(logger, e); userList=null; }finally{ close(rest, pstmt, conn); } return userList; } 这个DAO同样没什么好说的了 非常好理解. 以上就是一个简单的实现,最有参考价值的就是ACTION的写法. 大家有什么疑问可以问,我会根据大家的问题来进一步完善这篇文档,谢谢大家. |
|
liwanchun_xd
2007-06-24
very good. Thanks.
|
|
ohmy
2007-07-19
关于数据库分页我有一个问题:
我在ec:table的retrieveRowsCallback="<%= callbackMethod%>",这个变量是控制数据库分页(赋limit值)跟内存分页(赋process值)的。但是页面却显示“'/>”,控制台显示LimitCallback.retrieveRows(55) | You need to specify the totalRows (as an Integer) to use the org.ecside.table.callback.LimitCallback. 不知道什么原因,请fins版主告知。好像之前有帖提到过这个问题。 可以发我邮箱吗?yuanzhan2001@126.com 万分感谢! ohmy |
|
ohmy
2007-07-19
接上贴。
我的action里修改了fins的那段的数据库分页的action代码,然后jsp里的代码是这样的: <% String callbackMethod; if (WebUtil.getStackValue(request, "from").equals("execute")){ callbackMethod = "limit"; }else { callbackMethod = "process"; } %> 其他需要贴代码的地方,请版主告知,就是很着急,希望尽快能够得到大家的指教! 非常感谢! |
|
skaiven
2007-07-20
getColumnName
和 buildRecord 是哪里来的 |
|
perrychen
2007-08-22
good. Thanks.
|
|
solomon
2007-08-27
非常清晰,谢谢.
|
|
guoguo929
2007-08-31
不错,收藏~
|
|
guoguo929
2007-08-31
不错,收藏~
|
|
黑孔雀
2007-09-06
bufSql.append(" LIMIT ? OFFSET ?");
这句貌似不能在oracle里面执行 |