#java #jpa-2.2
#java #jpa-2.2
Вопрос:
Частью моего приложения Java EE является управление пользователями. Моя среда разработки — Netbeans 12.0 на Windows 10, JDK 14, Glassfish server 5.1, Apache Derby DB и JPA eclipse persistence. В настоящее время, на этапе разработки, я настроил JPA на удаление и создание таблиц при каждом перезапуске сервера.
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="listserviceDB">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>java:comp/DefaultDataSource</jta-data-source>
<properties>
<property name="eclipselink.ddl-generation.output-mode" value="both"/>
<property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
</properties>
</persistence-unit>
</persistence>
У меня есть компонент объекта «User», который настроен на использование автоматически сгенерированных первичных ключей для объекта:
public class User implements Serializable {
private static final long serialVersionUID = 1005L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@NotNull
private String userId;
@NotNull
private String password;
............................
@NotNull
private String userRole;
}
Всякий раз, когда запускается сервер, я заполнял все таблицы тестовыми данными из XML-файла, используя механизм JAXB. Вот пример моего UserList.xml для компонента сущности «User».
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<users>
<user>
<id>2</id>
<userId>AMackinzy</userId>
<password>password</password>
<userRole>update</userRole>
<firstName>Adam</firstName>
<lastName>Mc Keinzy</lastName>
<officePhone>(718)-815-8110</officePhone>
<cellPhone>(845)-340-5410</cellPhone>
<company>MMC</company>
</user>
<user>
<id>3</id>
<userId>AOzguer23</userId>
<password>password</password>
<userRole>consumer</userRole>
<firstName>Albert</firstName>
<lastName>Ozgur</lastName>
<officePhone>(213)-567-2390</officePhone>
<cellPhone>(917)-301-3491</cellPhone>
<company>Bernstern</company>
</user>
<user>
<id>1</id>
<userId>admin</userId>
<password>password</password>
<userRole>superadmin</userRole>
<firstName>Joseph</firstName>
<lastName>Ottaviano</lastName>
<officePhone>718-815-8111</officePhone>
<cellPhone>917-971-6854</cellPhone>
<company>Lemmen Inc.</company>
</user>
</users>
Когда приложение запущено и запущено, я могу визуально проверить данные через интерфейсные страницы JSF (Java Server Faces), и все выглядит нормально, что указывает на то, что код JAXB успешно загрузил исходные пользовательские данные в базу данных. Ужасная проблема возникает, когда я пытаюсь добавить нового пользователя с интерфейсной страницы JSF, которая завершается следующим исключением:
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.7.0.v20170811-d680af5): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.apache.derby.shared.common.error.DerbySQLIntegrityConstraintViolationException: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'SQL201230210444600' defined on 'PERSISTENCE_USER'.
Error Code: 20000
Call: INSERT INTO PERSISTENCE_USER (ID, CELLPHONE, COMPANY, DATEOFBIRTH, FIRSTNAME, LASTNAME, OFFICEPHONE, PASSWORD, USERID, USERROLE) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
bind => [10 parameters bound]
Query: InsertObjectQuery(org.me.mavenlistservicedb.entity.User@79460d95)
at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:331)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:905)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeNoSelect(DatabaseAccessor.java:967)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:637)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:564)
at org.eclipse.persistence.internal.sessions.AbstractSession.basicExecuteCall(AbstractSession.java:2093)
at org.eclipse.persistence.sessions.server.ClientSession.executeCall(ClientSession.java:309)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:270)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:256)
at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.insertObject(DatasourceCallQueryMechanism.java:405)
at org.eclipse.persistence.internal.queries.StatementQueryMechanism.insertObject(StatementQueryMechanism.java:165)
at org.eclipse.persistence.internal.queries.StatementQueryMechanism.insertObject(StatementQueryMechanism.java:180)
at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.insertObjectForWrite(DatabaseQueryMechanism.java:502)
at org.eclipse.persistence.queries.InsertObjectQuery.executeCommit(InsertObjectQuery.java:80)
at org.eclipse.persistence.queries.InsertObjectQuery.executeCommitWithChangeSet(InsertObjectQuery.java:90)
at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:314)
at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:911)
I am not sure why I am getting this error when the «User» entity leverages the Auto-key generation, the initial 3 pieces of data have been already loaded into the table, and that the auto-key generator has already been incremented to 4 (assuming the JPA starts generating keys from index=1)? So the first user I add through the JSF front end (which shares the same persistence code as JAXB did to add the initial data from the XML file to the table) should be assigned id=5?
Первоначально я подумал, может быть, поля идентификатора в XML-файле (т.Е. 1) Как-то связаны с этой ошибкой, но опять же со схемой автоматической генерации ключей идентификатор в XML должен быть переопределен автоматически сгенерированным значением JPA?
Я уже прочитал много сообщений на этом форуме и в других местах об ошибке, но не нашел никакого общего знаменателя с моей ситуацией, кроме ошибки. Спасибо за любое четкое объяснение основной причины этой ошибки и того, как я могу ее исправить, не имея приложения для генерации первичных ключей.
Обновление 1: оставшиеся данные в таблице
После тщательного расследования я обнаружил, что в таблице User при запуске приложения всегда остаются оставшиеся данные, что нарушает политику удаления и создания, которую я выбрал в persistence.xml файл (возможно, проблема с JPA?) В этом случае, когда контейнер вызывает мой одноэлементный класс @PostConstruct аннотированный метод loadData() для загрузки исходных данных из XML-файла, автоматическая генерация ключа начинается с 1, и это вызывает конфликт с одной из оставшихся строк, имеющих тот же идентификатор. Я изменил код, чтобы удалить все строки из таблицы при запуске сервера, и проблема исчезла, однако я вижу следующее исключение в файле журнала, которое не вызывает никаких проблем или прерываний для приложения:
Caused by: Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.7.0.v20170811-d680af5): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [id] of class [org.me.mavenlistservicedb.entity.User] is mapped to a primary key column in the database. Updates are not allowed.
at org.eclipse.persistence.exceptions.ValidationException.primaryKeyUpdateDisallowed(ValidationException.java:2551)
at org.eclipse.persistence.mappings.foundation.AbstractDirectMapping.writeFromObjectIntoRowWithChangeRecord(AbstractDirectMapping.java:1265)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildRowForUpdateWithChangeSet(ObjectBuilder.java:1773)
at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.updateObjectForWriteWithChangeSet(DatabaseQueryMechanism.java:1043)
at org.eclipse.persistence.queries.UpdateObjectQuery.executeCommitWithChangeSet(UpdateObjectQuery.java:84)
at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:314)
at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:911)
at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:810)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:108)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:85)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2979)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1892)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1874)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1824)
at org.eclipse.persistence.internal.sessions.CommitManager.commitChangedObjectsForClassWithChangeSet(CommitManager.java:273)
at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:131)
at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:4384)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1491)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1581)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.issueSQLbeforeCompletion(UnitOfWorkImpl.java:3256)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.issueSQLbeforeCompletion(RepeatableWriteUnitOfWork.java:355)
at org.eclipse.persistence.transaction.AbstractSynchronizationListener.beforeCompletion(AbstractSynchronizationListener.java:158)
... 29 more
Я определенно не обновляю и не изменяю какие-либо первичные ключи, и я оставил эту задачу функции автоматической генерации, поэтому я не уверен, почему я получаю это исключение? (если потребуется исходный код, я был бы рад предоставить его).
Ответ №1:
В этом случае Eclipse JPA выбирает стратегию на основе последовательности (последовательности SEQ_GEN, созданной в базе данных; она вызывает next value
эту последовательность), это не автоматически сгенерированный столбец.
Итак, в этом случае исключение нарушения ограничений понятно — сначала при запуске приложение создает 3 пользователя с идентификаторами: 1, 2, 3, не касаясь последовательности, а затем вы пытаетесь создать 4-го пользователя, но первое значение в последовательности равно 1.
Вы можете включить свойство show-sql
jpa для отслеживания SQLL, отправленных в базу данных.
Комментарии:
1. Спасибо за ответ. Я использую Apache derby DB с eclipse persistence JPA. Как мне получить доступ к hibernate в этой конфигурации?
2. @Darvin Я обновил свой ответ, чтобы ссылаться на Eclipse Persistence JPA. Я тестировал на обоих провайдерах (hibernate и Eclipse Persistence JPA), и оба ведут себя одинаково — идентификаторы генерируются из последовательности.
3. Большое вам спасибо, Патриция. Я наивно предположил, что JPA будет игнорировать идентификаторы (т. Е. <id>1</id> ) из XML-файла и автоматически генерировать идентификаторы для тех 3 фрагментов данных, которые я загрузил из файла. В этом случае, как мне заставить JPA автоматически генерировать последовательность для этих 3 фрагментов данных? Еще раз спасибо.
4. Идея такова: если вы JPA для инициализации, просто не указывайте идентификатор. Если вы не используете JPA, используйте последовательность для генерации идентификаторов. Пожалуйста, включите подробный код, отвечающий за инициализацию пользователей.
5. Проблема исчезла, главным образом потому, что я решил проблему с исключением duplicate primary key, которое сбивало контейнер с ног при запуске сервера, что приводило к каскаду необычных исключений, и иногда это препятствовало переходу контейнера в полностью рабочее состояние. Возможно, обработка исключений в коде контейнера должна лучше защищаться от ошибок в пользовательском приложении и выполнять лучшую работу по обнаружению ошибок и изоляции сбоев.