私はSpringとJPA/Hibernateで少し遊んでいますが、テーブルのカウンターをインクリメントする正しい方法について少し混乱しています。
My REST APIは、ユーザーのアクションに応じてデータベース内の値を増減する必要があります(以下の例では、タグを好きまたは嫌うと、タグ内のカウンターが1ずつ増加または減少しますテーブル)
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);
}
}
_
ご覧のとおり、.save()
の前に増分で20秒のスリープを故意に設定して、同時実行シナリオをテストできるようにしました。
初期タグカウンター= 10;
1)ユーザーがincrementTagを呼び出し、コードがスリープ状態になるため、エンティティの値は11で、DBの値はまだ10です。
2)ユーザーが減少タグを呼び出し、すべてのコードを実行します。値はデータベースが現在= 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}
期間(例:日)ごとのテーブル、およびデータが処理されて不要になった後のテーブル全体の削除/再作成