我正在使用Spring和JPA / Hibernate玩耍,我对在表中增加计数器的正确方法有些困惑。
我的REST API需要根据用户操作来增加和减少数据库中的某些值(在示例中,在下面的示例中,喜欢或不喜欢标签会使计数器在标签表中增加或减少1)。
tagRepository是JpaRepository(Spring-data),并且我已经像这样配置了交易
tagRepository
JpaRepository
<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); } }
如您所见,我故意在增加20秒的睡眠.save()时间之后才能够测试并发方案。
.save()
初始标签计数器= 10;
1)用户调用crementTag,代码进入hibernate状态,因此实体的值= 11,而数据库中的值仍为10 2)用户调用reduceTag并遍历所有代码。值是数据库现在= 9 3)睡眠完成,并使用计数为11的实体命中.save,然后命中.save()
1)用户调用crementTag,代码进入hibernate状态,因此实体的值= 11,而数据库中的值仍为10
2)用户调用reduceTag并遍历所有代码。值是数据库现在= 9
3)睡眠完成,并使用计数为11的实体命中.save,然后命中.save()
当我检查数据库时,该标记的值现在等于11。实际上(至少我想实现的目标)等于10。
这是正常现象吗?还是@Transactional注释不做是工作?
@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)修改同一行。
READ_UNCOMMITTED