JPA EntityManager:merge()试图在db中创建一个新行 – 为什么?

我通过Play Framework使用JPA。

我正在检查是否缓存了User对象,如果是,我将检索它并merge()它以便我可以更新字段并稍后保存更改:

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' 

它似乎试图插入一个新用户,即使该用户应该是,并且已经在数据库中。 为什么merge()会这样做?

或者也许我会以完全错误的方式解决这个问题 – 建议会受到赞赏。

我相信你的问题是Play管理JPA环境(和事务)的方式。

收到请求后,框架会立即创建JPA管理器和事务。 从那一刻起,所有模型实体都自动链接到管理器。

Play有助于以两种方式使用此模型:

  • 您必须明确指出要保存对象的更改(通过save())
  • 除非存在exception或您将其标记为回滚,否则将自动提交事务(JPA.setRollbackOnly())

通过运行“合并”,您尝试向Manager添加已存在的实体,这会导致唯一键exception。 如果您只是从缓存中加载实体,那么一旦完成,您就可以修改并调用save(),它将起作用。

我认为它可能是hashCode()equals() 。 如果没有正确实现,我将插入一个新实体而不是更新现有实体。

请参阅在Hibernate中重新附加分离对象的正确方法是什么? 。 合并尝试将陈旧状态写入数据库,以覆盖可能的其他并发更新。 链接的问题提到了session.lock(entity, LockMode.NONE); 作为一种可能的解决方案,我还没试过。

如果authToken不是主键,则可能正在合并的User实例的主键与数据库中对应的主键不匹配,因此merge()认为它是新User并尝试插入它。

因此,检查User的主键,可能已经损坏或以某种方式丢失。

这是一个实体定义问题 ; 特别是关于主键/未保存的值

实体定义必须正确,以便Hibernate将其识别为“已保存”。 例如,在可空版本字段中使用“null”可能会导致Hibernate忽略任何现有ID并将其视为未保存。

这是一个Hibernate问题,而不仅仅是JPA。 JPA是界面 – 您在使用特定实现时遇到问题。