Опасность: Ваш вопрос подразумевает, что вы, возможно, делаете ошибку проектирования - вы пытаетесь использовать последовательность базы данных для «делового» значения, которое представляется пользователям, в данном случае номера счетов.
Не используйте последовательность, если вам нужно что-то большее, чем проверка значения на равенство. У него нет порядка. У него нет «расстояния» от другого значения. Это просто равно или не равно.
Откат:
Последовательности обычно не подходят для такого использования, потому что изменения в последовательностях не откатываются с транзакцией ROLLBACK
. См. Нижние колонтитулы в последовательность функций и CREATE SEQUENCE
.
Откаты ожидаемые и нормальные. Они происходят из-за:
- взаимоблокировки, вызванные конфликтующим порядком обновления или другими блокировками между двумя транзакциями;
- откат оптимистической блокировки в Hibernate;
- временные ошибки клиента;
- обслуживание сервера администратором базы данных;
- конфликты сериализации в
SERIALIZABLE
или транзакции изоляции моментального снимка
... и более.
Ваше приложение будет иметь "дыры" в нумерации счетов, где происходят такие откаты. Кроме того, нет гарантии заказа, поэтому вполне возможно, что транзакция с более поздним порядковым номером будет зафиксирована раньше (иногда намного ранее), чем транзакция с более поздним номером.
Чанкинг:
Это также нормально для некоторых приложений, включая Hibernate, захватывать более одного значения из последовательности за раз и передавать их внутренним транзакциям. Это допустимо, потому что вы не должны ожидать, что сгенерированные последовательностью значения будут иметь какой-либо значимый порядок или быть сопоставимыми в любом случае, кроме равенства. Для нумерации счетов вы также хотите заказать, поэтому вы не будете вообще счастливы, если Hibernate захватит значения 5900-5999 и начнет раздавать их с 5999, считая вниз или поочередно затем вниз, так что ваши номера счетов идут: n, n + 1, n + 49, n + 2, n + 48, ... n + 50, n + 99, n + 51, n + 98, [n + 52 потеряно для отката], n + 97, ... . Да, в Hibernate .
существует распределитель *1048* high-then-low
Это не поможет, если вы не определите отдельные @SequenceGenerator
s в своих сопоставлениях, Hibernate также любит совместно использовать одну последовательность для каждого сгенерированного идентификатора. Некрасиво.
Правильное использование:
Последовательность подходит, только если only требует, чтобы нумерация была уникальной. Если вам также нужно, чтобы он был монотонным и порядковым, вам следует подумать об использовании обычной таблицы с полем счетчика через UPDATE ... RETURNING
или SELECT ... FOR UPDATE
(«пессимистическая блокировка» в Hibernate) или через оптимистическую блокировку Hibernate. Таким образом, вы можете гарантировать безупречные приращения без дырок или неупорядоченных записей.
Что делать вместо:
Создать таблицу только для счетчика. Имейте одну строку в этом, и обновите это, поскольку Вы читаете это. Это заблокирует его, не позволяя другим транзакциям получать ID, пока ваш не подтвердит.
Поскольку все операции выполняются последовательно, постарайтесь, чтобы транзакции, генерирующие идентификаторы счетов-фактур, были короткими и избегали выполнения в них большего объема работы, чем нужно.
CREATE TABLE invoice_number (
last_invoice_number integer primary key
);
-- PostgreSQL specific hack you can use to make
-- really sure only one row ever exists
CREATE UNIQUE INDEX there_can_be_only_one
ON invoice_number( (1) );
-- Start the sequence so the first returned value is 1
INSERT INTO invoice_number(last_invoice_number) VALUES (0);
-- To get a number; PostgreSQL specific but cleaner.
-- Use as a native query from Hibernate.
UPDATE invoice_number
SET last_invoice_number = last_invoice_number + 1
RETURNING last_invoice_number;
Также вы можете:
- Определите сущность для invoice_number, добавьте столбец
@Version
и позвольте оптимистической блокировке позаботиться о конфликтах;
- Определите сущность для invoice_number и используйте явную пессимистическую блокировку в Hibernate для выбора ... для обновления, а затем для обновления.
Все эти опции будут сериализовать ваши транзакции - либо путем отката конфликтов с помощью @Version, либо блокируя их (блокируя) до фиксации держателя блокировки. В любом случае, последовательности без пропусков действительно замедляют эту область вашего приложения, поэтому используйте последовательности без пропусков только тогда, когда это необходимо.
@GenerationType.TABLE
: заманчиво использовать @GenerationType.TABLE
с @TableGenerator(initialValue=1, ...)
. К сожалению, хотя GenerationType.TABLE позволяет вам указать размер выделения через @TableGenerator, он не дает никаких гарантий относительно порядка упорядочения или отката. См. Спецификации JPA 2.0, раздел 11.1.46 и 11.1.17. В частности "В этой спецификации не определено точное поведение этих стратегий. и сноска 102 " Переносимые приложения не должны использовать аннотацию GeneratedValue для других постоянных полей или свойств [чем @Id
первичные ключи] ". Поэтому небезопасно использовать @GenerationType.TABLE
для нумерации, которая требует отсутствия пробелов или нумерации, которая не относится к свойству первичного ключа, если только ваш провайдер JPA не дает больше гарантий, чем стандарт.
Если вы застряли с последовательностью :
Автор отмечает, что у них есть существующие приложения, использующие БД, которые уже используют последовательность, поэтому они застряли с ней.
Стандарт JPA не гарантирует, что вы можете использовать сгенерированные столбцы, кроме @Id, вы можете (а) игнорировать это и продолжать, пока ваш провайдер позволяет вам, или (б) выполнить вставку с настройками по умолчанию значение и перечитать из базы данных. Последний безопаснее:
@Column(name = "inv_seq", insertable=false, updatable=false)
public Integer getInvoiceSeq() {
return invoiceSeq;
}
Из-за insertable=false
поставщик не будет пытаться указать значение для столбца. Теперь вы можете установить подходящий DEFAULT
в базе данных, например, nextval('some_sequence')
, и это будет выполнено. Возможно, вам придется перечитать сущность из базы данных с помощью EntityManager.refresh()
после ее сохранения - я не уверен, что поставщик персистентности сделает это за вас, и я не проверил спецификацию или не написал демонстрационную программу.
Единственным недостатком является то, что кажется, что столбец нельзя сделать @ NotNull или nullable=false
, поскольку поставщик не понимает, что в базе данных есть значение по умолчанию для столбца. Это все еще может быть NOT NULL
в базе данных.
Если вам повезет, другие ваши приложения также будут использовать стандартный подход: либо опустить столбец последовательности в списке столбцов INSERT
, либо явно указать ключевое слово DEFAULT
в качестве значения вместо вызова nextval
, Это не составит труда выяснить, включив log_statement = 'all'
в postgresql.conf
и выполнив поиск по журналам. Если они это сделают, тогда вы можете фактически переключить все на пропуски, если решите, что вам нужно, заменив DEFAULT
на триггерную функцию BEFORE INSERT ... FOR EACH ROW
, которая устанавливает NEW.invoice_number
из таблицы счетчиков.