JPA с JTA: сохранение объекта и объединение каскадных дочерних объектов

У меня есть двунаправленная связь «один ко многим» со следующими классами сущностей:

0 или 1 клиент <-> 0 или более заказов на продукт

При сохранении клиента Я хочу, чтобы связанные объекты заказа на продукт также сохранялись (поскольку их внешний ключ к «родительскому» клиенту мог быть обновлен).

Разумеется, все необходимые параметры CASCADE устанавливаются на стороне клиента. Но это не работает, если вновь созданный клиент сохраняется в первый раз при ссылке на существующий заказ продукта, как в этом сценарии:

  1. заказ продукта '1' создается и сохраняется. Работает отлично.
  2. Создан клиент '2', и заказ на продукт '1' добавлен в его список заказов на продукт. Затем оно сохраняется. Не работает.

Я испробовал несколько подходов, но ни один из них не дал ожидаемого результата. См. эти результаты ниже. Я прочитал все связанные вопросы здесь, но они мне не помогли. Я использую 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 (без решения для конкретного поставщика).

18
задан SputNick 14 June 2012 в 08:22
поделиться