it-swarm.com.ru

Настройте JPA, чтобы PostgreSQL генерировал значение первичного ключа

Таким образом, наш проект использует базу данных PostgreSQL, и мы используем JPA для работы с базой данных .... Мы создали объекты из базы данных с автоматическим создателем в Netbeans 7.1.2.

После небольших изменений значения нашего первичного ключа описываются так:

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Basic(optional = false)
@NotNull
@Column(name = "idwebuser", nullable = false)
private Integer idwebuser;

Проблема в том, что теперь приложение не является гибким, потому что, когда мы изменяем базу данных напрямую (используя SQL или другой инструмент) вместо того, чтобы переходить через приложение Java - сгенерированное значение ниже, чем фактическое значение идентификатора базы данных, и поэтому мы получаем ошибку во время создание новых юридических лиц.

Есть ли вероятность, что JPA может просто позволить базе данных автоматически генерировать идентификатор и затем получить его после процесса создания? Или что может быть лучшим решением? Спасибо.

EDIT Более конкретно: У нас есть таблица пользователей, и моя проблема в том, что, используя любой тип стратегии генерации, JPA вставляет новую сущность с указанным ее идентификатором генератора. Это неправильно для меня, потому что, если я сам внесу изменения в таблицу, добавив новые записи, значение GeneratedValue для приложения будет ниже текущего идентификатора - что приводит нас к исключению с дублированным идентификатором .... Можем ли мы это исправить ;)?

короткая заметка об ответе С моей стороны была небольшая ложь, потому что мы использовали PG Admin -> Показать первые 100 строк и отредактированные строки оттуда вместо использования select. В любом случае, оказывается, что этот редактор каким-то образом пропускает процесс обновления идентификатора, и поэтому даже в БД, когда мы пишем правильную вставку, он выполняется с неправильным идентификатором! Так что это была в основном проблема редактора, который мы использовали, чем база данных и приложение ...

теперь он работает даже с использованием @GeneratedValue(strategy=GenerationType.IDENTITY)

34
Atais

Учитывая определение таблицы:

CREATE TABLE webuser(
    idwebuser SERIAL PRIMARY KEY,
    ...
)

Используйте отображение:

@Entity
@Table(name="webuser")
class Webuser {

    @Id
    @SequenceGenerator(name="webuser_idwebuser_seq",
                       sequenceName="webuser_idwebuser_seq",
                       allocationSize=1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator="webuser_idwebuser_seq")
    @Column(name = "idwebuser", updatable=false)
    private Integer id;

    // ....

}

Именование tablename_columname_seq - это последовательность имен PostgreSQL по умолчанию для SERIAL, и я рекомендую придерживаться ее.

allocationSize=1 важен, если вам нужен Hibernate для взаимодействия с другими клиентами в базе данных.

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

  • Никогда не предполагайте, что для любого идентификатора n есть идентификатор n-1 или n+1
  • Никогда не предполагайте, что идентификатор n был добавлен или зафиксирован перед идентификатором меньше n или после идентификатора больше n. Если вы действительно осторожны с тем, как вы используете последовательности, вы можете сделать это, но вы никогда не должны пытаться; вместо этого запишите метку времени в вашей таблице.
  • Никогда не добавляйте и не вычитайте из идентификатора. Сравните их на равенство и больше ничего.

См. документацию PostgreSQL для последовательностей и типы последовательных данных .

Они объясняют, что приведенное выше определение таблицы в основном является сокращением для:

CREATE SEQUENCE idwebuser_id_seq;
CREATE TABLE webuser(
    idwebuser integer primary key default nextval('idwebuser_id_seq'),
    ...
)
ALTER SEQUENCE idwebuser_id_seq OWNED BY webuser.idwebuser;

... который должен помочь объяснить, почему мы добавили аннотацию @SequenceGenerator для описания последовательности.


Если вам действительно нужна последовательность без пропусков (например, нумерация чеков или счетов-фактур), см. последовательности без пропусков серьезно, избегайте такой схемы и никогда используйте ее для первичного ключа.


Note: Если ваше определение таблицы выглядит следующим образом:

CREATE TABLE webuser(
    idwebuser integer primary key,
    ...
)

и вы вставляете в него, используя (небезопасно, не используйте):

INSERT INTO webuser(idwebuser, ...) VALUES ( 
    (SELECT max(idwebuser) FROM webuser)+1, ...
);

или (небезопасно, никогда не делайте этого):

INSERT INTO webuser(idwebuser, ...) VALUES ( 
    (SELECT count(idwebuser) FROM webuser), ...
);

затем вы делаете это неправильно и должны переключиться на последовательность (как показано выше) или на правильную реализацию последовательности без пробелов с использованием таблицы заблокированных счетчиков (опять же, смотрите выше и смотрите " безглючная последовательность "postgresql" в гугле). Оба вышеперечисленных действия делают неправильно, если в базе данных работает более одного соединения.

70
Craig Ringer

Кажется, вы должны использовать генератор последовательности, как:

@GeneratedValue(generator="YOUR_SEQ",strategy=GenerationType.SEQUENCE)
2
Denis Zevakhin

Пожалуйста, попробуйте использовать GenerationType.TABLE вместо GenerationType.IDENTITY. База данных создаст отдельную таблицу, которая будет использоваться для генерации уникальных первичных ключей, а также будет хранить последний использованный номер идентификатора.

1
Leszek

Вы также можете сэкономить на некоторых усилиях, написав сценарий для массового преобразования общего GenerationType.IDENTITY в решение, предложенное выбранным ответом. Приведенный ниже сценарий имеет некоторые небольшие зависимости от того, как он был преобразован в исходный файл Java. Пусть покупатель будет бдителен!

После запуска скрипта:

  1. Найдите и замените import javax.persistence.Table; на import javax.persistence.Table; import javax.persistence.SequenceGenerator;.
  2. Переформатируйте исходный код в NetBeans следующим образом:
    1. Выберите все исходные файлы для форматирования.
    2. НажмитеAlt+Shift+F
    3. Подтвердите переформатирование.

Сохраните следующий скрипт как update-sequences.sh или аналогичный:

#!/bin/bash

# Change this to the directory name (package name) where the entities reside.
PACKAGE=com/domain/project/entities

# Change this to the path where the Java source files are located.
cd src/main/Java

for i in $(find $PACKAGE/*.Java -type f); do
  # Only process classes that have an IDENTITY sequence.
  if grep "GenerationType.IDENTITY" $i > /dev/null; then
    # Extract the table name line.
    LINE_TABLE_NAME=$(grep -m 1 @Table $i | awk '{print $4;}')
    # Trim the quotes (if present).
    TABLE_NAME=${LINE_TABLE_NAME//\"}
    # Trim the comma (if present).
    TABLE_NAME=${TABLE_NAME//,}

    # Extract the column name line.
    LINE_COLUMN_NAME=$(grep -m 1 -C1 -A3 @Id $i | tail -1)
    COLUMN_NAME=$(echo $LINE_COLUMN_NAME | awk '{print $4;}')
    COLUMN_NAME=${COLUMN_NAME//\"}
    COLUMN_NAME=${COLUMN_NAME//,}

    # PostgreSQL sequence name.
    SEQUENCE_NAME="${TABLE_NAME}_${COLUMN_NAME}_seq"

    LINE_SEQ_GENERATOR="@SequenceGenerator( name = \"$SEQUENCE_NAME\", sequenceName = \"$SEQUENCE_NAME\", allocationSize = 1 )"
    LINE_GENERATED_VAL="@GeneratedValue( strategy = GenerationType.SEQUENCE, generator = \"$SEQUENCE_NAME\" )"
    LINE_COLUMN="@Column( name = \"$COLUMN_NAME\", updatable = false )\n"

    # These will depend on source code formatting.
    DELIM_BEGIN="@GeneratedValue( strategy = GenerationType.IDENTITY )"
    # @Basic( optional = false ) is also replaced.
    DELIM_ENDED="@Column( name = \"$COLUMN_NAME\" )"

    # Replace these lines...
    #
    # $DELIM_BEGIN
    # $DELIM_ENDED
    #
    # With these lines...
    #
    # $LINE_SEQ_GENERATOR
    # $LINE_GENERATED_VAL
    # $LINE_COLUMN

    sed -i -n "/$DELIM_BEGIN/{:a;N;/$DELIM_ENDED/!ba;N;s/.*\n/$LINE_SEQ_GENERATOR\n$LINE_GENERATED_VAL\n$LINE_COLUMN/};p" $i
  else
    echo "Skipping $i ..."
  fi
done

При создании приложений CRUD с использованием идентификаторов атрибутов NetBeans не будут включать редактируемые поля ввода.

0
Dave Jarvis