Question

J'ai 2 threads simultanés qui en même temps entrer dans un service de transaction (printemps).

Utilisation Hibernate, les charges de méthode de service certaines entités, traite ceux-ci, trouve un et supprime de la DB. Le pseudo-code est comme suit:

@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;
}

Dans le cas où deux threads en même temps passer le même paramètre, tous les deux « trouver » la même entité une fois appellera delete. L'un d'eux ne parviendront pas jeter un org.hibernate.StaleObjectStateException lorsque la session est proche.

Je voudrais que les deux fils retourneraient l'entité, sans exception levée. Pour y parvenir, j'ai essayé de verrouillage (avec « select ... mise à jour ») l'entité avant de le supprimer, comme suit:

@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;
}

J'utilise load() au lieu de get() puisque selon les API de mise en veille prolongée, get retourneraient l'entité si elle est déjà à la session, alors que la charge devrait relire.

Si deux fils entrent en même temps le procédé ci-dessus, l'un d'eux bloque un la de la phase de verrouillage, et lorsque le premier fil ferme la transaction, la seconde est réveillé jeter un org.hibernate.StaleObjectStateException. Pourquoi?

Pourquoi la charge verrouillée non seulement revenir nulle? Comment pourrais-je y parvenir?

Était-ce utile?

La solution

J'ai passé quelque temps à enquêter sur cette question et je compris enfin ce qui se passe.

Les essais de verrouillage PESSIMISTIC_WRITE de « verrouiller » l'entité qui est déjà chargée dans la session, il n'a pas relue l'objet de la DB. Débogage l'appel, j'ai vu que entity == locked true retourné (en Java). Les deux variables ont été pointant vers la même instance.

Pour forcer hiberner à recharger l'entité, il doit être retiré de la première session.

Le code suivant fait le tour:

@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;
}

Le PESSIMISTIC_WRITE MUT être utilisé avec get au lieu de load, car sinon une org.hibernate.ObjectNotFoundExceptionwould levée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top