`
ch_space
  • 浏览: 111399 次
  • 性别: Icon_minigender_1
  • 来自: 农村进城务工人员
社区版块
存档分类
最新评论

分页查询总行数缓存策略

    博客分类:
  • Java
阅读更多
文章有点长。。。

以前看到的分页模型大同小异,都是一个POJO结合各类视图技术实现的,但对于每次查询,都要计算总页数(统计记录总行数),对于记录数较少、并发不高的系统来讲,这似乎没有什么问题,但对于高并发,记录行数很多(千万级)的情况,每次的统计行数就要花费不少时间。我这里尝试着设计了一个行数缓存和一个简单的分页POJO(跟传统的POJO大同小异),请大家批评讨论,并提出一些建议。分享才能进步!

1、在哪里缓存
可以在客户端(使用Cookie),也可以在服务器端(设计一个Cache)。服务器端可以灵活定义缓存时间、刷新策略,这里仅讨论服务器端缓存(大家也可以提提客户端缓存的优缺点)。

2、使用什么作为缓存的Key
缓存当然要有Key-Value,什么作为Key合适呢?
对于每次查询,查询条件是不一样的,尤其对于复杂的多条件动态查询,即同一个Service方法可能会有不同的查询条件,这样每次的记录行数是不确定的。所以,可以唯一标示一个查询的就是请求调用的方法(Controler里分发)和对应的查询参数,如:/listUsers.do?name=xxx&curPage=1 。那么就需要在服务器端获取这个url,然后把&curPage=1 这个条件去掉。需要注意的是:对于form的提交,需要以get方式,参数才可以用request.getQueryString()获取。

确定使用url作为缓存的key后,就要设计Page模型和缓存模型了。一般情况下,在Controler里调用Service,需要传入一个Page对象,在Dao中需要根据url进行缓存查询,决定是否要统计行数,将url直接作为Dao层中方法的参数是很不优雅的。这里我将url设计成Page的一个属性,在Dao中可以方便的使用page.getUrl()获取url了。

3、缓存策略
我们以<url,RowCount>的key-value形式在HashMap中缓存一个行数记录。关于缓存策略,可以有很多种,这里分析一下。

1)每个url具有独立的缓存策略
就是说,每个url可以有不同的缓存时间、刷新策略。缓存时间可以根据这个url对应的预计并发情况、统计耗时确定。
刷新可以访问后无论缓存中是否有对应的行数记录立刻重置计时,也可以只在缓存过期后才刷新,而缓存有效时不进行刷新。

2)统一定义每个url的缓存策略
这种情况下只需要每隔一段时间重置所有缓存中的记录的计时器即可,是最简单的一种。然而间隔时间多少不好估计。

3)是否需要换出内存
一个url按100个字符计算,加上RowCount(含2个int,一个long)本身的内存占用大概30byte,一个记录大概230byte,如果一个系统有10000个需要分页的查询(若查询参数不同,数目远不止这个),缓存占用约2Mb。还是占用了不少的内存,因此需要设计内存换出策略。可以采用最近最少使用原则LRU(Least Recently Used)、最不常用原则LFU(Least Frequently Used)等。前者简单,后者貌似更公平。我们简单采用LRU的一个简化版本:当缓存条目达到限制时,将最近最久未访问的缓存记录换出(LRU是计数,这里是计时)。

4、示例代码
Page分页模型:
/**
 * A pagination tool,default pageSize:20
 * @author chen
 */
public class Page {
	private int totalRow;
	private int totalPage;
	private int curPage;
	private int pageSize;
	private String url;
	private static final int DEFAULT_PAGESIZE=20;
	/**
	 * Default page size is 20
	 * @param url A string in the address field of the browser
	 * @param curPage current page index
	 */
	public Page(String url,int curPage) {
		this.url=url.replaceAll("&?curPage=\\d*", "");
		this.curPage = curPage < 1 ? 1 : curPage;
		this.pageSize = DEFAULT_PAGESIZE;
	}

	/**
	 * @param url A string in the address field of the browser
	 * @param curPage Current page index
	 * @param pageSize Size of the page
	 */
	public Page(String url,int curPage, int pageSize) {
		this.url=url.replaceAll("&?curPage=\\d*", "");
		this.curPage = curPage < 1 ? 1 : curPage;
		this.pageSize = pageSize;
	}
	/**
	 * Set total row of the pager
	 */
	public void setTotalRow(int totalRow) {
		this.totalRow =totalRow;
		this.totalPage=this.totalRow < 1 ? 0 : (this.totalRow - 1) / pageSize + 1;
		//invalid state
		if(curPage>this.totalPage || curPage<1){
			this.totalPage=0;
			this.totalRow=0;
			this.curPage=1;
		}
	}
	......
}

缓存模型:
public class RowCountCache {
	private RowCountCache() {
	}
	private Map<String, RowCount> m = new HashMap<String, RowCount>();
	private static RowCountCache cache = new RowCountCache();
	private static final int MAXSIZE=10000;
	private static Calendar c=Calendar.getInstance();
	/**
	 * An object of this cache.
	 * @return this
	 */
	public static RowCountCache getInstance() {
		return cache;
	}
	/**
	 * Cache state of the object: In the cache and is valid.
	 */
	public static final int CACHESTATE_VALID=1;
	/**
	 * Cache state of the object: Cache is expired
	 */
	public static final int CACHESTATE_EXPIRED=-1;
	/**
	 * Cache state of the object: Not in the cache.
	 */
	public static final int CACHESTATE_UNCACHED=-2;
	
	/**
	 * Get the row-count number from the cache of the given url.
	 * @return A row-count number,-1 if was not cached.
	 */
	public int get(String url) {
		RowCount r = m.get(url);
		return r==null ? -1 : r.getTotalRow();
	}
	
	/**
	 * Put or refresh cached row-count corresponding given url with default cache time.
	 * @param totalRow A row-count number corresponding a specify url.
	 */
	public void putOrRefresh(String url,int totalRow) {
		int cacheState=RowCountCache.getInstance().getCacheState(url);
		if(cacheState==RowCountCache.CACHESTATE_UNCACHED){
			this.put(url, totalRow);
		}else{
			this.refresh(url, totalRow);
		}
	}
	/**
	 * Put or refresh cached row-count corresponding given url with custom cache time.
	 * @param totalRow A row-count number corresponding a specify url.
	 * @param cacheTime Time the totalRow will be cached, in seconds.
	 */
	public void putOrRefresh(String url,int totalRow,int cacheTime) {
		int cacheState=RowCountCache.getInstance().getCacheState(url);
		if(cacheState==RowCountCache.CACHESTATE_UNCACHED){
			this.put(url, totalRow,cacheTime);
		}
		if(cacheState==RowCountCache.CACHESTATE_EXPIRED){
			this.refresh(url, totalRow);
		}
	}
	private void put(String url,int totalRow) {
		if(m.size()>=MAXSIZE){
			Set<Map.Entry<String, RowCount>> set = m.entrySet();
			c.setTime(new Date());
			long max_interval=-1;
			String key="";
			//find the farthest unused RowCount record
			for(Iterator<Map.Entry<String, RowCount>> iter=set.iterator();iter.hasNext();){
				Map.Entry<String, RowCount> e = iter.next();
				RowCount r=e.getValue();
				long interval=c.getTimeInMillis()-r.getLastVisit();
				if(max_interval<interval){
					max_interval=interval;
					key=e.getKey();
				}
			}
			m.remove(key);
		}
		m.put(url, new RowCount(totalRow));
	}
	private void put(String url,int totalRow,int cacheTime) {
		m.put(url, new RowCount(totalRow,cacheTime));
	}
	private void refresh(String url,int totalRow) {
		RowCount r =m.get(url);
		r.refresh(totalRow);
	}
	/**
	 * Get the cache state of RowCount corresponding the given url
	 * @return cache state
	 */
	public int getCacheState(String url) {
		RowCount r=m.get(url);
		if(r==null){
			return RowCountCache.CACHESTATE_UNCACHED;
		}else if(r.isExpired()){
			return RowCountCache.CACHESTATE_EXPIRED;
		}else{
			return RowCountCache.CACHESTATE_VALID;
		}
	}
}
/**
 * An object in the cache corresponding a specify url.
 * @author chen
 */
class RowCount{
	//Default cached time,5sec.
	private static final int DEFAULT_CACHE_TIME=5;
	private static Calendar c=Calendar.getInstance();
	private int totalRow;
	private int cacheTime;
	private long lastVisit;
	/**
	 * Construct RowCount with default cached time,5sec.
	 * @param totalRow A row count number corresponding a specify url.
	 */
	public RowCount(int totalRow){
		this.totalRow=totalRow;
		this.cacheTime=DEFAULT_CACHE_TIME;
		c.setTime(new Date());
		this.lastVisit=c.getTimeInMillis();
	}
	/**
	 * Construct RowCount with custom cached time,5sec.
	 * @param totalRow A row count number corresponding a specify url.
	 * @param cacheTime Time of the object will be cached,in seconds.
	 */
	public RowCount(int totalRow,int cacheTime){
		this.totalRow=totalRow;
		this.cacheTime=cacheTime;
		c.setTime(new Date());
		this.lastVisit=c.getTimeInMillis();;
	}
	/**
	 * Get the value of the row count.
	 * @return the value of the row count.Return -1 if the cache is expired.
	 */
	public int getTotalRow(){
		if(!isExpired()){
			return this.totalRow;
		}
		return -1;
	}
	/**
	 * Refresh this RowCount object in the cache.
	 * @param row A new row count number.
	 */
	protected void refresh(int row){
		this.totalRow=row;
		c.setTime(new Date());
		this.lastVisit=c.getTimeInMillis();;
	}
	/**
	 * Check whether the cache is expired
	 * @return true,expired; false,unexpired
	 */
	public boolean isExpired(){
		c.setTime(new Date());
		long t=c.getTimeInMillis();
		return t > this.lastVisit + this.cacheTime*1000;
	}
	/**
	 * Get the last visit date of the object in milliseconds.
	 */
	protected long getLastVisit(){
		return this.lastVisit;
	}
}


下面是一个使用的Demo(部分代码)
一个请求发送到Controler(为简化,省略了Service层)
...
Page p=new Page(HttpUtil.getUrl(),HttpUtil.getInteger(request, "curPage"));
request.setAttribute("all", userDao.find(cond,p));
request.setAttribute("page", p);
return mapping.findForward("find.do");


Dao:
...
int cacheState=RowCountCache.getInstance().getCacheState(p.getUrl());
if(cacheState==RowCountCache.CACHESTATE_VALID){
     p.setTotalRow(RowCountCache.getInstance().get(p.getUrl()));
}else{
     p.setTotalRow(this.countRow(cond, p));
}
//Whatever the cache is valid,refresh it.You can aslo refresh only when the cache is expired
RowCountCache.getInstance().putOrRefresh(p.getUrl(), p.getTotalRow());
List<Users> all=ct.setFirstResult(p.getFirstRow()).setMaxResults(p.getPageSize()).list();
return all;


终于写完了,请大家多多提意见,共同进步。~~

分享到:
评论
22 楼 prowl 2009-11-05  
凤舞凰扬 写道
prowl 写道
1,楼主提到了高并发,应该看下com.java.util.concurrent包下的ConcurrentHashMap

ConcurrentHashMap并没有所谓非常高的性能,如果想看,建议看oscache吧。顺便提一下,包名多了个com
prowl 写道

2,单纯靠判断时间来对缓存进行删除操作我觉得不太科学,是否可以加一个计数器?在使用频率最少和时间之间做判决。
现在绝大多数cache的算法都是采用最近最少使用的,只是一些算法增加了当不使用超过一段时间后,也淘汰的补充行为(即使cache的空间是足够的)
prowl 写道

3,是否可以扩展一下让这个缓存应用到不同的场景,比如存取可能是一些实际的对象,或者删除之后是否可简单持久化到文件,下次读取文件的一些关键信息。(有一些操作很占内存不一定是DAO,比如一些文件的解析)
你说的这个就类似于EJB中bean的钝化和激活了,这种行为并不是cache,更多地像池的行为。对于cache,如果删除了,你又如何从文件中读取? 当然,现在许多的cache实现都支持内存cache和临时的持久性cache(转储于硬盘或数据库,减少对内存的需求)。
prowl 写道

4,维护多个有关联的Map的时候是否需要加入事务处理。
事务是数据库的概念了,基本上没有简单办法去实现内存对象的事务(能做出一个事务机制,就相当威猛了,远远超出了cache及cache应用的层次)。


有关第三点,对于频繁耗时的数据库操作,或者一些文件的解析,往往得到一些有用的信息要用上更长的时间,在源更新不频繁的情况下,有效对已经提取的有用信息进行持久化,再次访问直接读取信息文件,能显著的提高效率。这只是一个扩展,也不太同于池的概念。

第四,有时会遇到一些缓存之间是相互关联的,比如同时保存了2个Map,其中一个Map里的数据在更新或者获取的时候出现了异常,那这条有关联的数据其实是无效的。可以加一些简单的事务控制。

其实这只是我在项目中遇到的问题,及我的解决办法,有时间一定看以下oscache,多谢推荐。
21 楼 凤舞凰扬 2009-11-04  
yirentianran 写道
既然是高并发,那是否应该考虑同步机制?

有效和高效的同步控制,是cache性能优劣的一个重要评判标准的。
20 楼 凤舞凰扬 2009-11-04  
prowl 写道
1,楼主提到了高并发,应该看下com.java.util.concurrent包下的ConcurrentHashMap

ConcurrentHashMap并没有所谓非常高的性能,如果想看,建议看oscache吧。顺便提一下,包名多了个com
prowl 写道

2,单纯靠判断时间来对缓存进行删除操作我觉得不太科学,是否可以加一个计数器?在使用频率最少和时间之间做判决。
现在绝大多数cache的算法都是采用最近最少使用的,只是一些算法增加了当不使用超过一段时间后,也淘汰的补充行为(即使cache的空间是足够的)
prowl 写道

3,是否可以扩展一下让这个缓存应用到不同的场景,比如存取可能是一些实际的对象,或者删除之后是否可简单持久化到文件,下次读取文件的一些关键信息。(有一些操作很占内存不一定是DAO,比如一些文件的解析)
你说的这个就类似于EJB中bean的钝化和激活了,这种行为并不是cache,更多地像池的行为。对于cache,如果删除了,你又如何从文件中读取? 当然,现在许多的cache实现都支持内存cache和临时的持久性cache(转储于硬盘或数据库,减少对内存的需求)。
prowl 写道

4,维护多个有关联的Map的时候是否需要加入事务处理。
事务是数据库的概念了,基本上没有简单办法去实现内存对象的事务(能做出一个事务机制,就相当威猛了,远远超出了cache及cache应用的层次)。
19 楼 yirentianran 2009-11-04  
既然是高并发,那是否应该考虑同步机制?
18 楼 prowl 2009-11-03  
ch_space 写道
prowl 写道
1,楼主提到了高并发,应该看下com.java.util.concurrent包下的ConcurrentHashMap

2,单纯靠判断时间来对缓存进行删除操作我觉得不太科学,是否可以加一个计数器?在使用频率最少和时间之间做判决。

3,是否可以扩展一下让这个缓存应用到不同的场景,比如存取可能是一些实际的对象,或者删除之后是否可简单持久化到文件,下次读取文件的一些关键信息。(有一些操作很占内存不一定是DAO,比如一些文件的解析)

4,维护多个有关联的Map的时候是否需要加入事务处理。

另外凤舞凰扬提到的这里只能应用于GET请求也是一个短板,最近刚好也有一个项目里有缓存的内容,愚见。

你的想法不错,呵呵,缓存方案其实已经有很好的实现OSCache等,对象缓存、查询缓存等等这些较复杂的东西可以使用现有的缓存系统集成就行了。我对缓存其实也不是很了解,只是写一点自己的想法,一起讨论下。。。


本着讨论的精神,自己考虑下这些东西比直接用framework要来的有帮助
17 楼 ch_space 2009-11-03  
prowl 写道
1,楼主提到了高并发,应该看下com.java.util.concurrent包下的ConcurrentHashMap

2,单纯靠判断时间来对缓存进行删除操作我觉得不太科学,是否可以加一个计数器?在使用频率最少和时间之间做判决。

3,是否可以扩展一下让这个缓存应用到不同的场景,比如存取可能是一些实际的对象,或者删除之后是否可简单持久化到文件,下次读取文件的一些关键信息。(有一些操作很占内存不一定是DAO,比如一些文件的解析)

4,维护多个有关联的Map的时候是否需要加入事务处理。

另外凤舞凰扬提到的这里只能应用于GET请求也是一个短板,最近刚好也有一个项目里有缓存的内容,愚见。

你的想法不错,呵呵,缓存方案其实已经有很好的实现OSCache等,对象缓存、查询缓存等等这些较复杂的东西可以使用现有的缓存系统集成就行了。我对缓存其实也不是很了解,只是写一点自己的想法,一起讨论下。。。
16 楼 prowl 2009-11-03  
1,楼主提到了高并发,应该看下com.java.util.concurrent包下的ConcurrentHashMap

2,单纯靠判断时间来对缓存进行删除操作我觉得不太科学,是否可以加一个计数器?在使用频率最少和时间之间做判决。

3,是否可以扩展一下让这个缓存应用到不同的场景,比如存取可能是一些实际的对象,或者删除之后是否可简单持久化到文件,下次读取文件的一些关键信息。(有一些操作很占内存不一定是DAO,比如一些文件的解析)

4,维护多个有关联的Map的时候是否需要加入事务处理。

另外凤舞凰扬提到的这里只能应用于GET请求也是一个短板,最近刚好也有一个项目里有缓存的内容,愚见。
15 楼 danielli007 2009-11-03  
我觉得,还是根据不同的应用场景,来理解楼主的想法,应该是挺好的。
14 楼 凤舞凰扬 2009-11-02  
1. 对于缓存的有效性而言,数据操作的性质是至关重要的,也就是如果读远大于写,以及数据的准确数量并不是最重要的信息时(也就是数据量远大于每页的数据行数),使用缓存来处理分页才是有效的。举个简单例子,一个查询会返回上万级的数据结果集时,那么数据总量及页数的是不必那么精确的,因为没有人会关心以及验证这个信息。从这个角度讲,基于页面的缓存最适合的是互联网系统,而不是企业业务系统。
2. 楼主将URL从web传递到DAO,是种非常不可取的做法,这种穿透性根本就破坏了层之间的依赖关系。换句简单的表达,这是MVC最起码的原则了。
3. 即使是URL,也只能处理Get的访问,对于基于Post的表单查询(会有童鞋质疑为什么要用post),楼主又如何获取一个唯一的URL呢?一个有效地改进是对查询条件对象或者生成的SQL进行数据摘要,但它有个前提,也就是摘要所花费的时间也要远小于统计数据的时间。当然,它又有下面的问题。
4. 我们说查询缓存对于什么样的情况最有用呢?不同的用户会使用相同的查询条件么?如果大家使用几乎不相同的查询条件,那么这个缓存也就是失去了意义,会反复地更新而不是读取。所以,要解决这个问题,必须清楚实际项目的应用情况,做好更准确的分析。

     说明上面这些,其实是想让楼主看看另外一篇讨论ORM缓存的帖子,缓存的应用是一个比较深的学问。去写一个缓存实现,去应用一个缓存组件都非常简单,复杂就复杂在不同系统,不同应用场景,缓存的不同应用有很大的区别。
13 楼 qhdyuxuan 2009-11-02  
qiren83 写道
多谢楼主 很详细的代码和注释

看了一半 有点头大 先顶下

请问下楼主做JAVA开发几年了 哪年的 呵呵

最近写代码感觉没前途似的 以前出来的几个朋友都做其它的去了 一半以上开外贸公司 多的月10万 去年开始的




感觉你像深圳这边的. 到你主页一看还真是.. 在深圳做外贸的确实很多. 感觉哪个方面适合自己就去做. 考虑自身情况就好.
12 楼 lgdlgd 2009-11-02  
ch_space 写道
lgdlgd 写道
楼主想得有点简单了,总行数并不是URL参数能确定的,不同的人提交相同的参数,但因为权限的不同,查到的数目是不一样的;另外,新增记录后,你好像没有立即把缓存清除,客户如果新增后立即查一下结果,细心的可能会发现查是查到了,但是总行数没有变化,会认为你的程序有问题,这个问题可以在RowCount类中加入一个类别属性,值为对应POJO的全类名,这样你提供一个类似这样的方法clearCacheByType(Class pojoClass),新增记录就可以通过POJO类一下把所有相关的缓存清除。

同一个Action(Struts)或者Controller(Spring MVC)的用户的权限是一样的,权限控制根本不应该在Action或者Controller中实现。
用户是不用关心行数的变化的,他们只关心自己需要的数据有没有看到。。。
你的建议我会考虑,谢谢!


同一个ACTION的用户权限是一样的?你说的是功能权限吧?如果你说的是数据权限一样,难道同样是查一个表,要为不同权限的人写不同的ACTION吗?
11 楼 ch_space 2009-11-02  
lgdlgd 写道
楼主想得有点简单了,总行数并不是URL参数能确定的,不同的人提交相同的参数,但因为权限的不同,查到的数目是不一样的;另外,新增记录后,你好像没有立即把缓存清除,客户如果新增后立即查一下结果,细心的可能会发现查是查到了,但是总行数没有变化,会认为你的程序有问题,这个问题可以在RowCount类中加入一个类别属性,值为对应POJO的全类名,这样你提供一个类似这样的方法clearCacheByType(Class pojoClass),新增记录就可以通过POJO类一下把所有相关的缓存清除。

同一个Action(Struts)或者Controller(Spring MVC)的用户的权限是一样的,权限控制根本不应该在Action或者Controller中实现。
用户是不用关心行数的变化的,他们只关心自己需要的数据有没有看到。。。
你的建议我会考虑,谢谢!

10 楼 ChinaHopes 2009-11-02  
举手,我问一下。

用SQL分页不就行了么。

统一有一个缓存机制实现常用数据的缓存。

我提一个最简单的实现方式,用SQL做key,用ObjectPool放结果集。


还有,为什么用户要关心你的缓存呢?要手动刷新?
9 楼 lgdlgd 2009-11-02  
楼主想得有点简单了,总行数并不是URL参数能确定的,不同的人提交相同的参数,但因为权限的不同,查到的数目是不一样的;另外,新增记录后,你好像没有立即把缓存清除,客户如果新增后立即查一下结果,细心的可能会发现查是查到了,但是总行数没有变化,会认为你的程序有问题,这个问题可以在RowCount类中加入一个类别属性,值为对应POJO的全类名,这样你提供一个类似这样的方法clearCacheByType(Class pojoClass),新增记录就可以通过POJO类一下把所有相关的缓存清除。
8 楼 01404421 2009-11-02  
感觉LZ这样子把问题复杂化了,把总数当做一个参数传递到客户端再通过url传递回来就行了,如果第一次请求那后面读不到这个参数就说明是第一次查询,去查一次。
7 楼 yy629 2009-11-02  
xunmeng3547 写道
LZ写的很清晰,在这里说一下我的想法:
1)LZ的缓存是为了当用户查看第2页,第3页等情况设计的?如果是这样,总页数我觉得可以在第一次查询以后就放入client端,当用户查看N页时,把当前的总页数一起提交到Server端,或者在web页面时不去刷新总页数部分。

2)对于LZ在Server端进行缓存,如果用户用不同的查询条件,又或者在text框中多输入了一个空格,缓存都会重新查询的。
3)假如用户第一次查询数据之后,新增了一条记录,这时的再次查询时,由于Server端的缓存,用户应该不会查询到新加入的记录的。
4)个人觉得,这个总页数应该存储在client端,当查看N页时,可以一并提交到Server,如果是重新查询数据的话,应该再次查询DB,还计算总页数。


  我们就是这么处理的, 在客户端缓存了总页数.
  只是在第一次查询时获得总页数, 分页是把总页数再传回去, 如果用户想刷新总页数, 就点击"刷新"按钮.
6 楼 ch_space 2009-11-02  
xunmeng3547 写道
LZ写的很清晰,在这里说一下我的想法:
1)LZ的缓存是为了当用户查看第2页,第3页等情况设计的?如果是这样,总页数我觉得可以在第一次查询以后就放入client端,当用户查看N页时,把当前的总页数一起提交到Server端,或者在web页面时不去刷新总页数部分。
4)个人觉得,这个总页数应该存储在client端,当查看N页时,可以一并提交到Server,如果是重新查询数据的话,应该再次查询DB,还计算总页数。

设计的初衷就是解决高并发、记录量大的情况下统计行数出现的性能问题。放在客户端,则每个用户的第一次查询都会统计行数,而服务器端缓存可以实现所有用户共享行数记录,尤其对于高并发的情况下(如5秒内有1000个不同的用户访问,则只需要统计一次,而在客户端要统计1000次)。

引用
2)对于LZ在Server端进行缓存,如果用户用不同的查询条件,又或者在text框中多输入了一个空格,缓存都会重新查询的。

这个空格(首尾)是需要过滤掉的,而字符中间的空格无需关心,本身就是不同的查询参数(当然你也可以过滤掉)。
引用
3)假如用户第一次查询数据之后,新增了一条记录,这时的再次查询时,由于Server端的缓存,用户应该不会查询到新加入的记录的。

在高并发下,你会在乎是否可以马上看到最新的内容?况且5秒的延迟还可以忍受吧(5秒钟之后即可看到新增的内容)。
5 楼 xunmeng3547 2009-11-02  
LZ写的很清晰,在这里说一下我的想法:
1)LZ的缓存是为了当用户查看第2页,第3页等情况设计的?如果是这样,总页数我觉得可以在第一次查询以后就放入client端,当用户查看N页时,把当前的总页数一起提交到Server端,或者在web页面时不去刷新总页数部分。

2)对于LZ在Server端进行缓存,如果用户用不同的查询条件,又或者在text框中多输入了一个空格,缓存都会重新查询的。
3)假如用户第一次查询数据之后,新增了一条记录,这时的再次查询时,由于Server端的缓存,用户应该不会查询到新加入的记录的。
4)个人觉得,这个总页数应该存储在client端,当查看N页时,可以一并提交到Server,如果是重新查询数据的话,应该再次查询DB,还计算总页数。
4 楼 ch_space 2009-11-02  
WenBin_Zhou 写道
LZ的写的很好,但我想请教几个问题:
    1.在内存中用map缓存用户的请求参数,如果是同一个用户在一段时间内发同一请求,但不同的curPage参数,LZ的做法是多进行了缓存,却没有想覆盖,这样是否是浪费而没意义的呢
    2.List<Users> all=ct.setFirstResult(p.getFirstRow()).setMaxResults(p.getPageSize()).list(); Lz使用hibernate吧,那hibernate的一二级缓存为何不考虑使用呢?
    3.分页吧,是Web应用吧?那服务器的session来缓存应该是最妥当的,缓存用户会话期间的请求参数,会话结束,即可以自动在设置的时间内销毁。
    4.在页面用数据岛缓存,但这需要js控制,也是不错的选择
最后 Lz代码写得很棒,学习了!


1、代码中已经把curPage这个参数忽略掉了,即使不同的curPage,只要缓存有效,也不会重新计算总行数。
public Page(String url,int curPage, int pageSize) {   
        this.url=url.replaceAll("&?curPage=\\d*", "");//就在这里   
        this.curPage = curPage < 1 ? 1 : curPage;   
        this.pageSize = pageSize;   
    }

2、你当然可以不使用Hibernate,代码只是一个示例,Dao层可以使用其他实现。Hibernate的二级缓存用来缓存总行数是不合适的,每次的查询条件是不一样的。
3、session在web应用中最好少用,占用服务器较大资源。
4、js当然也可以,可惜我不太熟悉。。。
3 楼 ch_space 2009-11-02  
qiren83 写道
多谢楼主 很详细的代码和注释

看了一半 有点头大 先顶下

请问下楼主做JAVA开发几年了 哪年的 呵呵

最近写代码感觉没前途似的 以前出来的几个朋友都做其它的去了 一半以上开外贸公司 多的月10万 去年开始的



你说的是“钱途”,不是“前途”
做事情要静心,别人赚并不一定自己也会赚
要明白自己适合做什么
坚持自己的理想
祝你成功!

相关推荐

    千万级数据分页查询存储过程SQLServer

    4. **缓存策略**:对于经常访问的页面,可以考虑使用缓存技术,如Redis,减少数据库压力。 总的来说,利用SQL Server的存储过程进行千万级数据分页查询,结合合理的索引策略和优化技巧,能够有效提升查询性能,降低...

    oracle分页程序的实现

    在这个例子中,我们首先计算出总行数和起始行号、结束行号,然后通过比较rowid(Oracle的逻辑地址)来选取指定范围内的数据。 在实际开发中,为了提高性能,通常还会对查询进行优化,如合理设计索引、减少不必要的...

    前端 elementUI 穿梭框大量数据分页处理

    4. **数据缓存**:对于已经加载过的数据,我们可以将其存储在缓存中,以避免重复请求。在用户翻页时,优先从缓存中获取数据,只有在缓存为空时才向服务器发起请求。 5. **优化DOM操作**:减少不必要的DOM操作,例如...

    各种分页方法

    10. **最佳实践**:对于大型数据集,考虑使用缓存策略,如Redis,将部分数据预加载到内存,或者使用前端分页,只在用户滚动时加载更多数据,这种被称为“无限滚动”。 以上就是关于MySQL数据库中分页方法的一些核心...

    asp高效分页 效率很高

    ' 这里是获取总行数的逻辑 End Function Public Function GetPagingSQL() ' 这里是生成分页SQL的逻辑 End Function Public Function GetPageLinks() ' 这里是生成分页链接的逻辑 End Function End Class `...

    ListView高效分页

    在实现分页时,需要设置数据源的分页属性,例如`PageIndex`(当前页索引)、`PageSize`(每页条目数)和`TotalRowCount`(总行数)。 3. **事件处理**:ListView提供了`PageIndexChanging`事件,当用户切换页面时...

    asp.net分页

    - 预估总行数:避免每次分页都计算总行数,可以使用缓存或预估值。 六、总结 在ASP.NET中,分页是一个重要的功能,通过控件分页和自定义分页可以满足不同场景的需求。理解并熟练掌握这些方法,可以有效地提升Web...

    目前用到的两个分页存储过程代码

    首先,我们来看第一个存储过程`[sq8reyoung].[fenye_num]`,它的主要目的是计算满足特定条件的数据总行数。这个存储过程接受两个参数:`@TableNames`(要查询的表名)和`@Filter`(查询条件)。如果`@Filter`为空,...

    hibernate分页例子

    在IT行业中,数据库操作是必不可少的一部分,特别是在Java领域,Hibernate作为一种强大的对象关系映射(ORM)框架,极大地简化了...同时,为了提升性能,还可以考虑缓存策略、索引优化等手段,以提高分页查询的效率。

    PHP用于文件内容操作的分页类.zip

    1. **总长度计算**:为了实现分页,需要知道文件的总长度或总行数。这个类可能提供了读取文件并计算总行数的方法,这一步是确定可以有多少页的基础。 2. **参数管理**:在网页请求中,分页通常通过URL参数如`page`...

    AspNetPager72Samples

    - 使用缓存策略,减少数据库访问次数。 - 考虑用户界面的友好性,提供清晰的分页指示,如“共XX页,当前第XX页”。 - 考虑到搜索引擎优化(SEO),提供链接形式的分页,便于爬虫抓取。 通过深入学习和实践`...

    PHP分页函数代码(简单实用型)

    分页功能的优化还包括缓存策略、负载均衡和响应式设计等。例如,可以使用缓存技术(如Memcached或Redis)来存储分页结果,减少数据库查询次数。对于高并发场景,可以考虑使用负载均衡器来分散请求。对于移动设备和...

    JS代码实现table数据分页效果

    // 获取数据总行数 var sumPages = Math.ceil(sumRows / pageSize); // 得到总页数 var $pager = $('&lt;div class="page"&gt;&lt;/div&gt;'); for (var pageIndex = 1; pageIndex ; pageIndex++) { $('&lt;a href="#"&gt;&lt;span&gt;...

    C#原生报表操作--设置每页打印30行实例源码

    2. **分页逻辑**:在C#中实现分页,需要计算数据源的总行数并根据每页的行数(这里是30行)进行分割。可以使用数据集合(如DataTable或List)的分页方法,或者手动遍历数据并按每30行划分到不同的页中。分页后的数据...

    mysql获取group by的总记录行数另类方法

    需要注意的是,`SQL_CALC_FOUND_ROWS`和`FOUND_ROWS()`不适用于所有的SQL优化策略,例如,它们可能不会在查询缓存中工作。因此,在性能至关重要的情况下,可能需要寻找其他解决方案,如预先计算并存储在数据库中的...

    kdtable的虚模式加载数据

    `KDTable1.setRowCount(list.size())`设置了表格的总行数,这一步可选,如果没有提供,kdtable会在第一次请求数据时自动计算。 接下来,添加了一个`KDTDataRequestListener`,这个监听器会在用户滚动表格(或触发...

    SqlServer 2005 T-SQL Query 学习笔记(3)

    例如,如果我们设置NTILE(3),则结果集会被分为3个组,如果总行数不能均分为3组,多余的行将分配到前两个组。NTILE()在数据分区和分组分析中非常有用,可以方便地将数据划分为低、中、高三档,或者根据需求进行...

    PHP 翻页 实例代码

    让我们深入了解一下这个代码的工作原理。 首先,我们定义了一个名为`Page`的类,它包含了以下几个私有属性: 1. `$pageSize`:表示每一页显示的数据...同时,对于大型网站,可能需要考虑缓存策略以减少数据库查询。

Global site tag (gtag.js) - Google Analytics