У меня есть двунаправленная связь «один ко многим» со следующими классами сущностей:
0 или 1 клиент <-> 0 или более заказов на продукт
При сохранении клиента Я хочу, чтобы связанные объекты заказа на продукт также сохранялись (поскольку их внешний ключ к «родительскому» клиенту мог быть обновлен).
Разумеется, все необходимые параметры CASCADE устанавливаются на стороне клиента. Но это не работает, если вновь созданный клиент сохраняется в первый раз при ссылке на существующий заказ продукта, как в этом сценарии:
Я испробовал несколько подходов, но ни один из них не дал ожидаемого результата. См. эти результаты ниже. Я прочитал все связанные вопросы здесь, но они мне не помогли. Я использую EclipseLink 2.3.0, чистые аннотации JPA 2.0 и JTA в качестве типа транзакции в базе данных Apache Derby (JavaDB) в памяти на GlassFish 3.1.2. Отношения сущностей управляются графическим интерфейсом JSF. Управление отношениями на уровне объектов работает (помимо сохранения), я проверил его с помощью тестов JUnit.
Подход 1) «По умолчанию» (на основе шаблонов классов NetBeans)
Клиент:
@Entity
public class Client implements Serializable, ParentEntity {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToMany(mappedBy = "client", cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH},
fetch= FetchType.LAZY)
private List<ProductOrder> orders = new ArrayList<>();
// other fields, getters and setters
}
ProductOrder:
@Entity
public class ProductOrder implements Serializable, ChildEntity {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne // owning side
private Client client;
// other fields, getters and setters
}
Общий фасад сохраняемости:
// Called when pressing "save" on the "create new..." JSF page
public void create(T entity) {
getEntityManager().persist(entity);
}
// Called when pressing "save" on the "edit..." JSF page
public void edit(T entity) {
getEntityManager().merge(entity);
}
Результат:
create() немедленно выдает это исключение:
Предупреждение: во время вызова EJB произошло системное исключение. Публичный метод ClientFacade недействителен javaee6test.beans.AbstractFacade.create(java.lang.Object) javax.ejb.EJBException: транзакция прервана...
Причина: javax.transaction.RollbackException: Транзакция отмечена для отката. ...
Причина: Исключение [EclipseLink-4002] (Eclipse Persistence Службы - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException Внутренний Исключение: java.sql.SQLIntegrityConstraintViolationException: заявление было прервано, потому что это привело бы к дублированию ключа значение в уникальном или первичном ключевом ограничении или уникальном указанном индексе с помощью «SQL120513133540930», определенного в «PRODUCTORDER». Код ошибки: -1 Вызов: ВСТАВИТЬ В PRODUCTORDER (ID, CLIENT_ID) ЗНАЧЕНИЯ (?, ?) bind => [2 привязанных параметра] Запрос: InsertObjectQuery(javaee6test.model.ProductOrder[ id=1 ]) ...
Вызвано: java.sql.SQLIntegrityConstraintViolationException: оператор был прервано, потому что это привело бы к дублированию значения ключа в уникальном или ограничение первичного ключа или уникальный индекс, идентифицированный «SQL120513133540930», определенный в «PRO-DUCTORDER». ...
Вызвано: org.apache.derby.client.am.SqlException: оператор был прерван потому что это привело бы к дублированию значения ключа в уникальном или ограничение первичного ключа или уникальный индекс, определяемый «SQL120513133540930», определенный в «PRODUCTOR-DER».
Я не понимаю этого исключения. редактировать () работает нормально. НО я хотел бы добавлять заказы на товары клиенту во время его создания, так что этого недостаточно.
Подход 2) только слияние ()
Изменения в универсальном фасаде персистентности:
// Called when pressing "save" on the "create new..." JSF page
public void create(T entity) {
getEntityManager().merge(entity);
}
// Called when pressing "save" on the "edit..." JSF page
public void edit(T entity) {
getEntityManager().merge(entity);
}
Результат:
При вызове create() выходные данные EclipseLink Logging говорят:
Отлично: ВСТАВИТЬ В КЛИЕНТ (ID, NAME , ADDRESS_ID) ЗНАЧЕНИЯ (?, ?, ?) bind => [3 связанных параметра]
, но НЕТ «ОБНОВЛЕНИЯ» в таблице заказов продуктов. Таким образом, отношения не устанавливаются. С другой стороны, edit() работает нормально.
Apporach 3) Id GenerationType.IDENTITY для обоих типов сущностей
Изменения как в классе заказа клиента, так и в классе продукта:
...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
Результат:
При вызове create() выходные данные EclipseLink Logging говорят:
Отлично: ВСТАВИТЬ В КЛИЕНТ (ИМЯ, ADDRESS_ID) ЗНАЧЕНИЯ (?, ?) bind => [2 параметры связаны]
Отлично: значения IDENTITY_VAL_LOCAL()
Отлично: ВСТАВИТЬ В PRODUCTORDER (ORDERDATE, CLIENT_ID) VALUES (?, ?) bind => [2 параметры связаны]
Отлично: значения IDENTITY_VAL_LOCAL()
Таким образом, вместо установления связи с заказом продукта, добавленным в список клиента, создается и сохраняется новый объект заказа продукта (!) и связь с этим объектом установлен. То же самое здесь, edit() работает нормально.
Подход 4) Комбинированный подход (2) и (3)
Результат: То же, что и подход (2).
У меня такой вопрос: Есть ли способ реализовать описанный выше сценарий? Как его можно заархивировать?Я бы хотел остаться с JPA (без решения для конкретного поставщика).