técnica tiempo protocolo por ordenacion multiple marcas granularidad fases estricto datos control concurrencia bloqueos bloqueo b2f java spring hibernate jpa transactions

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 desde counters_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