2013-04-23 6 views
23

Come posso ottenere l'equivalente di questo codice:Come abilitare LockModeType.PESSIMISTIC_WRITE quando si cercano entità con Spring Data JPA?

tx.begin(); 
Widget w = em.find(Widget.class, 1L, LockModeType.PESSIMISTIC_WRITE); 
w.decrementBy(4); 
em.flush(); 
tx.commit(); 

... ma utilizzando primavera e la primavera-Data-JPA annotazioni?

La base del mio codice esistente è:

@Service 
@Transactional(readOnly = true) 
public class WidgetServiceImpl implements WidgetService 
{ 
    /** The spring-data widget repository which extends CrudRepository<Widget, Long>. */ 
    @Autowired 
    private WidgetRepository repo; 

    @Transactional(readOnly = false) 
    public void updateWidgetStock(Long id, int count) 
    { 
    Widget w = this.repo.findOne(id); 
    w.decrementBy(4); 
    this.repo.save(w); 
    } 
} 

Ma non so come specificare che tutto nel metodo updateWidgetStock dovrebbe essere fatto con un set di blocco pessimistico.

C'è un'annotazione primavera dati JPA org.springframework.data.jpa.repository.Lock che consente di impostare una LockModeType, ma non so se è valida per metterlo sul metodo updateWidgetStock. Suona più come un'annotazione sul WidgetRepository, perché il Javadoc dice:

org.springframework.data.jpa.repository
@Target (value = METHOD)
@Retention (value = Runtime)
@Documented
public @interface Lock
Annotazione utilizzata per specificare il LockModeType da utilizzare durante l'esecuzione della query. Verrà valutato quando si utilizza Query su un metodo di query o se si ottiene la query dal nome del metodo.

... in modo che non sembra essere utile.

Come è possibile eseguire il metodo updateWidgetStock() con LockModeType.PESSIMISTIC_WRITE impostato?

+0

Entrambe le risposte a questa domanda potrebbe essere utile: http://stackoverflow.com/questions/11880924/how-to-add-custom-method -to-spring-data-jpa –

risposta

38

@Lock è supportato sui metodi CRUD a partire dalla versione 1.6 di Spring Data JPA (infatti, è già disponibile uno milestone). Vedi questo ticket per maggiori dettagli.

Con questa versione è sufficiente dichiarare quanto segue:

interface WidgetRepository extends Repository<Widget, Long> { 

    @Lock(LockModeType.PESSIMISTIC_WRITE) 
    Widget findOne(Long id); 
} 

Questo farà sì che la parte implementazione CRUD della delega repository supporto per applicare la configurazione LockModeType alla find(…) chiamata sul EntityManager.

+0

Questo @Lock funziona già con spring-data-jpa 1.4.1 nei miei test ... ma comunque la domanda è, come posso passare tipo di blocco? Non voglio usare sempre il lock pessimistico, molti dei senari vengono letti principalmente. Ma quando so che sto facendo delle scritture, e che ci saranno contese mi piacerebbe usare un lock pessimistico. findOneLock (String, LockModeType)? –

+2

ah ok, si vede in 1.4.1 funziona quando lo aggiungo a un findOne sovrascritto, ma non quando lo aggiungo do il mio findByXxx. La domanda principale rimane, come posso usare un blocco un po 'di tempo, ma non sempre. –

7

Se si è in grado di utilizzare Spring Data 1.6 o superiore ignorare questa risposta e fare riferimento alla risposta di Oliver.

Le annotazioni pessimistiche di Spring @Lock si applicano solo (come indicato) alle query. Non ci sono annotazioni che so che possono influenzare un'intera transazione. È possibile creare un metodo findByOnePessimistic che chiama findByOne con un blocco pessimistico oppure è possibile modificare findByOne per ottenere sempre un blocco pessimistico.

Se si volesse implementare la propria soluzione probabilmente lo si potrebbe fare. Sotto il cofano il @Lock annotazione viene elaborata dal LockModePopulatingMethodIntercceptor che esegue le seguenti operazioni:

TransactionSynchronizationManager.bindResource(method, lockMode == null ? NULL : lockMode); 

si potrebbe creare qualche manager serratura statica che aveva una variabile ThreadLocal<LockMode> membro e quindi avere un aspetto avvolto intorno ogni metodo in ogni repository che chiama bindResource con la modalità di blocco impostata su ThreadLocal. Ciò consentirebbe di impostare la modalità di blocco su una base per-thread. È quindi possibile creare la propria annotazione @MethodLockMode che avvolgerebbe il metodo in un aspetto che imposta la modalità di blocco specifica del thread prima di eseguire il metodo e lo cancella dopo l'esecuzione del metodo.

+0

Qual è il punto di avere un blocco pessimistico su una query se non si mantiene fino alla fine della transazione (e quindi comprende tutti gli aggiornamenti successivi)? –

+0

Un blocco pessimistico ottenuto in una query deve essere conservato fino alla fine della transazione. Questi secondi due paragrafi descrivevano come si poteva creare una transazione in cui tutte le query ottenevano serrature pessimistiche. – Pace

+2

Se ho capito bene, posso ottenere l'effetto desiderato semplicemente facendo in modo che findOne utilizzi un blocco pessimistico, che il blocco dovrebbe contenere fino alla fine della mia transazione (che terminerà alla fine del mio metodo 'updateWidgetStock()' –

5

Se non si desidera sovrascrivere metodo standard findOne(), è possibile acquisire un blocco nel vostro metodo personalizzato utilizzando select ... for update interrogazione proprio come questo:

/** 
* Repository for Wallet. 
*/ 
public interface WalletRepository extends CrudRepository<Wallet, Long>, JpaSpecificationExecutor<Wallet> { 

    @Lock(LockModeType.PESSIMISTIC_WRITE) 
    @Query("select w from Wallet w where w.id = :id") 
    Wallet findOneForUpdate(@Param("id") Long id); 
} 

Tuttavia, se si utilizza PostgreSQL, le cose possono diventa un po 'complicato quando si desidera impostare il timeout di blocco per evitare deadlock. PostgreSQL ignora la proprietà standard javax.persistence.lock.timeout impostata nelle proprietà JPA o nell'annotazione @QueryHint.

L'unico modo per farlo funzionare era creare un repository personalizzato e impostare manualmente il timeout prima di bloccare un'entità. Non è bello, ma almeno si sta lavorando:

public class WalletRepositoryImpl implements WalletRepositoryCustom { 

@PersistenceContext 
private EntityManager em; 


@Override 
public Wallet findOneForUpdate(Long id) { 
    // explicitly set lock timeout (necessary in PostgreSQL) 
    em.createNativeQuery("set local lock_timeout to '2s';").executeUpdate(); 

    Wallet wallet = em.find(Wallet.class, id); 

    if (wallet != null) { 
     em.lock(wallet, LockModeType.PESSIMISTIC_WRITE); 
    } 

    return wallet; 
} 

}

+0

Sfortunatamente, questo non aggiorna la cache 'entityManager'. È necessario eseguire manualmente un 'entityManager.refresh (portafoglio)' – rcomblen

Problemi correlati