java - ejemplo - spring jpa repository example
¿Cómo habilitar LockModeType.PESSIMISTIC_WRITE al buscar entidades con Spring Data JPA? (3)
¿Cómo puedo lograr el equivalente de este código?
tx.begin();
Widget w = em.find(Widget.class, 1L, LockModeType.PESSIMISTIC_WRITE);
w.decrementBy(4);
em.flush();
tx.commit();
... pero usando las anotaciones Spring-Spring-Data-JPA?
La base de mi código actual es:
@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);
}
}
Pero no sé cómo especificar que todo en el método updateWidgetStock
se debe hacer con un conjunto de bloqueo pesimista.
Hay una anotación Spring Data JPA org.springframework.data.jpa.repository.Lock
que le permite establecer un LockModeType
, pero no sé si es válido ponerlo en el método updateWidgetStock
. Suena más como una anotación en el WidgetRepository
, porque el Javadoc dice:
org.springframework.data.jpa.repository
@Target (value = METHOD)
@Retention (value = RUNTIME)
@Documentado
cerradura de interfaz pública
Anotación utilizada para especificar el LockModeType que se utilizará al ejecutar la consulta. Se evaluará al usar Query en un método de consulta o si deriva la consulta del nombre del método.
... así que eso no parece ser útil.
¿Cómo puedo hacer que mi método updateWidgetStock()
se ejecute con LockModeType.PESSIMISTIC_WRITE
establecido?
Si no desea anular el findOne()
estándar de findOne()
, puede adquirir un bloqueo en su método personalizado al usar select ... for update
consulta de select ... for update
como esta:
/**
* 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);
}
Sin embargo, si está utilizando PostgreSQL, las cosas pueden complicarse un poco si desea establecer el tiempo de espera de bloqueo para evitar interbloqueos. PostgreSQL ignora la propiedad estándar javax.persistence.lock.timeout
establecida en las propiedades JPA o en la anotación @QueryHint
.
La única forma en que podía hacerlo funcionar era crear un repositorio personalizado y configurar el tiempo de espera manualmente antes de bloquear una entidad. No es agradable, pero al menos funciona:
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;
}
}
@Lock
es compatible con los métodos CRUD a partir de la versión 1.6 de Spring Data JPA (de hecho, ya hay un milestone disponible). Vea este ticket para más detalles.
Con esa versión, simplemente declaras lo siguiente:
interface WidgetRepository extends Repository<Widget, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
Widget findOne(Long id);
}
Esto hará que la implementación de CRUD, parte del proxy del repositorio de respaldo, aplique LockModeType
configurado a la llamada de find(…)
en EntityManager
.
Si puede usar Spring Data 1.6 o superior, ignore esta respuesta y consulte la respuesta de Oliver.
Las anotaciones pesimistas @Lock
Spring Data solo se aplican (como ha señalado) a las consultas. No hay anotaciones que conozca que puedan afectar una transacción completa. Puede crear un método findByOnePessimistic
que invoca findByOne
con un bloqueo pesimista o puede cambiar findByOne
para obtener siempre un bloqueo pesimista.
Si quisiera implementar su propia solución, probablemente podría. Debajo del capó, la anotación @Lock
es procesada por LockModePopulatingMethodIntercceptor
que hace lo siguiente:
TransactionSynchronizationManager.bindResource(method, lockMode == null ? NULL : lockMode);
Podrías crear algún gestor de bloqueo estático que tuviera una variable de miembro ThreadLocal<LockMode>
y luego tener un aspecto envuelto alrededor de cada método en cada repositorio que llamara bindResource
con el modo de bloqueo establecido en ThreadLocal
. Esto le permitiría configurar el modo de bloqueo por subproceso. Luego, podría crear su propia anotación @MethodLockMode
que envolvería el método en un aspecto que establezca el modo de bloqueo específico del subproceso antes de ejecutar el método y lo borre después de ejecutar el método.