Комментарии 26
Надо сделать ClientId праймари ключом, тогда достаточно селекта + insert (или find + saveorupdate), в случае constraint violation — просто вернуть аргументы процедуры, т.к. сущность уже создана. Второй вариант — дописать ON CONFLICT/ON DUPLICATE KEY/IGNORE или подобное прямо в процедуре.
Распределенный кэш можно использовать, но будет ли он выгоднее чем процедура еще вопрос.
Не знаю как там дальше используется ClientId, но нужен ли он вообще как отдельная сущность?
Спасибо за статью!
Интереса ради — сколько времени у вас съедает блокировка на базе которая еще и по сети распределена ?
А что делать в случае нескольких нод? В этом случае всегда нужен какой-то внешний арбитр.
В продакшене это не работает: там как минимум два сервера, а данная синхронизация работает только для одной JVM. Правильных решений три: переделать клиента, переделать API, использовать транзакции на стороне DB.
Решение 1
А пардон, где написано, что ваш сервис singleton, чтобы синхронизироваться по this? Тогда уж поставьте syncronized(MyService.class). Не будет работать в кластере.
Решение 2
Менеджмент «протухания» clientId ложится в данном случае на плечи GС.
Вообще жесть! Зачем вы вводите людей в заблуждение? Где заявлено про "протухание" и GC? String.intern() складывает строки в constant pool, который лежит в permanent generation. И скоро вместо протухания вы получите снижение производительности и как финал OutOfMemory.
all: никогда так не делайте и вообще забудьте про intern()!
Решение 3
Не работает в кластере.
Решение 4
Лочить по сети N нод для каждого getUserById() — ну, ну. Кроме того, привлекается какое-то левое решение.
Решение 0
Единственно правильное — использовать старый добрый механизм транзакций базы данных с повтором. Например так:
https://www.baeldung.com/spring-retry
@Retryable(value = { SQLException.class }, maxAttempts = 3, backoff = @Backoff(delay = 1000))
@Transactional
public Client getOrCreateUser(String clientId)
сервис singleton
Сервис singleton — см. исходники. В случае использования scope prototype синхронизироваться по this действительно нельзя.
И скоро вместо протухания вы получите снижение производительности и как финал OutOfMemory
Согласен, данное решение на практике неприменимо.
Не работает в кластере
Да, я об этом указал в статье: «решения 1-3 вполне подойдут для небольших одноинстансных сервисов».
Лочить по сети N нод для каждого getUserById()
Блокировка будет не на каждый getUserById, а только на операцию создания Client и блокироваться будут только запросы конкретного клиента.
Решение 0 Единственно правильное
Если задача именно решить проблему со вставкой, то вполне можно использовать Ваш вариант. Основная цель статьи больше показать способы синхронизации запросов, возможно просто пример с integrity constraint violation не самый подходящий, так как эта проблема имеет решения и без синхронизации.
Основная цель статьи больше показать способы синхронизации запросов, возможно просто пример с integrity constraint violation не самый подходящий
Проблема синхронизации к Spring-у вообще не имеет отношения. Если у вас под низом rdbms, то самое натуральное — это использовать ее средства синхронизации (напр. select for update). Даже если операция ничего не сохраняет в базу, но требует консистенции, проще будет создать таблицу mutex-ов и синхронизироваться по ней. Если же база распределенная и без acid, а запросы делаются асинхронно, то тут универсального решения нет — требуется адаптировать архитектуру под преследуемые цели, и в любом случае это будет сложнее и хуже.
В моем проекте сначала попытался словить ConstraintIntegrityViolation exception и после пробовал получить entity ещё раз. Из-за производительности сильно не переживал т.к. у Hibernate есть first-level caching вшитый.
Потом перешёл на Retryable mechanism, более чисто и понятней.
Синхронизация клиентских запросов в Spring