Comment puis-je obtenir l'équivalent de ce code:
tx.begin();
Widget w = em.find(Widget.class, 1L, LockModeType.PESSIMISTIC_WRITE);
w.decrementBy(4);
em.flush();
tx.commit();
... mais en utilisant les annotations Spring et Spring-Data-JPA?
La base de mon code existant est:
@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);
}
}
Mais je ne sais pas comment spécifier que tout dans la méthode updateWidgetStock
doit être fait avec un ensemble de verrous pessimistes.
Il y a une annotation Spring Data JPA org.springframework.data.jpa.repository.Lock
Qui vous permet de définir un LockModeType
, mais je ne sais pas s'il est valide de le mettre sur la méthode updateWidgetStock
. Cela ressemble plus à une annotation sur le WidgetRepository
, car le Javadoc dit:
org.springframework.data.jpa.repository
@ Cible (valeur = MÉTHODE)
@ Rétention (valeur = RUNTIME)
@Documenté
public @interface Lock
Annotation utilisée pour spécifier le LockModeType à utiliser lors de l'exécution de la requête. Il sera évalué lors de l'utilisation de Query sur une méthode de requête ou si vous dérivez la requête à partir du nom de la méthode.
... donc cela ne semble pas être utile.
Comment puis-je exécuter ma méthode updateWidgetStock()
avec LockModeType.PESSIMISTIC_WRITE
Défini?
@Lock
Est pris en charge sur les méthodes CRUD à partir de la version 1.6 de Spring Data JPA (en fait, il existe déjà un jalon disponible). Voir ceci ticket pour plus de détails.
Avec cette version, vous déclarez simplement ce qui suit:
interface WidgetRepository extends Repository<Widget, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
Widget findOne(Long id);
}
Cela entraînera la partie d'implémentation CRUD du proxy de référentiel de sauvegarde pour appliquer le LockModeType
configuré à l'appel find(…)
sur le EntityManager
.
Si vous ne souhaitez pas remplacer la méthode findOne()
standard, vous pouvez acquérir un verrou dans votre méthode personnalisée en utilisant select ... for update
requête comme ceci:
/**
* 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);
}
Cependant, si vous utilisez PostgreSQL, les choses peuvent devenir un peu compliquées lorsque vous souhaitez définir un délai d'expiration de verrouillage pour éviter les blocages. PostgreSQL ignore la propriété standard javax.persistence.lock.timeout
défini dans les propriétés JPA ou dans @QueryHint
annotation.
La seule façon de le faire fonctionner était de créer un référentiel personnalisé et de définir un délai d'attente manuellement avant de verrouiller une entité. Ce n'est pas sympa mais au moins ça marche:
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;
}
}
Si vous pouvez utiliser Spring Data 1.6 ou supérieur, ignorez cette réponse et reportez-vous à la réponse d'Oliver.
Les données de printemps pessimistes @Lock
les annotations ne s'appliquent (comme vous l'avez souligné) qu'aux requêtes. Je ne connais aucune annotation qui puisse affecter une transaction entière. Vous pouvez soit créer une méthode findByOnePessimistic
qui appelle findByOne
avec un verrou pessimiste ou vous pouvez modifier findByOne
pour toujours obtenir un verrou pessimiste.
Si vous vouliez implémenter votre propre solution, vous le pourriez probablement. Sous le capot, le @Lock
l'annotation est traitée par LockModePopulatingMethodIntercceptor
qui effectue les opérations suivantes:
TransactionSynchronizationManager.bindResource(method, lockMode == null ? NULL : lockMode);
Vous pouvez créer un gestionnaire de verrouillage statique avec un ThreadLocal<LockMode>
variable membre, puis avoir un aspect enroulé autour de chaque méthode dans chaque référentiel qui a appelé bindResource
avec le mode de verrouillage défini dans ThreadLocal
. Cela vous permettrait de définir le mode de verrouillage pour chaque thread. Vous pouvez ensuite créer votre propre @MethodLockMode
annotation qui encapsule la méthode dans un aspect qui définit le mode de verrouillage spécifique au thread avant d'exécuter la méthode et l'efface après l'exécution de la méthode.