求教jfinal如何处理并发时的数据存储和更新的问题?

事情是这样的,一篇文章,需要统计每天的阅读量。当日第一次阅读时,save();之后就是update();很简单的逻辑,代码如下:

public void set(String blogId) {
		Date now = new Date() ;
		String date = new SimpleDateFormat("yyyy-MM-dd").format(now);
		TodayReaders readTimes = this.get(blogId, date);
		if (null != readTimes) {
			readTimes.setReadTimes(readTimes.getReadTimes() + 1 );
			readTimes.update();
		}else{
			readTimes = new TodayReaders() ;
			readTimes.setId(getIds());
			readTimes.setReadTimes(1) ;
			readTimes.setBlogId(blogId);
			readTimes.setDate(now);
			readTimes.save();
		}
	}


但是……正式环境里面,居然就有一篇文章被存储了两条数据。请问遇到这样的情况该如何使用jfinal完美的解决?

评论区

JFinal

2016-12-12 21:16

一条 sql 搞定,在记录不存在时插入,存在时执行 update,大致是:
insert into table(f1, fn) VALUES(?,?) on duplicate key update count=count+1;
然后 Db.update(sql) 打完收工

通常这类功能,会做成只去操作缓存,然后再启动一个线程定时将缓存中的数据写入数据库,同时清空缓存,这样可以避免经常性写库操作,尤其对高并发有帮助

nbjgl

2016-12-13 14:33

@JFinal 这样的业务操作,其实是经常会遇到的。建议jfinal下个版本写个方法,可以处理这样的情况,用了jfinal , 好久不写insert的sql,基本都忘光了。

nbjgl

2016-12-13 14:43

@JFinal , 上面的解决方案,貌似并不能解决我的问题。 此处是根据 blogId 和 date 来确定是否已存在当日的统计数据 , 并不是根据 主键id 。 所以您建议的sql实在是不知道怎么实现。

JFinal

2016-12-13 15:19

@nbjgl 那就拆分成两个 sql,大致如下:
int n = Db.update("update t set count=count+1 where where id=? and date=?", id, date);
if (n == 0) {
Xxx xxx = new Xxx();
xxx.setId(id);
xxx.setCount(1);
xxx.setDate(new Date());
xxx.save();
}
上面的代码意思是,先尝试性的让其加1,如果 n 为 0 表示记录不存在,此时就可以去插入一条记录

注意上述代码放在一个事务中,以免并发情况下插入同 id 且同 date 的记录

最好的办法还是使用缓存,让一个专门的线程处理,连事务都不需要

nbjgl

2016-12-14 09:06

@JFinal 上面的代码和我之前写的代码貌似没有太大区别。依然无法防止并发的情况,即:两次请求,同时update,count都为0,然后同时save…… 其实我想问的就是怎么加事务,您这边一句话带过了~~能否详细说下?这个问题关注的人还蛮多的~~

JFinal

2016-12-14 10:36

@nbjgl 事务的用法在 jfinal 手册上有详细的例子,有两种用法,下面提供我个人常用的一种:
Db.tx(new IAtom() {
public void run() {
Db.update(sql, ...);
Db.update(sql,...);
new Xxx().set(...).set(...).save();
}
}

nbjgl

2016-12-14 19:21

@JFinal 这样的写法 是不是就可以保证查询并插入数据的时候,不会有其他的请求也同时查询和插入数据了?

nbjgl

2016-12-14 19:26

@JFinal 改造后的代码:

public void set( final String blogId ) {

Db.tx(new IAtom() {
@Override
public boolean run() throws SQLException {
Date now = new Date() ;
int count = Db.update("UPDATE t_today_readers tr SET tr.readTimes = tr.readTimes + 1 WHERE tr.blogId = ? AND tr.date = ? " , blogId , now );
if (count == 0) {
TodayReaders readTimes = new TodayReaders() ;
readTimes.setId(getIds());
readTimes.setReadTimes(1) ;
readTimes.setBlogId(blogId);
readTimes.setDate(now);
return readTimes.save();
}
return true ;
}
}) ;

}


这样就可以了吗?

nbjgl

2016-12-14 19:36

这样加事务是不是就是让请求排队,依次处理,不会出现同时处理两个的情况吗?

JFinal

2016-12-14 20:13

@nbjgl 事务的理解没那么简单,任何数据库实现事务都是在保障数据一致性的前提下,尽可能速度快,也就是说可以不必排队就不去排,例如两个线程同时操作同一个库,但是为不同的表,又或者操作的同一张表,但不是同一条记录,又或是同一条记录但不是同一个字段,必然会针对性能搞出一整套理论

nbjgl

2016-12-14 22:12

@JFinal 那我现在这样写,就是可以防止 同一日期、同一篇文章 插入两条数据的情况了吧?

zerov

2018-02-24 15:46

@nbjgl 解决了没?

chenxb8089

2018-05-15 20:23

@JFinal 您好,我也是第一次提问,最近我也在相关类型的业务(幸运大转盘),我自己测试的时候,很多人同时点击开始抽奖,代码我也加了Db.tx,比如5个奖品,但是我6个人同时点击,然后就会报错,我加了事务感觉还是不行啊,求解。

热门反馈

扫码入社