1

同時に(Spring)トランザクションサービスに入る2つの同時スレッドがあります。

Hibernate を使用して、サービス メソッドはいくつかのエンティティをロードして処理し、エンティティを見つけて DB から削除します。擬似コードは次のとおりです。

@Transactional
public MyEntity getAndDelete(String prop) {
    List<MyEntity> list = (List<MyEntity>)sessionFactory
        .getCurrentSession()
        .createCriteria(MyEntity.class)
        .add( Restrictions.eq("prop", prop) )
        .list();

    // process the list, and find one entity
    MyEntity entity = findEntity(list);
    if (entity != null) {
        sessionFactory.getCurrentSession().delete(entity);
    }
    return entity;
}

2 つのスレッドが同時に同じパラメーターを渡す場合、両方が同じエンティティを「検索」し、両方が を呼び出しますdeleteorg.hibernate.StaleObjectStateExceptionそのうちの 1 つは、セッションが閉じているときに をスローして失敗します。

例外がスローされずに、両方のスレッドがエンティティを返すことを望みます。これを達成するために、次のように、エンティティを削除する前に(「select ... for update」で)ロックしようとしました:

@Transactional
public MyEntity getAndDelete(String prop) {
    List<MyEntity> list = (List<MyEntity>)sessionFactory
        .getCurrentSession()
        .createCriteria(MyEntity.class)
        .add( Restrictions.eq("prop", prop) )
        .list();

    // process the list, and find one entity
    MyEntity entity = findEntity(list);
    if (entity != null) {
        // reload the entity with "select ...for update"
        // to ensure the exception is not thrown
        MyEntity locked = (MyEntity)sessionFactory
            .getCurrentSession()
            .load(MyEntity.class, entity.getId(), new LockOptions(LockMode.PESSIMISTIC_WRITE));
        if (locked != null) {
            sessionFactory.getCurrentSession().delete(locked);
        }
    }
    return entity;
}

load()代わりに使用するのget()は、hibernate API によると、既にセッションにある場合は get がエンティティを返し、load はエンティティを再読み取りする必要があるためです。

2 つのスレッドが同時に上記の方法に入ると、そのうちの 1 つがロック ステージをブロックし、最初のスレッドがトランザクションを閉じると、2 番目のスレッドが呼び出されてorg.hibernate.StaleObjectStateException. なんで?

ロックされたロードが null を返さないのはなぜですか? どうすればこれを達成できますか?

4

1 に答える 1

1

この問題の調査に時間を費やし、最終的に何が起こるかを理解しました。

PESSIMISTIC_WRITE ロックは、セッションに既にロードされているエンティティを「ロック」しようとします。DB からオブジェクトを再読み込みしません。呼び出しをデバッグすると、(Java 用語で)entity == locked返されることがわかりました。true両方の変数が同じインスタンスを指していました。

休止状態でエンティティを強制的にリロードするには、最初にエンティティをセッションから削除する必要があります。

次のコードはトリックを行います。

@Transactional
public MyEntity getAndDelete(String prop) {
    List<MyEntity> list = (List<MyEntity>)sessionFactory
        .getCurrentSession()
        .createCriteria(MyEntity.class)
        .add( Restrictions.eq("prop", prop) )
        .list();

    // process the list, and find one entity
    MyEntity entity = findEntity(list);
    if (entity != null) {

        // Remove the entity from the session.
        sessionFactory.getCurrentSession().evict(entity);

        // reload the entity with "select ...for update"
        MyEntity locked = (MyEntity)sessionFactory
            .getCurrentSession()
            .get(MyEntity.class, entity.getId(), new LockOptions(LockMode.PESSIMISTIC_WRITE));
        if (locked != null) {
            sessionFactory.getCurrentSession().delete(locked);
        }
    }
    return entity;
}

PESSIMISTIC_WRITE mut はgetの代わりに使用できます。loadそうしないと aorg.hibernate.ObjectNotFoundExceptionがスローされるためです。

于 2011-10-20T18:30:26.017 に答える