web-dev-qa-db-ja.com

Spring、JPA、およびHibernate-並行性の問題なしでカウンターをインクリメントする方法

私はSpringとJPA/Hibernateで少し遊んでいますが、テーブルのカウンターをインクリメントする正しい方法について少し混乱しています。

My REST APIは、ユーザーのアクションに応じてデータベース内の値を増減する必要があります(以下の例では、タグを好きまたは嫌うと、タグ内のカウンターが1ずつ増加または減少しますテーブル)

tagRepositoryJpaRepository(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_アノテーションは機能していませんか?

23
Johny19

最も簡単な解決策は、 データベースへの同時実行性を委任する で、単に データベース分離レベル 現在変更されている行をロックすることです。

増分は次のように簡単です。

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を使用しない限り)。

50
Vlad Mihalcea

たとえば、オプティミスティックロックを使用します。これは、問題を解決するための最も簡単な解決策です。詳細については、-> https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html を参照してください

1
mh-dev

追加する最終的に一貫した別のソリューション:

  • 個別のcounters_incrementテーブルを作成して、各カウンターインクリメントを挿入します
  • counters_incrementからメインcountersテーブルを更新するスケジューラーを追加します

もっと:

  • 書き込みが多いcounters_incrementストレージ用の別のDB(例:Cassandra、Redis)
  • counters_increment_{period}期間(例:日)ごとのテーブル、およびデータが処理されて不要になった後のテーブル全体の削除/再作成
0
aux