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

时间:2023-01-31 03:28:59

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.

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

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)

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

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

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

<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.

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

initial tag counter = 10;

初始标签计数器= 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

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

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

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

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

3)睡眠结束并使用计数为11的实体命中.save然后命中.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

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

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

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

2 个解决方案

#1


39  

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

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

#2


1  

For example use Optimistic Locking. This should be the easiest solution to solve your problem. For more details see -> https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html

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

#1


39  

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

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

#2


1  

For example use Optimistic Locking. This should be the easiest solution to solve your problem. For more details see -> https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html

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