Hibernate аннотация для серийного типа PostgreSQL - PullRequest
7 голосов
/ 02 февраля 2010

У меня есть таблица PostgreSQL, в которой у меня есть столбец inv_seq, объявленный как serial.

У меня есть класс бобов Hibernate для отображения таблицы. Все остальные столбцы читаются правильно, кроме этого столбца. Вот объявление в классе бина Hibernate:

....
  ....
        @GeneratedValue(strategy=javax.persistence.GenerationType.AUTO)
        @Column(name = "inv_seq")
        public Integer getInvoiceSeq() {
            return invoiceSeq;
        }

         public void setInvoiceSeq(Integer invoiceSeq) {
        this.invoiceSeq = invoiceSeq;
    }
  ....
....

Правильно ли объявлено?
Я могу видеть последовательные числа, сгенерированные столбцом в базе данных, но я не могу получить к ним доступ в классе java.

Пожалуйста, помогите.

Ответы [ 4 ]

15 голосов
/ 01 августа 2012

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

Не используйте последовательность, если вам нужно что-то большее, чем проверка значения на равенство. У него нет порядка. У него нет «расстояния» от другого значения. Это просто равно или не равно.

Откат: Последовательности обычно не подходят для такого использования, потому что изменения в последовательностях не откатываются с транзакцией 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 из таблицы счетчиков.

2 голосов
/ 01 августа 2012

Я обнаружил, что hibernate 3.6 пытается использовать одну последовательность для всех сущностей, когда вы устанавливаете ее в AUTO, поэтому в моем приложении я использую IDENTITY в качестве стратегии генерации.

@Id
@Column(name="Id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id; 

@ У Крейга было несколько очень хороших замечаний о том, что номера счетов нужно увеличивать, если вы представляете их пользователям, и он предложил использовать для этого таблицу. Если вы в конечном итоге используете таблицу для хранения следующего идентификатора, вы можете использовать отображение, подобное этому.

@Column(name="Id")
@GeneratedValue(strategy=GenerationType.TABLE,generator="user_table_generator")
@TableGenerator(
    name="user_table_generator", 
    table="keys",
    schema="primarykeys",
    pkColumnName="key_name",
    pkColumnValue="xxx",
    valueColumnName="key_value",
    initialValue=1,
    allocationSize=1)
private Integer id; 
0 голосов
/ 31 июля 2012

Я использую Postgres + Hibernate в своих проектах, и вот что я делаю:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "hibernate_sequence")
@SequenceGenerator(name = "hibernate_sequence", sequenceName = "hibernate_sequence")
@Column(name = "id", unique = true, nullable = false)
protected Long id;

public Long getId() {
    return id;
}
public void setId(Long id) {
    this.id = id;
}

У меня это прекрасно работает.

0 голосов
/ 02 февраля 2010

В зависимости от вашей ситуации это может не сработать. В Hibernate открыта ошибка, которая документирует это поведение.

http://opensource.atlassian.com/projects/hibernate/browse/HHH-4159

Если вы открыты для использования файла отображения вместо аннотаций, я смог воссоздать проблему (NULL в столбцах SERIAL, которые не являются частью первичного ключа). Использование «сгенерированного» атрибута элемента свойства заставляет Hibernate перечитать строку после вставки, чтобы получить сгенерированное значение столбца:

<class name="Ticket" table="ticket_t">
    <id name="id" column="ticket_id">
        <generator class="identity"/>
    </id>
    <property name="customerName" column="customer_name"/>
    <property name="sequence" column="sequence" generated="always"/>
</class>

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...