Spring,JPA和Hibernate – 如何在没有并发问题的情况下增加计数器

我正在玩Spring和JPA / Hibernate,我对在表中递增计数器的正确方法感到困惑。

我的REST API需要根据用户操作递增和递减数据库中的某些值(在下面的示例中,喜欢或不喜欢标记会使计数器在Tag表中递增或递减1)

tagRepository是一个JpaRepository (Spring-data),我已经像这样配置了事务

  @Controller public class TestController { @Autowired TagService tagService public void increaseTag() { tagService.increaseTagcount(); } public void decreaseTag() { tagService.decreaseTagcount(); } } @Transactional @Service public class TagServiceImpl implements TagService { public void decreaseTagcount() { Tag tag = tagRepository.findOne(tagId); decrement(tag) } public void increaseTagcount() { Tag tag = tagRepository.findOne(tagId); increment(tag) } private void increment(Tag tag) { tag.setCount(tag.getCount() + 1); Thread.sleep(20000); tagRepository.save(tag); } private void decrement(Tag tag) { tag.setCount(tag.getCount() - 1); tagRepository.save(tag); } } 

正如你所看到的,我已经在.save()之前在增量JUST上进行了20秒的睡眠,以便能够测试并发场景。

初始标签计数器= 10;

1)用户调用increaseTag并且代码命中睡眠,因此entity = 11的值和DB中的值仍为10

2)用户调用decreaseTag并浏览所有代码。 值是数据库现在= 9

3)睡眠结束并使用计数为11的实体命中.save然后命中.save()

当我检查数据库时,该标签的值现在等于11 ..实际上(至少我希望实现)它将等于10

这种行为是否正常? 或者@Transactional注释不起作用?

最简单的解决方案是将并发性委托给您的数据库,并简单地依赖于当前修改的行上的数据库隔离级别锁定:

增量就像这样简单:

 UPDATE Tag t set t.count = t.count + 1 WHERE t.id = :id; 

并且减量查询是:

 UPDATE Tag t set t.count = t.count - 1 WHERE t.id = :id; 

UPDATE查询在当前事务提交之前锁定已修改的行,防止其他事务修改同一行(只要您不使用READ_UNCOMMITTED )。

例如,使用乐观锁定。 这应该是解决问题的最简单的解决方案。 有关详细信息,请参阅 – > https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html

另一个最终一致的解决方案:

  • 创建单独的counters_increment表以插入每个计数器增量
  • 添加调度程序以从counters_increment更新主counters

更多:

  • 用于写入大量counters_increment存储的另一个DB(例如Cassandra,Redis)
  • 每个周期(例如一天)的counters_increment_{period}表,并在处理完数据后不再需要删除/重新创建整个表