问题描述
@Entity
public class A {
@GeneratedValue
@Id
private long id;
public long getId() {
return id;
}
public void setId(final long id) {
this.id = id;
}
@OneToMany(mappedBy = "a")
List<B> bs;
public List<B> getBs() {
return bs;
}
public void setBs(final List<B> bs) {
this.bs = bs;
}
}
@Entity
public class B {
@GeneratedValue
@Id
private long id;
public long getId() {
return id;
}
public void setId(final long id) {
this.id = id;
}
@ManyToOne
@JoinTable
A a;
public A getA() {
return a;
}
public void setA(final A a) {
this.a = a;
}
}
为了建立关系,我必须致电
To establish the relationship, I have to call
b.setA(a);
a.getBs().add(b);
为什么两者都是必要的,为什么只做
Why is both necessary, Why is it not sufficient to do only
b.setA(a);
或
a.getBs().add(b);
?
关系存储在一个连接表, b.setA(a)
将更新该连接表。
The relationship is stored in a join table, and b.setA(a)
will update that join table.
但当我做一个之后查询, a.getBs()
为空。为什么会这样?
But when I do a query afterwards, a.getBs()
is empty. Why is that?
这是一个用于说明问题的测试用例。请注意,最后一个断言失败。
Here is a Test case that illustrates the question. Note that the very last assert fails.
public class QuickTestAB2 {
private static String dbUrlBase = "jdbc:derby:testData/db/test.db";
private static String dbUrlCreate = dbUrlBase + ";create=true";
private static String dbUrlDrop = dbUrlBase + ";drop=true";
private EntityManagerFactory factory;
private EntityManager em;
public Map<String, String> createPersistenceMap(final String dbUrl) {
final Map<String, String> persistenceMap = new HashMap<>();
persistenceMap.put("javax.persistence.jdbc.url", dbUrl);
return persistenceMap;
}
public void dropDatabase() throws Exception {
if (em != null && em.isOpen()) {
em.close();
}
if (factory != null && factory.isOpen()) {
factory.close();
}
try (Connection conn = DriverManager.getConnection(dbUrlDrop)) {
} catch (final SQLException e) {
// always
}
}
public void deleteDatabase() throws Exception {
dropDatabase();
final File file = new File("testData/db/test.db");
if (file.exists()) {
FileUtils.forceDelete(file);
}
}
public void createNewDatabase() throws SQLException, IOException {
FileUtils.forceMkdir(new File("testData/db"));
try (Connection conn = DriverManager.getConnection(dbUrlCreate)) {
}
}
@BeforeClass
public static void setUpBeforeClass01() throws Exception {
Tests.enableLog4J();
JPATests.enableJPA();
}
@AfterClass
public static void tearDownAfterClass01() throws Exception {
}
@Before
public void setUp01() throws Exception {
deleteDatabase();
createNewDatabase();
final Map<String, String> map = createPersistenceMap(dbUrlCreate);
factory = Persistence.createEntityManagerFactory("pu", map);
}
@After
public void tearDown01() throws Exception {
if (em != null && em.isOpen()) {
em.close();
}
em = null;
if (factory != null && factory.isOpen()) {
factory.close();
}
factory = null;
}
@Test
public void test01() throws Exception {
em = factory.createEntityManager();
final A a = new A();
final B b = new B();
b.setA(a);
try {
em.getTransaction().begin();
em.persist(a);
em.persist(b);
em.getTransaction().commit();
} finally {
em.close();
}
em = factory.createEntityManager();
B b2;
A a2;
try {
em.getTransaction().begin();
Query q = em.createQuery("SELECT b FROM B b");
b2 = (B) q.getSingleResult();
q = em.createQuery("SELECT a FROM A a");
a2 = (A) q.getSingleResult();
em.getTransaction().commit();
} finally {
em.close();
}
assertThat(a2, is(not(nullValue())));
assertThat(b2, is(not(nullValue())));
assertThat(b2.getA(), is(not(nullValue())));
assertThat(a2.getBs().isEmpty(), is(false));
}
}
动机:当 a.Bs
的数量变大时,通过更改仅一侧来更改双向关系会很有用。在这种情况下,拥有端的 UPDATE SELECT
查询比调用 a.getBs()快得多.remove(b)
另请参见。
Motivation: It can be useful to change a bidirectional relationship by changing "only one side", when the number of a.Bs
gets large. In this case, an UPDATE SELECT
query on the owning side is much faster than to call a.getBs().remove(b)
See also here.
推荐答案
如下面,JPA和java对象要求您设置关系的两侧。只要您设置了拥有方,数据库就会根据关系更改进行更新,但非拥有方只会反映数据库中的内容(如果您手动设置它),或者强制刷新或重新加载数据库。从单独的上下文中读取实体不会强制重新加载,因为您的JPA提供程序可以使用二级缓存;这是EclipseLink中的。你的另一个读取是从共享缓存中返回A,它与原始对象一样,没有B添加到其B列表中。
As stated in Nikos Paraskevopoulos' answer below, JPA and java objects require you to set both sides of a relationship. As long as you set the owning side, the database will be updated with the relationship changes, but the non-owning side will only reflect what is in the database if you manually set it, or you force it to be refreshed or reloaded. Reading the entity from a separate context does not force reloading, as your JPA provider can use a second level cache; this is the default in EclipseLink. Your other read is returning the A from the shared cache, which like your original object, does not have B added to its list of Bs.
最简单的解决方案是将B设置为A的列表。这里的其他选项虽然是使用em.refresh(a)或查询提示强制刷新A,或者禁用共享缓存。
The easiest solution is to just set B into A's list upfront. Other options here though would be to force the refresh of A using em.refresh(a) or with a query hint, or disable the shared cached.
这篇关于我是否必须设置双向关系?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!