Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

java - Spring, JPA, and Hibernate - how to increment a counter without concurrency issues

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...