Hibernate传入瞬时实体调用merge时为何执行两次查询?
JPA规范规定:如果X是新的实体实例,会创建一个新的托管实体实例X',并将X的状态复制到新的托管实体实例X'中。
因此我通过以下两个示例尝试理解上述内容:
案例1:
entityManager.getTransaction().begin(); //假设数据库中不存在ID为2的Employee记录 Employee e=new Employee(); e.setId(2); e.setName("john"); /* 我理解上下文刷新时,下面的操作应仅执行INSERT查询 但Hibernate会执行两次查询:先SELECT后INSERT */ entityManager.merge(e); // entityManager.getTransaction().commit();
案例2:
entityManager.getTransaction().begin(); //假设数据库中存在ID为3的Employee记录 Employee e=new Employee(); e.setId(3); e.setName("arthur"); /* 我理解上下文刷新时,下面的操作应执行失败,因为会出现主键重复的记录 但Hibernate会执行两次查询:先SELECT后UPDATE */ entityManager.merge(e); // entityManager.getTransaction().commit();
我是Hibernate新手,我的merge理解是否正确?如果不正确,我遗漏了什么?另外merge似乎也适用于瞬时实体,这是否正确?
你的理解误区与merge的实际逻辑
1. 案例1为何先SELECT再INSERT?
你手动给瞬时实体设置了主键ID(e.setId(2)),这导致Hibernate无法直接判定该实体是全新待插入还是已存在于数据库。按照JPA规范,只要实体携带了主键值,Hibernate必须先执行SELECT查询校验对应ID的记录是否存在——哪怕你确定该ID不存在,这也是框架遵循规范的默认校验逻辑。
若要让Hibernate直接执行INSERT跳过前置SELECT,可采用两种方式:
- 不手动设置主键,依靠
@GeneratedValue等主键生成策略让Hibernate自动生成ID; - 给实体添加
@Version乐观锁字段,或通过自定义状态判断逻辑明确告知Hibernate这是全新实体。
2. 案例2为何不报错反而执行UPDATE?
你的核心误解在于:merge的本质不是“插入新实体”,而是将传入实体的状态同步到持久化上下文的托管实体中。当你传入携带已存在ID的瞬时实体时,Hibernate会先查询数据库中对应的托管实例,然后把你传入实体的状态(此处为name字段)复制到该托管实例上,最终在事务提交时执行UPDATE操作。
merge从不会因主键重复抛出异常——它的设计目标就是处理实体状态同步,无论传入的是瞬时、游离还是托管实体,都会先查找对应ID的托管实例:不存在则创建新托管实例并执行INSERT,存在则更新状态并执行UPDATE。
3. merge是否适用于瞬时实体?
完全正确。merge是JPA专门用于**非托管实体(瞬时、游离)**与持久化上下文同步的方法,它可以接收瞬时实体,将其状态同步到持久化上下文中的托管实体(若不存在则创建新的托管实体)。
内容的提问来源于stack exchange,提问作者enigmatic-coder

