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是界面 – 您在使用特定实现时遇到问题。