【问题标题】:JPA QL for selecting non-owning side of ManytoMany relationship?JPA QL 用于选择多对多关系的非拥有方?
【发布时间】:2011-12-20 03:41:13
【问题描述】:

我在 A 和 B 之间有一个多对多关系,其中 A 是拥有方。 我在 A 类中定义了 ManyToMany:

@ManyToMany(....)
private Set<B> bs

但我不想公开 B 中的集合,因此 B 中没有定义 @ManyToMany 属性(例如 Set as)。 当我想使用 JPA QL 选择 A 实例映射到的所有 B 实体时,它会出现问题。我做不到:

"SELECT b FROM B b JOIN b.as A WHERE A.id = :id"

我可以在@ManyToMany 属性中设置 fetch = Fetch.EAGER 并使用 A.getBs() 来获取相关的 B。但我不喜欢使用 Fetch.EAGER。 有什么建议吗? 谢谢

【问题讨论】:

    标签: java hibernate orm


    【解决方案1】:

    在 B 中没有定义 @ManyToMany 属性(例如设置为)

    (我不得不纠正自己,因为从 B 中省略 Set&lt;A&gt; as 似乎完全不会引发异常。)

    如果您只想隐藏B 中的Set&lt;A&gt; as,则可以将其声明为private 并使用双向映射(使用non-owningmappedBy 属性/em> 关系的一侧)。在这种情况下,以下查询运行成功:

    EntityManager em = ...
    String sql = "SELECT b FROM B b JOIN b.as a WHERE a.id = :id";
    TypedQuery<B> tq = em.createQuery(sql, B.class);
    tq.setParameter("id", 100);
    for (B b : tq.getResultList())
      System.out.println(b);
    

    (示例 sn-ps 均基于答案下方的表格、数据和实体。)

    打印出来:

    B{id=333, data=b}
    B{id=999, data=bbb}
    

    此 JPQL 查询是以下原生 SQL 查询的映射:

    SELECT b.id, b.data 
    FROM a, b, a_has_b 
    WHERE a.id = a_has_b.a_id 
    AND b.id = a_has_b.b_id 
    AND a_id = 100;
    

    您基本上想要一个单向关系(通过从B 中省略mappedBy——在下面——或删除Set&lt;A&gt; as)。但是,这样一来,您将无法像您所描述的那样执行查询。只是没办法。

    如果B 中没有Set&lt;A&gt; as,持久性提供程序将向您咆哮(无法解析属性——在休眠的情况下)。如果您仅从非拥有方省略mappedBy,则持久性提供程序将不知道 that 关系的 other 方在哪里。要么使用mappedBy,要么在B 中创建一个inverse @JoinTable 注释(mappedBy 在那里,所以你不必使用后者)。

    如果您只有从 AB 的单向映射,您只能通过其 id 获取 A 实体并找到与其关联的所有 B 实体,就像这样(正是您所描述的):

    TypedQuery<A> tq = em.createQuery("SELECT a FROM A a WHERE id = :id", A.class);
    tq.setParameter("id", 100);
    for (A a : tq.getResultList())
      for (B b : a.bs)
        System.out.println(b);
    

    这对我有用,无需指定 fetch = Fetch.EAGER,并打印与以前相同的内容。

    请注意,如果Fetch.LAZY 生效,如果您在关闭EntityManager 或(休眠)Session 后尝试访问延迟加载的实体,则会收到错误消息。您对此无能为力:这是它应该工作的方式。

    EntityManager em = ...
    // fetch your B instances
    List<B> bs = ...
    em.close();
    for (B b : bs)
      for (A a : b.as)
        // *BOOM*
        System.out.println(a);
    

    您可以做两件事来防止 BOOM 的发生。

    1. 关闭您的EntityManagerSession 您已完成您的A 对象并且不再使用它们。如果您在 em 关闭之前调用 b.as,Hibernate(或任何其他持久性提供程序)将从数据库中延迟加载 A 对象。
    2. 在您的B 实体的@ManyToMany 注释上将fetch 更改为FetchType.EAGER。这样,当您从数据库中获取 B 对象时,它们的 Set&lt;A&gt; as 属性也将由 Hiberate 加载(我认为可以使用不同的 CascadeType 设置来进行进一步的控制)。

    我建议您改用双向映射(不要省略 mappedBy)或将 B 设为拥有方(但前者会非常有用)。

    表格

    test.a

    +-------+-------------+------+-----+---------+
    | Field | Type        | Null | Key | Default |
    +-------+-------------+------+-----+---------+
    | id    | int(11)     | NO   | PRI | 0       |
    | data  | varchar(45) | YES  |     | NULL    |
    +-------+-------------+------+-----+---------+
    

    test.b

    +-------+-------------+------+-----+---------+
    | Field | Type        | Null | Key | Default |
    +-------+-------------+------+-----+---------+
    | id    | int(11)     | NO   | PRI | 0       |
    | data  | varchar(45) | YES  |     | NULL    |
    +-------+-------------+------+-----+---------+
    

    test.a_has_b

    +-------+---------+------+-----+---------+-------+
    | Field | Type    | Null | Key | Default | Extra |
    +-------+---------+------+-----+---------+-------+
    | a_id  | int(11) | NO   | PRI | 0       |       |
    | b_id  | int(11) | NO   | PRI | 0       |       |
    +-------+---------+------+-----+---------+-------+
    

    数据

    test.a

    +-----+------+
    | id  | data |
    +-----+------+
    | 100 | a    |
    | 200 | aa   |
    | 300 | aaa  |
    +-----+------+
    

    test.b

    +-----+------+
    | id  | data |
    +-----+------+
    | 333 | b    |
    | 666 | bb   |
    | 999 | bbb  |
    +-----+------+
    

    test.a_has_b

    +------+------+
    | a_id | b_id |
    +------+------+
    |  100 |  333 |
    |  300 |  333 |
    |  100 |  999 |
    +------+------+
    

    实体

    一个

    @Entity
    @Table(schema = "test", name = "a")
    public final class A {
    
      @Id
      public int id;
    
      @Basic
      public String data;
    
      @ManyToMany(targetEntity = B.class,
                  cascade = CascadeType.ALL,
                  fetch = FetchType.LAZY)
      @JoinTable(schema = "test",
                 name = "a_has_b",
                 joinColumns = @JoinColumn(table = "a",
                                           name = "a_id",
                                           referencedColumnName = "id"),
                 inverseJoinColumns = @JoinColumn(table = "b",
                                                  name = "b_id",
                                                  referencedColumnName = "id"))
      public Set<B> bs = Sets.newLinkedHashSet();
    
      @Override
      public String toString() {
        return "A{id=" + id + ", data=" + data + "}";
      }
    }
    

    B

    @Entity
    @Table(schema = "test", name = "b")
    public final class B {
    
      @Id
      public int id;
    
      @Basic
      public String data;
    
      // omitting mappedBy results in a uni-directional relationship
      @ManyToMany(targetEntity = A.class,
                  cascade = CascadeType.ALL,
                  fetch = FetchType.LAZY,
                  mappedBy = "bs")
      public Set<A> as = Sets.newLinkedHashSet();
    
      @Override
      public String toString() {
        return "B{id=" + id + ", data=" + data + "}";
      }
    }
    

    【讨论】:

    • 您好,Kohanyi,感谢您的回答。所以这意味着我无法使用 JPA QL 来查询非拥有方而不指定在拥有方中设置的属性。
    • 如果我没记错,指定 fetch=fetch.LAZY 会在我访问数据时给我异常会话关闭。让我再检查一下,让你知道。
    • 您好,如果不是 fetch.EAGER,我会出现这个错误:org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: A.bs, no session or session was closed跨度>
    • @KhueVu 我已经更新了关于您收到的错误的答案(中间部分)。如果您必须在会话关闭后访问延迟加载的状态:而是急切地加载状态,或者在您完成操作和读取状态之前不要关闭会话。
    • @KhueVu 我真的想为你指出一件事:我是匈牙利人,在匈牙利使用东方名称顺序(就像在新加坡——你的祖国一样)等等,你可以叫我罗伯特,这是我的名字。我真的很想说这个,因为英国人(来自使用西方名字顺序的国家的人)总是称我为 Kohányi,这很烦人。这就是它应该工作的方式。当一个人(具有西方定名)来到匈牙利时,他/她总是以他/她的西方定名称呼或提及。 (没有冒犯的意思——真的,一点也没有!)
    猜你喜欢
    • 2011-07-12
    • 2017-08-25
    • 2012-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-26
    • 2018-06-23
    • 2014-01-26
    相关资源
    最近更新 更多