Hibernate 在访问关联实体的 id 时生成 SQL 查询

Posted

技术标签:

【中文标题】Hibernate 在访问关联实体的 id 时生成 SQL 查询【英文标题】:Hibernate generating SQL queries when accessing associated entity's id 【发布时间】:2011-04-13 19:16:20 【问题描述】:

我的 Hibernate 实体看起来像这样(getter 和 setter 被忽略了):

@Entity
public class EntityA 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    private EntityB parent;


@Entity
public class EntityB extends SuperEntity 
    @OneToMany(mappedBy = "parent")
    @Fetch(FetchMode.SUBSELECT)
    @JoinColumn(name = "parent_id")
    private Set<EntityA> children;


@MappedSuperclass
public class SuperEntity 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private long itemId;

当我查询 EntityA 时,它加载正常,父关联被 Hibernate 代理替换(因为它是 Lazy)。如果我想访问父母的 id,我执行以下调用:

EntityA entityA = queryForEntityA();
long parentId = entityA.getParent().getItemId();

据我了解,调用不应往返于数据库,因为 Id 存储在 EntityA 表中,并且代理应该只返回该值。但是,在我的情况下,这会生成一个获取 EntityB 的 SQL 语句,然后才返回 Id。

如何调查问题?这种不正确行为的一些可能原因是什么?

【问题讨论】:

【参考方案1】:

据我了解,调用不应往返于数据库,因为 Id 存储在 EntityA 表中,并且代理应该只返回该值。

使用属性访问类型。您遇到的行为是字段访问类型的“限制”。以下是 Emmanuel Bernard 的解释:

这很不幸,但在意料之中。这是字段级访问的限制之一。 基本上我们无法知道 getId() 确实只是去访问 id 字段。所以我们需要加载整个对象以保证安全。

所以把你的代码改成:

@Entity
public class EntityA 
    private EntityB parent;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    public EntityB getParent() 
        return parent; 
    
    ...


@MappedSuperclass
public class SuperEntity 
    private long itemId;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    public long getItemId()  
        return itemId;
    
    ...

相关问题

Hibernate Annotations - Which is better, field or property access?

参考文献

Proxy loaded on getId-call when using annotations on fields proxy getId => why sql is generated ! HHH-3718(如果这个问题可以解决的话)

【讨论】:

天哪,谢谢,我从来没有想过这是个问题。 HHH-3718 终于修复了:hibernate.atlassian.net/browse/HHH-3718【参考方案2】:

你说的有道理 - 因为 EntityA 包含父 ID,所以它不会产生数据库命中。我只是不确定 getParent() 调用是否实际加载了 EntityB 对象,无论您是否只对 ID 感兴趣。如果您想保存 DB 命中,您可以尝试将子集合(和任何其他字段)标记为 Lazy。

@Entity
public class EntityB : SuperEntity 
    @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
    @Fetch(FetchMode.SUBSELECT)
    @JoinColumn(name = "parent_id")
    private Set<EntityA> children;

【讨论】:

OneToMany 默认是 LAZY。【参考方案3】:

至于 Hibernate:This behavior 自 Hibernate 5.2.12 起已更改。

【讨论】:

以上是关于Hibernate 在访问关联实体的 id 时生成 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

休眠等于方法

Hibernate @OneToMany 关联尝试设置空 FK 值

MySQL 使用 JPA + Hibernate 的 9 个高性能技巧

在 equals 和 hashcode 方法中使用自动生成的 Hibernate 实体对象的 id

hibernate注解配置

Hibernate反向工程使用心得