JPA объединяет поля только для чтения - PullRequest
4 голосов
/ 12 июля 2010

У нас самая простая задача CRUD с JPA 1.0 и JAX-WS.
Допустим, у нас есть сущность Person.

@Entity
public class Person
{
   @Id
   private String email;

   @OneToOne(fetch = FetchType.LAZY)
   @JoinColumn(insertable = false, updatable = false)
   private ReadOnly readOnly;

   @Column
   private String name;      

   @XmlElement
   public String getEmail()
   {
      return email;
   }

   public void setEmail(String email)
   {
      this.email = email;
   }

   @XmlElement
   public Long getReadOnlyValue()
   {
      return readOnly.getValue();
   }

   // more get and set methods
}

Вот сценарий. Клиент делает запрос веб-службы для создания лица. На стороне сервера все просто. И это работает как ожидалось.

@Stateless
@WebService
public class PersonService
{
   @PersistenceContext(name = "unit-name")
   private EntityManager entityManager;

   public Person create(Person person)
   {
      entityManager.persist(person);

      return person;
   }
}

Теперь клиент пытается обновить пользователя, и именно здесь, как по мне, JPA показывает свою несостоятельность.

public Person update(Person person)
{
   Person existingPerson = entityManager.find(Person.class, person.getEmail());

   // some logic with existingPerson
   // ...      

   // At this point existingPerson.readOnly is not null and it can't be null
   // due to the database.
   // The field is not updatable.
   // Person object has readOnly field equal to null as it was not passed 
   // via SOAP request.
   // And now we do merge.

   entityManager.merge(person);

   // At this point existingPerson.getReadOnlyValue() 
   // will throw NullPointerException. 
   // And it throws during marshalling.
   // It is because now existingPerson.readOnly == person.readOnly and thus null.
   // But it won't affect database anyhow because of (updatable = false)

   return existingPerson;
}

Чтобы избежать этой проблемы, мне нужно предоставить set для объекта readOnly и сделать что-то подобное перед слиянием.

Person existingPerson = entityManager.find(Person.class, person.getEmail());
person.setReadOnlyObject(existingPerson.getReadOnlyObject()); // Arghhh!

Мои вопросы:

  • Это функция или просто несогласованность?
  • Как ты (или будешь вы) справляетесь с такими ситуациями? пожалуйста не советуйте мне использовать DTO.

1 Ответ

1 голос
/ 12 июля 2010

Это особенность или просто несоответствие?

Я не знаю, но я бы сказал, что это ожидаемое поведение с merge. Вот что происходит при вызове слияния для сущности:

  • существующий объект загружается в контексте постоянства (если его там еще нет)
  • состояние копируется из объекта для объединения в загруженную сущность
  • изменения, внесенные в загруженный объект, сохраняются в базе данных при сбросе
  • загруженный объект возвращается

Это прекрасно работает с простым регистром, но не работает, если вы получаете частично оцененный объект (с некоторыми полями или ассоциацией, установленными на null) со значением merge: в базе данных для пустых полей будет установлено значение null, это может быть не то, что вы хотите.

Как вы (или вы) справляетесь с такими ситуациями? Пожалуйста, не советуйте мне использовать DTO.

В этом случае вы должны использовать «ручное слияние»: загрузите существующую сущность, используя find, и обновите себе поля, которые вы хотите обновить, скопировав новое состояние, и пусть JPA обнаружит изменения и сбросит их в базу данных. .

...