You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hibernate 6执行SELECT查询后自动触发UPDATE查询的原因排查

为什么Hibernate调用get()查询后自动执行了UPDATE语句?

我定义了User2和Company两个实体,User2通过@ManyToOne(fetch = LAZY)关联Company。但调用Session的get()方法查询User2后,Hibernate先执行了SELECT查询,紧接着又执行了UPDATE,这是怎么回事?

User2实体代码

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
@Builder
@Entity
@Table(name = "users")
public class User2 {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    @Column(unique = true)
    private String username;

    @Embedded
    @EqualsAndHashCode.Exclude
    private PersonalInfo personalInfo;

    @Enumerated(STRING)
    @EqualsAndHashCode.Exclude
    private Role role;

    @Type(JsonType.class)
    @EqualsAndHashCode.Exclude
    private String info;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "company_id")
    @EqualsAndHashCode.Exclude
    @ToString.Exclude
    private Company company;
}

Company实体代码

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
@Builder
@Entity
public class Company {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    private String name;
}

主程序代码

public class HibernateEntityMappingRunner {
    public static void main(String[] args) {
        try (SessionFactory sessionFactory = buildSessionFactory();
             Session session = sessionFactory.openSession()
        ) {
            session.beginTransaction();

            User2 user2 = session.get(User2.class, 2);

            session.getTransaction().commit();
        }
    }
}

Hibernate控制台日志

Hibernate: 
    select
        u1_0.id,
        u1_0.company_id,
        u1_0.info,
        u1_0.birth_date,
        u1_0.firstname,
        u1_0.lastname,
        u1_0.role,
        u1_0.username 
    from
        users u1_0 
    where
        u1_0.id=?
Hibernate: 
    update
        users 
    set
        company_id=?,
        info=?,
        birth_date=?,
        firstname=?,
        lastname=?,
        role=?,
        username=? 
    where
        id=?

原因分析

这是Hibernate脏检查机制导致的:当事务提交时,Hibernate会对比持久化上下文中实体的当前状态和从数据库加载时的快照状态,一旦发现差异就会触发UPDATE语句。具体到你的场景,可能的触发点有这些:

  • JSON字段序列化差异:info字段用了@Type(JsonType.class),如果数据库存储的JSON字符串和实体中String对象的序列化/反序列化逻辑不一致(比如空格、换行、键的排序不同),Hibernate会误认为该字段被修改。
  • 嵌入式对象的equals/hashCode问题:PersonalInfo作为嵌入式对象,若未正确实现equals和hashCode,或者其内部字段存在类型精度差异(比如数据库是DATE类型,实体用LocalDateTime,加载时自动补全时间为00:00:00,和快照对比产生差异),会被判定为脏数据。
  • 懒加载关联的代理对象差异:虽然用了LAZY加载Company,但Hibernate会加载company_id外键值。如果实体中Company代理对象的标识和快照中的外键值在equals判断时返回false,也会触发脏检查。

验证与解决方法

  1. 验证脏检查:在session.getTransaction().commit()前调用session.evict(user2),如果不再执行UPDATE,即可确认是脏检查导致。
  2. 修复JSON字段:统一JSON序列化/反序列化的格式(比如固定缩进、键排序),确保和数据库存储完全一致。
  3. 修正嵌入式对象:为PersonalInfo正确实现equals和hashCode,同时匹配数据库字段的类型(比如用LocalDate对应DATE类型)。
  4. 优化更新策略:在User2实体上添加@DynamicUpdate,Hibernate只会更新实际修改的字段,方便定位触发更新的具体字段。

内容的提问来源于stack exchange,提问作者Dont you mind

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 03:33:28