Подтвердить что ты не робот

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

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

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 (не для конкретного поставщика).

4b9b3361

Ответ 1

Привет, у меня была такая же проблема сегодня, я спрашиваю список рассылки openJPA с этим электронным письмом:

Привет. У меня возникла проблема со вставкой и обновлением ссылки в том же объекте.

Я пытаюсь вставить новый объект (Экзамен), который имеет ссылку на другой объект (Person), и в то же время я хочу обновить атрибут (birthDate) объекта Person. Обновление никогда не происходит, хотя я устанавливаю CascadeType для ВСЕ. Единственный способ, которым это работает, - это упорство и после этого операция слияния. Это нормально? Нужно ли что-то менять?

Мне не нравится идея "ручного обновления" с использованием слияния в объекте Person, потому что я не знаю, сколько объектов (дочерний объект экзамена) пользователь хочет обновить.

Entities:

public class Exam{  
   @ManyToOne(cascade= CascadeType.ALL)
   @JoinColumn(name = "person_id")
   public Person person;
......
}

public class Person{
    private Date birthDate;
   @OneToMany(mappedBy = "person")
    private List<Exam> exams
.......
}

public class SomeClass{
   public void someMethod(){
      exam = new Exam()
      person.setBirthDate(new Date());
      exam.setPerson(person); 
      someEJB.saveExam(exam);
   }
}

public class someEJB(){

   public void saveExam(Exam exam){
        ejbContext.getUserTransaction().begin();
        em.persist(exam);
        //THIS WORKS
        em.merge(exam.getPerson());
        ejbContext.getUserTransaction().commit();       
   }

}

Нужно ли использовать метод MERGE для каждого дочернего объекта?

И ответ был следующим:

Похоже, ваша проблема в том, что экзамен является новым, но Person существующий и существующий Entity игнорируется при каскадировании сохраняющегося операция. Я считаю, что это работает так, как ожидалось.

Пока ваши отношения установлены в CascadeType.ALL, вы всегда можете измените ваш em.persist(экзамен); на em.merge(экзамен);. Это позаботится о сохраняя новый экзамен, и он также будет каскадировать вызов слияния человек.

Спасибо, Rick


Надеюсь, это поможет вам.

Ответ 2

Используйте аннотацию @Joincolumn в объекте 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
   @JoinColumn(name = "PRODUCTORDER_ID", referencedColumnName = "ID")
   //write your database column names into name and referencedColumnName attributes.
   private Client client;

   // other fields, getters and setters
   }

Ответ 3

Убедитесь, что вы устанавливаете обе стороны отношений, вы не можете просто добавить заказ клиенту, вам также нужно установить клиент заказа. Также вам необходимо объединить оба объекта, которые вы изменили. Если вы просто объедините клиент, тогда клиент заказа не будет объединен (хотя ваш каскад вы вызываете его объединение).

persist не работает, поскольку persist требует, чтобы сохраняемый объект был правильным для контекста персистентности, то есть не ссылался на отдельные объекты, он должен ссылаться на управляемые объекты.

Ваша проблема исходит от вас, отделяя объекты. Если вы не отделили объекты, у вас не было бы одинаковых проблем. Обычно в JPA вы создаете EntityManager, находите/запрашиваете свои объекты, редактируете/сохраняете их, совершаете вызов. Не требуется слияние.