java - JPA QL 用于选择多对多关系的非拥有方?

标签 java hibernate orm

我在 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。 有什么建议吗? 谢谢

最佳答案

no @ManyToMany attribute defined in B (e.g Set as)

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

如果您只想隐藏Set<A> asB ,那么你可以将其声明为 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 查询是以下 native 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 也在那里,这样你就不必后者了)。

如果您只有来自 A 的单向映射走向B您只能通过其 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 后尝试访问延迟加载的实体,您将收到错误或( hibernate )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 后对象并且不再使用它们。如果您调用b.as之前em关闭 Hibernate(或任何其他持久性提供程序)将加载 A从数据库中延迟对象。
  2. 在您的B上实体的@ManyToMany注释更改fetchFetchType.EAGER 。这样,当您获取B时数据库中的对象Set<A> as属性也将由 Hiberate 加载(我认为可以使用不同的 CascadeType 设置进行进一步的控制)。

我建议您改用双向映射(不要省略 mappedBy )或创建 B拥有方(但前者会很有用)。

表格

测试.a

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

测试.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       |       |
+-------+---------+------+-----+---------+-------+

数据

测试.a

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

测试.b

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

test.a_has_b

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

实体

A

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

关于java - JPA QL 用于选择多对多关系的非拥有方?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8570512/

相关文章:

java - 在不同位置关闭 FileOutputStream 的最佳编码实践

JAVA - 如何避免 MySQL 中的值重复?

java - HQL 左连接条件

orm - 如何向 One2many 字段中的 Many2many 字段添加多个值?Odoo 10

java - 当 ServerCall 在第一个 ServerInterceptro 中关闭时如何忽略 gRPC(java) ServerInterceptor 的其余部分?

java - 更新android studio 3.0.1时发现安装区有一些冲突

hibernate - 更新域类和刷新 session ?

mysql - Hibernate 按子表的 Id 排序

orm - ServiceStack.OrmLite如何按属性忽略属性

model-view-controller - DDD/MVC : how to avoid hitting the Repository from the View?