Spring,JPA和Hibernate - 如何在没有并发问题的情况下增加计数器 [英] Spring, JPA, and Hibernate - how to increment a counter without concurrency issues

查看:125
本文介绍了Spring,JPA和Hibernate - 如何在没有并发问题的情况下增加计数器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



在Spring和JPA / Hibernate中玩弄了一下,我有点困惑于正确的方式来增加表中的计数器。我的REST API需要根据用户动作增加或减少数据库中的某个值(在下面的示例中,喜欢或不喜欢标签会使计数器在标签表中增加或减少一个)

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

 < bean id =transactionManagerclass =org.springframework.orm.jpa.JpaTransactionManager /> 

@Controller
public class TestController {
$ b $ @Autowired
TagService tagService

public void increaseTag(){
tagService.increaseTagcount();
}
public void decreaseTag(){
tagService.decreaseTagcount();



$ b @Transactional
@Service
公共类TagServiceImpl实现TagService {


public void decreaseTagcount(){
Tag tag = tagRepository.findOne(tagId);
递减(标记)
}

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);






$ b正如你所看到的,在 .save()之前,必须增加20秒,以便能够测试并发场景。

initial标记计数器= 10;


<1>用户调用increaseTag并且代码命中了sleep,所以实体的值
= 11,并且DB中的值仍然是10 b
$ b 2)用户调用decreaseTag并遍历所有代码。
值是数据库现在= 9 b / b
$ b 3)睡眠完成并触及.save,实体的
计数为11然后命中.save()


当我检查数据库时,该标记的值现在等于11 ..现实(至少我想达到什么)它将等于10



这种行为是否正常?或者 @Transactional 注解没有做的工作是什么?

解决方案

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



增量就像这样简单:

  UPDATE标签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查询对修改后的行进行锁定,防止其他事务修改同一行,在当前交易提交之前(只要你不愿意) 't使用 READ_UNCOMMITTED )。


I'm playing around a bit with Spring and JPA/Hibernate and I'm a bit confused on the right way to increment a counter in a table.

My REST API needs to increment and decrement some value in the database depending on the user action (in the example bellow, liking or disliking a tag will make the counter increment or decrement by one in the Tag Table)

tagRepository is a JpaRepository (Spring-data) and I have configured the transaction like this

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"/>

@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);
    }
}

As you can see I have put on purpose a sleep of 20 second on increment JUST before the .save() to be able to test a concurrency scenario.

initial tag counter = 10;

1) A user calls increaseTag and the code hits the sleep so the value of the entity = 11 and the value in the DB is still 10

2) a user calls the decreaseTag and goes through all the code. the value is the database is now = 9

3) The sleeps finishes and hits the .save with the entity having a count of 11 and then hits .save()

When I check the database, the value for that tag is now equal to 11.. when in reality (at least what I would like to achieve) it would be equal to 10

Is this behaviour normal? Or the @Transactional annotation is not doing is work?

解决方案

The simplest solution is to delegate the concurrency to your database and simply rely on the database isolation level lock on the currently modified rows:

The increment is as simple as this:

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

and the decrement query is:

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

The UPDATE query takes a lock on the modified rows, preventing other transactions from modifying the same row, before the current transaction commits (as long as you don't use READ_UNCOMMITTED).

这篇关于Spring,JPA和Hibernate - 如何在没有并发问题的情况下增加计数器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆