2015-07-31 6 views
2

В моем приложении я моделирую счет-фактуру. В моей стране (Италия) каждый счет-фактура должен иметь уникальный последовательный номер без отверстий, который каждый год должен перезапускать с 1.Spring JPA с формированием фискальной последовательности Hibernate

Я долго и долго думал о лучшем способе его реализации, но я не нашел хорошего гида об этом. На данный момент у меня есть JpaRepository где я мой заказ синхронизированный save() метод, в котором я получаю последний идентификатор, используемый:

SELECT MAX(numero) FROM Invoice WHERE YEAR(date) = :year 

Проблема этого подхода заключается в том, что это не очень безопасно, потому что разработчик должен знать, что сохранить следует делать только с этой конкретной услугой.

Вместо этого я хотел бы больше подхода, который скрыт от разработчика. Я думал использовать метод @Prepersist в @EntityListeners. Это звучит хорошо, но получаете менеджер сущностей внутри этого класса не так просто .... поэтому, возможно, не является оптимальным местом ...

Наконец-то я думал о спящем перехватчик ....

Пожалуйста, дайте мне некоторые намеки. Проблема кажется довольно общей проблемой; так что, возможно, еще есть хорошая практика.

Благодаря

+0

Существует еще одна проблема с использованием метода 'synchronized'. Синхронизация работает в рамках одной JVM, а не через JVM. Итак, если вы масштабируетесь в кластере, каждая JVM будет иметь свой собственный «синхронизированный» метод, но между ними не будет никакой синхронизации. – manish

ответ

5

Эта проблема может быть разбита на следующие требования:

  1. Последовательная уникальный: Генерация чисел в последовательности, начиная с заданного значения (скажем, 1000001), а затем всегда приращение на фиксированное значение (скажем, 1).
  2. Отсутствие пробелов: Между номерами не должно быть пробелов. Таким образом, если первое число сгенерировано 1000001, приращение равно 1, и на данный момент создано 200 номеров, последнее число должно быть 1000201.
  3. Параллелизм: Несколько процессов должны иметь возможность генерировать числа в одно и то же время.
  4. Генерация при создании: Цифры должны быть сгенерированы во время создания записи.
  5. Нет эксклюзивных замков: Для генерации чисел не требуется никаких эксклюзивных замков.

Любое решение может соответствовать только 4 из этих 5 требований. Например, если вы хотите гарантировать 1-4, каждый процесс должен будет принимать блокировки, чтобы никакой другой процесс не мог генерировать и использовать тот же самый номер, который он сгенерировал. Поэтому введение 1-4 в качестве требований будет означать, что 5 придется отпустить. Аналогично, если вы хотите гарантировать 1, 2, 4 и 5, вам нужно убедиться, что только один процесс (поток) генерирует число за раз, поскольку уникальность не может быть гарантирована в параллельной среде без блокировки. Продолжайте эту логику, и вы поймете, почему невозможно гарантировать все эти требования одновременно.

Теперь решение зависит от того, какой из 1-5 вы готовы пожертвовать. Если вы готовы пожертвовать # 4, но не # 5, вы можете запустить пакетный процесс во время простоя, чтобы сгенерировать числа.Однако, если вы разместите этот список перед бизнес-пользователем (или финансовым парнем), они попросят вас соблюдать 1-4, поскольку № 5 является чисто технической проблемой (для них), и поэтому они не захотят быть беспокоился об этом. В этом случае возможна следующая стратегия:

  • Выполнение всех возможных вычислений, необходимых для формирования счета-фактуры, в результате чего шаг создания номера счета-фактуры является самым последним. Это гарантирует, что все исключения, которые могут произойти, произойдут до того, как будет сгенерирован номер, а также чтобы убедиться, что блокировка выполняется в течение очень короткого промежутка времени, тем самым не слишком сильно влияя на параллелизм или производительность приложения.
  • Сохраните отдельную таблицу (например, DOCUMENT_SEQUENCE), чтобы сохранить следы последнего сгенерированного номера.
  • Незадолго до сохранения счета-фактуры возьмите эксклюзивную блокировку на уровне строк в таблице последовательностей (скажем, уровень изоляции SERIALIZABLE), найдите нужное значение последовательности для немедленного использования и сохранения счета-фактуры. Это не должно занимать слишком много времени, потому что чтение строки, увеличение ее значения и сохранение записи должно быть достаточно коротким. Если возможно, сделайте эту короткую транзакцию вложенной транзакцией на основную.
  • Сохраняйте достаточно доступный тайм-аут базы данных, чтобы одновременные потоки, ожидающие блокировки SERIALIZABLE, не слишком быстро выходили из строя.
  • Сохраните всю эту операцию в цикле повтора, повторите попытку не менее 10 раз, прежде чем полностью отказаться. Это гарантирует, что если очередь блокировки слишком быстро наращивается, операции все еще проверяются несколько раз, прежде чем полностью отказаться. Многие коммерческие пакеты имеют количество попыток, равное 40, 60 или 100.

В дополнение к этому, если это возможно и разрешено в соответствии с вашими принципами разработки базы данных, установите уникальное ограничение в столбце номера счета-фактуры, чтобы дублировать значения не хранится ни при каких условиях.

Весна дает вам все инструменты для реализации этого.

У меня есть sample app, который демонстрирует использование всех этих элементов вместе.

+0

Спасибо за ваше очень чистое объяснение. У вас недостаточно требований, и да, я хочу гарантировать 1-4. Я взглянул на ваш код, но не вижу исключительной блокировки в таблице последовательности ... В конце мой вопрос: хорошая идея генерировать число в методе @PrePersist в слушателе, поэтому я уверен что любой сервис или репозиторий вы вызываете, в конце число всегда генерируется автоматически. Благодаря! – drenda

+0

В моем примере избегаются эксклюзивные блокировки, но вместо этого используется уникальное ограничение на номер счета.Ограничение может быть удалено, и изолированность транзакции может быть изменена на 'SERIALIZABLE', хотя это приводит к гораздо большему количеству исключений (тайм-аут транзакции) с более чем 10 одновременными пользователями. Вы можете использовать маршрут '@ PrePersist'. – manish

+0

Так что неплохая идея использовать @PrePersist также, если EntityManager не инъектируется? – drenda

Смежные вопросы