在 B 中没有定义 @ManyToMany 属性(例如设置为)
(我不得不纠正自己,因为从 B 中省略 Set<A> as 似乎完全不会引发异常。)
如果您只想隐藏B 中的Set<A> 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<A> as)。但是,这样一来,您将无法像您所描述的那样执行查询。只是没办法。
如果B 中没有Set<A> as,持久性提供程序将向您咆哮(无法解析属性——在休眠的情况下)。如果您仅从非拥有方省略mappedBy,则持久性提供程序将不知道 that 关系的 other 方在哪里。要么使用mappedBy,要么在B 中创建一个inverse @JoinTable 注释(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 或(休眠)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 的发生。
- 关闭您的
EntityManager 或Session 在您已完成您的A 对象并且不再使用它们。如果您在 em 关闭之前调用 b.as,Hibernate(或任何其他持久性提供程序)将从数据库中延迟加载 A 对象。
- 在您的
B 实体的@ManyToMany 注释上将fetch 更改为FetchType.EAGER。这样,当您从数据库中获取 B 对象时,它们的 Set<A> 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 + "}";
}
}