Je joue un peu avec Spring et JPA/Hibernate et je suis un peu confus sur la bonne façon d’incrémenter un compteur dans une table.
Mon REST API doit incrémenter et décrémenter une valeur dans la base de données en fonction de l'action de l'utilisateur (dans l'exemple ci-dessous, aimer ou ne pas aimer une balise obligera le compteur à s'incrémenter ou à se décrémenter de une dans la table des balises)
tagRepository
est une JpaRepository
(Spring-data) et j'ai configuré la transaction comme ceci
<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);
}
}
Comme vous pouvez le constater, j’ai volontairement dormi 20 secondes au maximum juste avant la .save()
pour pouvoir tester un scénario de concurrence.
compteur d'étiquette initial = 10;
1) Un utilisateur appelle augmentationTag et le code frappe le sommeil pour que la valeur de l'entité = 11 et la valeur dans le DB est toujours 10
2) un utilisateur appelle le signe de réduction et passe en revue tout le code. la valeur est la base de données est maintenant = 9
3) Le sommeil se termine et frappe le .save avec l'entité ayant un comptez 11, puis appuyez sur .save ()
Lorsque je vérifie la base de données, la valeur de cette balise est maintenant égale à 11 .. alors qu'en réalité (du moins ce que je voudrais atteindre), elle serait égale à 10
Ce comportement est-il normal? Ou l'annotation @Transactional
ne fait-elle pas son travail?
La solution la plus simple consiste à déléguer la simultanéité à votre base de données et à vous appuyer simplement sur le verrou de la base de données sur les lignes actuellement modifiées:
L'incrément est aussi simple que cela:
UPDATE Tag t set t.count = t.count + 1 WHERE t.id = :id;
et la requête de décrémentation est:
UPDATE Tag t set t.count = t.count - 1 WHERE t.id = :id;
La requête UPDATE verrouille les lignes modifiées, empêchant les autres transactions de modifier la même ligne, avant que la transaction en cours ne soit validée (tant que vous n'utilisez pas READ_UNCOMMITTED
).
Par exemple, utilisez Optimistic Locking . Ceci devrait être la solution la plus simple pour résoudre votre problème. Pour plus de détails, voir -> https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html
Une autre solution éventuellement cohérente à ajouter:
counters_increment
séparée pour insérer chaque incrément de compteurcounters
principale à partir du counters_increment
Plus:
counters_increment
avec une écriture importante (par exemple, Cassandra, Redis)counters_increment_{period}
table par période (par exemple, jour) et suppression/recréation de la table entière après le traitement des données et leur utilisation