2011-02-03 2 views
4

Я использую JPA через Play Framework.JPA EntityManager: merge() пытается создать новую строку в db - почему?

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

user = (User) Cache.get("user-auth-" + sessionAuthToken); 
if (user != null) { 
    user = user.merge(); // I believe this is the same as EntityManager.merge() 
} 

Однако, когда я сделать это, я получаю следующее сообщение об ошибке:

PersistenceException occured : 
    org.hibernate.exception.ConstraintViolationException: 
    could not insert: [models.User] 
... 
Caused by: com.mysql.jdbc.exceptions.jdbc4. 
    MySQLIntegrityConstraintViolationException: 
    Duplicate entry '1235411688335416533' for key 'authToken' 

похоже, что ее пытаются вставить новый пользователь, даже если этот пользователь должен быть, и уже есть в базе данных. Зачем сливать() делать это?

Или, возможно, я собираюсь сделать это совершенно неправильно - совет будет оценен.

+0

Является ли 'authToken' первичным ключом? – axtavt

+0

Не первичный ключ, но есть уникальное ограничение на него – sanity

+0

@sanity - в следующий раз, когда у вас есть проблема с гибернацией, возможно, стоит упомянуть этот факт. JPA - это интерфейс, проблема с реализацией Hibernate. Несколько подробностей относительно определения сущности не повредят - см. Ниже мой ответ, который идентифицирует вероятную причину вашей проблемы. –

ответ

2

Я считаю, что ваша проблема заключается в том, как Play управляет средой JPA (и транзакциями).

После получения запроса инфраструктура немедленно создает менеджер JPA и транзакцию. С этого момента все ваши объекты модели автоматически связаны с менеджером.

Play облегчает работу с этой моделью 2-мя способами:

  • Вы должны явно указать, что вы хотите, чтобы сохранить изменения в объект (через сохранить())
  • сделка совершается автоматически, если не будет исключение или пометить для отката (JPA.setRollbackOnly())

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

1

См. What is the proper way to re-attach detached objects in Hibernate?. Merge пытается записать состояние stale в db, чтобы перезаписать возможные другие одновременные обновления. Связанный вопрос упоминает session.lock(entity, LockMode.NONE); как возможное решение, но я его еще не пробовал.

1

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

Итак, проверьте первичный ключ User, возможно, он каким-то образом был поврежден или потерян.

3

Я думаю, что это может быть поблем с hashCode() и equals(). Если они не реализованы правильно, добавьте новый объект, который нужно вставить вместо обновления существующего.

+3

Нет, идентификация сущности выполняется через первичный ключ. – OrangeDog

+1

Это означает, что вы не можете сравнивать или хранить две несопровождаемые entites внутри набора, потому что они будут 'euqal', если вы не создадите' UUID' в конструкторе объекта. (Но это не привело бы к вашей проблеме) –

1

Это проблема определения сущности ; особенно в отношении первичных ключей/несохраненных значений.

Определение сущности должно быть правильным, поскольку Hibernate распознает его как «уже сохраненный». Например, наличие «null» в поле с нулевой версией может привести к тому, что Hibernate игнорирует любой существующий ID &, считая его несохраненным.

Это вопрос спящего режима, а не только JPA. JPA - это интерфейс - у вас возникают проблемы с конкретной реализацией.

Смежные вопросы