(1+N) 通过 OnetoOne 关联进行选择

发布于 2024-09-15 18:11:24 字数 1396 浏览 7 评论 0原文

考虑以下模型:

@Entity
public class User {

    @Id
    @Column(name = "USER_ID")
    private Long userId;

    @Column(name = "FIRST_NAME")
    private String firstName;

    @Column(name = "LAST_NAME")
    private String lastName;

    @OneToOne
    @PrimaryKeyJoinColumn
    private UserExt userExt;
...     //getters and setters

}

@Entity
public class UserExt {

    @Id
    @Column(name="USER_ID")
    private Long id;

    private String cdpId;

    private Date lastChanged;
...     //getters and setters
}

执行时:

Query query = session.createQuery("from User");
List<User> list = query.list();

Hibernate 执行

Hibernate: select user0_.USER_ID as USER1_0_, user0_.FIRST_NAME as FIRST2_0_, user0_.LAST_NAME as LAST3_0_, user0_.EXT_USERNAME as EXT4_0_ from USER user0_
Hibernate: select userext0_.USER_ID as USER1_1_0_, userext0_.cdpId as cdpId1_0_, userext0_.lastChanged as lastChan3_1_0_ from USER_EXT userext0_ where userext0_.USER_ID=?
Hibernate: select userext0_.USER_ID as USER1_1_0_, userext0_.cdpId as cdpId1_0_, userext0_.lastChanged as lastChan3_1_0_ from USER_EXT userext0_ where userext0_.USER_ID=?
...
...

使用具有特定属性的查询有效(选择 u.firstName、u.userExt.cdpId)。

但是,由于我想要完整的用户实体(“来自用户”),hibernate 会为第一个结果行中的每个结果行生成一个选择。

我不明白,因为默认的获取策略应该是“懒惰”而不是“急切”。强制其为 LAZY 并不能解决问题。

Considering the following model:

@Entity
public class User {

    @Id
    @Column(name = "USER_ID")
    private Long userId;

    @Column(name = "FIRST_NAME")
    private String firstName;

    @Column(name = "LAST_NAME")
    private String lastName;

    @OneToOne
    @PrimaryKeyJoinColumn
    private UserExt userExt;
...     //getters and setters

}

@Entity
public class UserExt {

    @Id
    @Column(name="USER_ID")
    private Long id;

    private String cdpId;

    private Date lastChanged;
...     //getters and setters
}

when executing :

Query query = session.createQuery("from User");
List<User> list = query.list();

Hibernate executes

Hibernate: select user0_.USER_ID as USER1_0_, user0_.FIRST_NAME as FIRST2_0_, user0_.LAST_NAME as LAST3_0_, user0_.EXT_USERNAME as EXT4_0_ from USER user0_
Hibernate: select userext0_.USER_ID as USER1_1_0_, userext0_.cdpId as cdpId1_0_, userext0_.lastChanged as lastChan3_1_0_ from USER_EXT userext0_ where userext0_.USER_ID=?
Hibernate: select userext0_.USER_ID as USER1_1_0_, userext0_.cdpId as cdpId1_0_, userext0_.lastChanged as lastChan3_1_0_ from USER_EXT userext0_ where userext0_.USER_ID=?
...
...

Using a query with specific properties works (select u.firstName, u.userExt.cdpId).

However since I want the full User Entity ("from User"), hibernate generates one select for each result row in the first.

I don't get it since the default fetch strategy should be LAZY not EAGER. Forcing it to LAZY didn't fix the problem.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

疯狂的代价 2024-09-22 18:11:24

这里有两个问题阻止延迟加载:

  1. OneToOne 的默认获取策略是 EAGER (请记住,LAZY 只是一个提示持久性提供者)。
  2. 如果关联不可为空(至少不使用字节码检测),LAZY 只能在 OneToOne 关联上工作。

9.1.23 OneToOne 注解

OneToOne 注释定义了一个
与另一个的单值关联
具有一对一关系的实体
多重性。通常情况下不会
需要指定相关的
明确的目标实体,因为它可以
通常可以从类型推断
被引用的对象。

表 16 列出了注释元素
可以为 OneToOne 指定
注释及其默认值。

@Target({METHOD, FIELD}) @Retention(RUNTIME)
公共@接口OneToOne {
    类 targetEntity() 默认 void.class;
    CascadeType[] Cascade() 默认 {};
    FetchType fetch() 默认 EAGER;
    布尔可选()默认true;
    字符串mappedBy()默认“”;
}

我测试了以下内容:

@OneToOne(optional = false, fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
private UserExt userExt;

并确认简单的 from User 仅加载所有用户

Hibernate: 
    select 
        user0_.USER_ID as USER1_0_,
        user0_.FIRST_NAME as FIRST2_0_,
        user0_.LAST_NAME as LAST3_0_
    from 
        USER user0_

并且不执行 N 个额外查询,UserExt 是延迟加载的。

因此,如果关联是强制性的,请使用适当的映射:)如果是非强制性的,则必须:

  • 使用字节码检测和无代理获取(请参阅下面的相关问题)
  • 使用假的 ManyToOne 代替(没有使用共享主键测试此映射)
  • 使用 join fetch 急切加载 UserExt 以避免 N 个后续选择(当然,这在某种程度上违背了单独的表)

请注意,当您使用 Query 接口时,Hibernate >= 3.x 会忽略 Fetch 注释。在这种情况下,您需要明确地编写该内容。这是一个示例:

EntityManager em = [...]
[...]
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);

Root<User> usersRoot = criteria.from(User.class);
usersRoot.fetch("Address", JoinType.LEFT);

List<User> users = em.createQuery(criteria).getResultList();

相关问题

参考

  • JPA 1.0规范
    • 第 9.1.23 节“OneToOne 注释”

There are two problems preventing lazy loading here:

  1. The default fetch strategy of OneToOne is EAGER (and keep in mind that LAZY is just a hint to the persistence provider).
  2. LAZY can only work on a OneToOne association if the association is non-nullable (at least without using bytecode instrumentation).

9.1.23 OneToOne Annotation

The OneToOne annotation defines a
single-valued association to another
entity that has one-to-one
multiplicity. It is not normally
necessary to specify the associated
target entity explicitly since it can
usually be inferred from the type of
the object being referenced.

Table 16 lists the annotation elements
that may be specified for a OneToOne
annotation and their default values.

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface OneToOne {
    Class targetEntity() default void.class;
    CascadeType[] cascade() default {};
    FetchType fetch() default EAGER;
    boolean optional() default true;
    String mappedBy() default "";
}

I tested the following:

@OneToOne(optional = false, fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
private UserExt userExt;

And confirm that a simple from User only loads all the users

Hibernate: 
    select 
        user0_.USER_ID as USER1_0_,
        user0_.FIRST_NAME as FIRST2_0_,
        user0_.LAST_NAME as LAST3_0_
    from 
        USER user0_

And doesn't perform N additional queries, the UserExt are loaded lazily.

So, if you association is mandatory, use the appropriate mapping :) And if it is non-mandatory, you'll have to either:

  • use bytecode instrumentation and no-proxy fetch (see related question below)
  • use a fake ManyToOne instead (didn't test this mapping with a shared primary key)
  • eager load the UserExt using a join fetch to avoid the N subsequent selects (of course, this somehow defeats the point of a separate table)

Note that Hibernate >= 3.x ignores the Fetch annotation when you use the Query interface. In that case, you need to write that explicitly. this is an example:

EntityManager em = [...]
[...]
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);

Root<User> usersRoot = criteria.from(User.class);
usersRoot.fetch("Address", JoinType.LEFT);

List<User> users = em.createQuery(criteria).getResultList();

Related questions

Reference

  • JPA 1.0 specification
    • Section 9.1.23 "OneToOne Annotation"
揪着可爱 2024-09-22 18:11:24

使用 -ToOne(例如 @ManyToOne 和 @OneToOne)时的默认获取策略是 fetch=FetchType.EAGER 而不是 fetch=FetchType.LAZY

但 Hibernate HQL 会覆盖默认的获取策略。如果您想仅使用一个查询来检索完全初始化的对象,则必须调用

from 
    User u
 left join fetch 
    u.userExt

Default fetching strategy when using -ToOne such as @ManyToOne and @OneToOne is fetch=FetchType.EAGER NOT fetch=FetchType.LAZY

But Hibernate HQL overrides default fetching strategy. If you want to retrieve a fully initialized object by using just one query you must call

from 
    User u
 left join fetch 
    u.userExt
墨落成白 2024-09-22 18:11:24

使用可选= true 与像这样的一对一关系,以避免您自己的获取策略的n+1 问题。

@OneToOne(fetch = FetchType.LAZY, optional=true)
@PrimaryKeyJoinColumn

use optional =true with a one-to-one relationship like this to avoid the n+1 issue with your own Fetch Strategy.

@OneToOne(fetch = FetchType.LAZY, optional=true)
@PrimaryKeyJoinColumn
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文