У меня есть двунаправленное отношение "один ко многим" со следующими классами сущностей:
0 или 1 клиент = 0 или более заказов на продукт
При сохранении сущности клиента я хочу, чтобы связанные сущности объекта продукта также сохранялись (поскольку их внешний ключ к "родительскому" клиенту, возможно, был обновлен).
Конечно, все необходимые параметры CASCADE устанавливаются на стороне клиента. Но он не работает, если вновь созданный клиент сохраняется в первый раз, ссылаясь на существующий заказ продукта, как в этом сценарии:
- заказ продукта '1' создается и сохраняется. Прекрасно работает.
- Клиент "2" создан, а в список заказов на продукт добавлен заказ "1". Затем он сохраняется. Не работает.
Я пробовал несколько аплогов, но ни один из них не показал ожидаемого результата. Ниже приведены результаты. Здесь я читал все связанные вопросы, но они мне не помогли. Я использую EclipseLink 2.3.0, чистые JPA 2.0 Annotations и JTA как тип транзакции в DBA в базе данных 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 public void javaee6test.beans.AbstractFacade.create(java.lang.Object) javax.ejb.EJBException: транзакция отменена...
Вызвано: javax.transaction.RollbackException: транзакция, отмеченная для отката....
Вызвано: Исключение [EclipseLink-4002] (Устойчивость Eclipse Услуги - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException Внутренний Исключение: java.sql.SQLIntegrityConstraintViolationException: состояние было прервано, поскольку оно вызвало бы дубликат ключа значение в уникальном или первичном ключевом ограничении или уникальном индексе по "SQL120513133540930", определенному в "PRODUCTORDER". Код ошибки: -1 Call: INSERT INTO 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'.
Я не понимаю это исключение. edit() отлично работает. НО я хотел бы добавить заказ продукта клиенту в момент его создания, поэтому этого недостаточно.
Подход 2) merge() только
Изменения в фазе общего сохранения:
// 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, ИМЯ, ADDRESS_ID) ЗНАЧЕНИЯ (?,?,?) bind = > [3 привязанных параметра]
но NO "UPDATE" в таблице заказа продукта. Таким образом, связь не установлена. С другой стороны, edit(), с другой стороны, отлично работает.
Apporach 3) Id GenerationType.IDENTITY для обоих типов сущностей
Изменения в классе заказа клиента и продукта:
...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
Результат:
В create() в журнале EclipseLink Logging указано:
Изобразительное: ВСТАВИТЬ В КЛИЕНТ (ИМЯ, ADDRESS_ID) ЗНАЧЕНИЯ (?,?) bind = > [2 связанные с параметрами]
Изобразительное: значения IDENTITY_VAL_LOCAL()
Изобразительное: INSERT INTO PRODUCTORDER (ORDERDATE, CLIENT_ID) ЗНАЧЕНИЯ (?,?) Bind = > [2 связанные с параметрами]
Изобразительное: значения IDENTITY_VAL_LOCAL()
таким образом, вместо того, чтобы устанавливать отношение к заказу на продукт, добавленному в список клиентов, создается и сохраняется новый объект заказа prodcut (!) и устанавливается связь с этим объектом. То же самое, edit() отлично работает.
Apporach 4) Подход (2) и (3) комбинированный
Результат: То же, что и подход (2).
Мой вопрос: Есть ли способ реализовать описанный выше сценарий? Как он может быть заархивирован? Я хотел бы остаться с JPA (не для конкретного поставщика).