我在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。 有什么建议吗? 谢谢
答案 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
,以便您不必后者)。
如果你只有从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
关闭之前调用b.as
,Hibernate(或任何其他持久性提供程序)将从数据库中懒惰地加载em
个对象。A
实体的B
注释更改@ManyToMany
上更改为fetch
。这样,当您从数据库中获取FetchType.EAGER
个对象时,他们的B
属性也会被Hiberate加载(我认为可以使用不同的Set<A> as
设置进行进一步控制。我建议您使用双向映射(不要忽略CascadeType
)或使mappedBy
成为拥有方(但前者会非常有用)。
B
+-------+-------------+------+-----+---------+
| Field | Type | Null | Key | Default |
+-------+-------------+------+-----+---------+
| id | int(11) | NO | PRI | 0 |
| data | varchar(45) | YES | | NULL |
+-------+-------------+------+-----+---------+
+-------+-------------+------+-----+---------+
| Field | Type | Null | Key | Default |
+-------+-------------+------+-----+---------+
| id | int(11) | NO | PRI | 0 |
| data | varchar(45) | YES | | NULL |
+-------+-------------+------+-----+---------+
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| a_id | int(11) | NO | PRI | 0 | |
| b_id | int(11) | NO | PRI | 0 | |
+-------+---------+------+-----+---------+-------+
+-----+------+
| id | data |
+-----+------+
| 100 | a |
| 200 | aa |
| 300 | aaa |
+-----+------+
+-----+------+
| id | data |
+-----+------+
| 333 | b |
| 666 | bb |
| 999 | bbb |
+-----+------+
+------+------+
| 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 + "}";
}
}