java - tiempo - protocolo de bloqueo en dos fases
Spring, JPA e Hibernate: cómo incrementar un contador sin problemas de concurrencia (3)
La solución más sencilla es delegar la concurrencia a su base de datos y simplemente confiar en el bloqueo del nivel de aislamiento de la base de datos en las filas modificadas actualmente:
El incremento es tan simple como esto:
UPDATE Tag t set t.count = t.count + 1 WHERE t.id = :id;
y la consulta de decremento es:
UPDATE Tag t set t.count = t.count - 1 WHERE t.id = :id;
La consulta de ACTUALIZACIÓN toma un bloqueo en las filas modificadas, evitando que otras transacciones modifiquen la misma fila, antes de que se READ_UNCOMMITTED
la transacción actual (siempre y cuando no use READ_UNCOMMITTED
).
Estoy jugando un poco con Spring y JPA / Hibernate y estoy un poco confundido en la forma correcta de incrementar un contador en una tabla.
Mi API REST necesita aumentar y disminuir algún valor en la base de datos según la acción del usuario (en el ejemplo siguiente, si le gusta o no le gusta una etiqueta hará que el contador aumente o disminuya en uno en la Tabla de etiquetas)
tagRepository
es un JpaRepository
(Spring-data) y he configurado la transacción como esta
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"/>
@Controller
public class TestController {
@Autowired
TagService tagService
public void increaseTag() {
tagService.increaseTagcount();
}
public void decreaseTag() {
tagService.decreaseTagcount();
}
}
@Transactional
@Service
public class TagServiceImpl implements TagService {
public void decreaseTagcount() {
Tag tag = tagRepository.findOne(tagId);
decrement(tag)
}
public void increaseTagcount() {
Tag tag = tagRepository.findOne(tagId);
increment(tag)
}
private void increment(Tag tag) {
tag.setCount(tag.getCount() + 1);
Thread.sleep(20000);
tagRepository.save(tag);
}
private void decrement(Tag tag) {
tag.setCount(tag.getCount() - 1);
tagRepository.save(tag);
}
}
Como puede ver, he puesto a propósito una suspensión de 20 segundos en el incremento JUST antes de .save()
para poder probar un escenario de concurrencia.
contador de etiqueta inicial = 10;
1) Un usuario llama a IncreaseTag y el código se pone en espera por lo que el valor de la entidad = 11 y el valor en el DB aún es 10
2) un usuario llama a la etiqueta baja y pasa por todo el código. el valor es la base de datos es ahora = 9
3) El sueño finaliza y golpea el .save con la entidad contando 11 y luego pulsa .save ()
Cuando reviso la base de datos, el valor de esa etiqueta ahora es igual a 11 ... cuando en realidad (al menos lo que me gustaría lograr) sería igual a 10
¿Es este comportamiento normal? ¿O la anotación de @Transactional
no está haciendo es trabajo?
Otra solución eventualmente consistente para agregar:
- cree una tabla de
counters_increment
separada para insertar cada incremento de contador - agregue un programador para actualizar la tabla de
counters
principales desdecounters_increment
Más:
- otra base de datos para el almacenamiento de escrituras de
counters_increment
pesado (por ejemplo, Cassandra, Redis) -
counters_increment_{period}
tabla por período (por ejemplo, día) y eliminar / volver a crear toda la tabla después de que los datos se procesen y ya no sean necesarios
Por ejemplo, utilice el bloqueo optimista. Esta debería ser la solución más fácil para resolver su problema. Para obtener más detalles, consulte -> https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html