Настройте 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)
Ответы
Ответ 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
Это работает для меня
- создать таблицу, как это, используйте SERIAL.
CREATE TABLE webuser(
idwebuser SERIAL PRIMARY KEY,
...
)
- добавьте @GeneratedValue (стратегии = GenerationType.IDENTITY) в поле идентификатора.
@Entity
@Table(name="webuser")
class Webuser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// ....
}