Подтвердить что ты не робот

Настройте 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 может просто позволить базе данных генерировать идентификатор автоматически, а затем получить его после процесса создания? Или что может быть лучшим решением? Спасибо.

ИЗМЕНИТЬ Более конкретно: У нас есть таблица пользователей, и моя проблема заключается в том, что с использованием любого типа генерации стратегии JPA вставляет новый объект с указанным им идентификатором-генератором. Это неправильно для меня, потому что, если я вношу изменения в таблицу самостоятельно, добавляя новые записи, приложение GeneratedValue для приложения ниже текущего ID - что приводит нас к исключению с дублированным идентификатором. Можем ли мы исправить это?)?

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

теперь он работает даже с помощью @GeneratedValue(strategy=GenerationType.IDENTITY)

4b9b3361

Ответ 1

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

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 для совместной работы с другими клиентами в базе данных.

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

  • Никогда не предполагайте, что для любого id n существует id n-1 или n+1
  • Никогда не предполагайте, что id 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 для описания последовательности.


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


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

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" в Google). Оба указанных выше делают неправильные вещи, если в базе данных работает более одного соединения.

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

После запуска script:

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

Сохраните следующее script как 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 атрибуты идентификатора не будут содержать редактируемые поля ввода.

Ответ 5

Это работает для меня

  1. создать таблицу, как это, используйте SERIAL.
CREATE TABLE webuser(
    idwebuser SERIAL PRIMARY KEY,
    ...
)
  1. добавьте @GeneratedValue (стратегии = GenerationType.IDENTITY) в поле идентификатора.
@Entity
@Table(name="webuser")
class Webuser {

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

    // ....

}