调用hibernateTamplate的merge方法,抛出异常ConstraintViolationException 违背一致性异常,merge 时难道不是当数据库存在此主键时update ,不存在时insert 吗,怎么会报主键冲突呢?即hibernate认为数据库没有,所以merge 时执行了insert。
ConstraintViolationException
刚开始把这个单词都理解错了:此意违背数据完整性约束(database integrity constraint),它是造成SpringDataIntegrityViolationException 的最常见的原因: spring-dataIntegrityviolationexception;英语不达标不利于解决问题
StackOverflow: merge performs insert instead of update
jpa-hibernate-merge-performs-insert-instead-of-update 有回答:
I had a version column which was not set when seed data was inserted into database. Hence all the problems with update and delete
是version字段引起的,hibernate用version字段做乐观锁,所以问题又回到了之前遇到entity删除删不掉了 : https://quizix.gitbooks.io/hibernate/content/hibernate_user_guide/9_locking.html
A version or timestamp property can never be null for a detached instance. Hibernate detects any instance with a null version or timestamp as transient, regardless of other unsaved-value strategies that you specify. Declaring a nullable version or timestamp property is an easy way to avoid problems with transitive reattachment in Hibernate, especially useful if you use assigned identifiers or composite keys. 对于一个detached实例,version和timestamp属性永远不能为null。Hibernate检测到任何version或者timestamp属性为null的instance均视为transient。忽视你指定的其他未被保存的值。Hibernate中声明一个空的version或者timestamp属性是一个简单的办法来避免转换重附着,当你使用了指定Id或组合键时非常有用
回答中另一个例子:hibernate-merge-may-insert-new-record 级联查出的属性entity(User 的UserDetail 属性),在保存的时候并没有Id ,所以merge 时执行了insert而不是update。
Detached–>Entity的状态
detach 不受EntityManager管理的对象,但仍代表数据库里的一个记录;文中列举了三个属性:
- 许多JPA方法不支持在detached实例
- Retrieval by navigation from detached objects is not supported, so only persistent fields that have been loaded before detachment should be used. 这句话没读懂,大意应该是只有在detached前的persistent域才能使用(好像还能部分field受管理,即取回部分,取决于策略)
- 修改一个detached实例并不会影响到数据库,除非merge,重新接收
EntityManager 管理 另外提一点关于merge(文中的级联merge与detach就不讲了):
The content of the specified detached entity object is copied into an existing managed entity object with the same identity (i.e. same type and primary key). If the EntityManager does not manage such an entity object yet a new managed entity object is constructed. The detached object itself, however, remains unchanged and detached.
对于merge 方法,如果EntityManager 中没有同样Id的实例,则new一个,否则复制其状态;但是这个待merge对象(merge 的参数)本身依然没有变化、依然detached,但merge 返回的是受管理的实例。 有人就提问了JPA的persist 与merge 的区别:(jpa-entitymanager-why-use-persist-over-merge)[https://stackoverflow.com/questions/1069992/jpa-entitymanager-why-use-persist-over-merge] 第一个回答中我们得知,persist 区别在,传入的实例就是那个受管理的实例,后续对实例的修改(flush 后)也会影响到数据库
再来看Entity的所有状态: Working with JPA Entity Objects entity都有哪些状态?Transient Persistent Detached JAP的Entity状态变迁(来自StackOverflow回答:https://stackoverflow.com/a/30168342) Hibernate的状态变迁: hibernate对EntityManager 的实现在类:org.hibernate.impl.SessionImpl hibernate-core-3.6.10.Final.jar baeldung对各种hibernate API的详细讲解,非常值得阅读:hibernate-save-persist-update-merge-saveorupdate
回到问题
问题简化成了:从数据库取出一个Entity,其version字段为NULL—>修改某个属性—>hibernate的merge 抛出异常ConstraintViolationException 我大致追了下hibernate的代码,看到每个数据库(不准确)操作请求都被转换成一个Event,比如MergeEvent 、LoadEvent 、UpdateEvent ,然后都会提供默认的Listener去处理这些事件,最终的DefaultMergeEventListener我是没看明白。 我猜测是,因为受管理的Entity的version为NULL,则认为它是Transient状态的,于是就new了一个,把merge参数那个实例的所有属性都复制过来(包括primaryKey),然后执行insert
|