JPA QL选择ManytoMany关系的非拥有方?

时间:2011-12-20 03:41:13

标签: java hibernate orm

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

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

但是我不想暴露B中的set,所以没有在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。 有什么建议吗? 谢谢

1 个答案:

答案 0 :(得分:1)

  

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

(我必须纠正自己,因为似乎从Set<A> as省略B完全不会引发异常。)

如果您只想隐藏Set<A> as中的B,那么您可以将其声明为private并使用双向映射(使用mappedBy属性关系的非拥有方面)。在这种情况下,以下查询成功运行:

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);

(示例片段全部基于答案下半部分中的表格,数据和实体。)

打印:

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;

你基本上想要一个单向关系(省略mappedBy来自B - 低于 - 或下降Set<A> as)。但是,这样,您将无法像您所描述的那样执行查询。只是没办法它会起作用。

如果Set<A> as中没有B,则持久性提供程序会咆哮(属性无法解析 - 如果是Hibernate)。如果您只从非拥有方省略mappedBy,那么持久性提供程序将不知道那个关系的其他方面在哪里。您可以使用mappedBy或在@JoinTable中创建反向 B注释(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. 完成<{1}}对象之后关闭EntityManagerSession ,不再使用它们。如果在A关闭之前调用b.as,Hibernate(或任何其他持久性提供程序)将从数据库中懒惰地加载em个对象。
  2. A实体的B注释更改@ManyToMany上更改为fetch。这样,当您从数据库中获取FetchType.EAGER个对象时,他们的B属性也会被Hiberate加载(我认为可以使用不同的Set<A> as设置进​​行进一步控制。
  3. 我建议您使用双向映射(不要忽略CascadeType)或使mappedBy成为拥有方(但前者会非常有用)。

    test.a

    B

    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 |
    +-------+-------------+------+-----+---------+
    | id    | int(11)     | NO   | PRI | 0       |
    | data  | varchar(45) | YES  |     | NULL    |
    +-------+-------------+------+-----+---------+
    

    数据

    test.a

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

    test.b

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

    test.a_has_b

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

    实体

    A

    +------+------+
    | 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 + "}";
      }
    }