Domanda

Ho 2 thread simultanei che, allo stesso tempo entrare in un servizio di transazione (primavera).

Utilizzando Hibernate, i carichi metodo di servizio alcune entità, elabora quelle, trova uno e lo elimina dal DB. La pseudo codice è la seguente:

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

Nel caso in cui due thread contemporaneamente passano lo stesso parametro, entrambi sarà "trovare" la stessa entità di entrambi chiamerà delete. Uno di loro non riuscirà lanciando un org.hibernate.StaleObjectStateException quando la sessione è vicino.

vorrei che entrambi i fili sarebbero tornati l'entità, senza alcuna eccezione essere gettato. Al fine di raggiungere questo obiettivo, ho cercato di blocco (con "selezionare ... per l'aggiornamento") il soggetto prima di eliminarlo, come segue:

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

Io uso load() invece di get() dal momento che secondo le API di Hibernate, get sarebbero ritornati l'entità se già nella sessione, mentre il carico dovrebbe ri-leggere.

Se due fili entrano nello stesso momento in cui il metodo di cui sopra, uno di essi blocchi una la fase di bloccaggio, e quando il primo filo chiude la transazione, il secondo è risvegliato gettando un org.hibernate.StaleObjectStateException. Perché?

Perché il carico bloccato non solo restituire nulla? Come avrei potuto realizzare questo?

È stato utile?

Soluzione

ho trascorso qualche tempo approfondendo il problema e alla fine ho capito che cosa succede.

I tentativi di blocco PESSIMISTIC_WRITE "bloccare" l'entità che è già caricato nella sessione, non rileggere l'oggetto dal DB. Debug la chiamata, ho visto che entity == locked true tornato (in termini Java). Entrambe le variabili sono state rivolte alla stessa istanza.

Per forzare Hibernate per ricaricare l'entità, deve essere rimosso dalla sessione prima.

Il codice seguente fa il trucco:

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

Il PESSIMISTIC_WRITE MUT essere utilizzato con get invece di load, perché altrimenti un org.hibernate.ObjectNotFoundExceptionwould essere gettati.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top