我是否必须为双向关系设置双方?
Do I have to set both sides for a bidirectional relationship?
@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;
}
}
要建立关系,我必须调用
b.setA(a);
a.getBs().add(b);
为什么两者都需要,为什么只做还不够
b.setA(a);
或
a.getBs().add(b);
?
关系存储在联接 table 中,b.setA(a)
将更新该联接 table。
但是当我之后查询时,a.getBs()
是空的。这是为什么?
这是一个说明问题的测试用例。请注意,最后一个断言失败了。
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
的数量变大时,通过改变"only one side"来改变双向关系会很有用。在这种情况下,拥有方的 UPDATE SELECT
查询比调用 a.getBs().remove(b)
快得多
另见 .
问题中有 2 个“方面”:Java 方面和 JPA 方面。
Java边
更完整的代码清单可能是:
@Entity
class A {
@OneToMany(mappedBy = "a")
@JoinTable
List<B> bs;
public List<B> getBs() {
return bs;
}
public void setBs(List<B> bs) {
this.bs = bs;
}
}
@Entity
class B {
@ManyToOne
@JoinTable
A a;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
JPA 实体仍然是 Java object。如果你不指示Java明确,例如“在 Bs 的 collection 中添加 B,当它的 a
属性 设置时”它没有理由自动执行。话虽如此,我经常看到这样的模式(为简洁起见跳过空检查):
@Entity
class A {
...
public void addB(B b) {
bs.add(b);
b.setA(this);
}
public void removeB(B b) {
if( bs.remove(b) ) {
b.setA(null);
}
}
}
JPA 端
JPA 2.1。规格,通道2.9 “实体关系”:
A bidirectional relationship has both an owning side and an inverse (non-owning) side. A unidirectional relationship has only an owning side. The owning side of a relationship determines the updates to the relationship in the database, as described in section 3.2.4.
- The inverse side of a bidirectional relationship must refer to its owning side by use of the
mappedBy
element
在问题的设置中,B.a
是拥有方,因为A.bs
指定mappedBy="a"
。规范说只有当 owning 端更新时,关系才会更新(即,将插入连接 table 中的条目)。这就是为什么做 b.setA(a)
更新连接 table.
完成上述并成功更新数据库后,从数据库读取相关的A object fresh应该获取正确的 bs
collection。可以肯定的是,首先尝试合并 B,提交事务,然后在不同的事务中获取(或刷新它)A。如果您希望 Java objects 的状态立即反映在同一笔交易中,您别无选择,只能同时设置 b.a
和 a.getBs().add(b)
.
如 EclipseLink 中 , 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 所述。您的另一个读取是从共享缓存返回 A,它与您的原始对象一样,没有将 B 添加到其 B 列表中。
最简单的解决方案是预先将 B 设置到 A 的列表中。不过,这里的其他选项是使用 em.refresh(a) 或查询提示强制刷新 A,或者禁用共享缓存。
@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;
}
}
要建立关系,我必须调用
b.setA(a);
a.getBs().add(b);
为什么两者都需要,为什么只做还不够
b.setA(a);
或
a.getBs().add(b);
?
关系存储在联接 table 中,b.setA(a)
将更新该联接 table。
但是当我之后查询时,a.getBs()
是空的。这是为什么?
这是一个说明问题的测试用例。请注意,最后一个断言失败了。
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
的数量变大时,通过改变"only one side"来改变双向关系会很有用。在这种情况下,拥有方的 UPDATE SELECT
查询比调用 a.getBs().remove(b)
快得多
另见
问题中有 2 个“方面”:Java 方面和 JPA 方面。
Java边
更完整的代码清单可能是:
@Entity
class A {
@OneToMany(mappedBy = "a")
@JoinTable
List<B> bs;
public List<B> getBs() {
return bs;
}
public void setBs(List<B> bs) {
this.bs = bs;
}
}
@Entity
class B {
@ManyToOne
@JoinTable
A a;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
JPA 实体仍然是 Java object。如果你不指示Java明确,例如“在 Bs 的 collection 中添加 B,当它的 a
属性 设置时”它没有理由自动执行。话虽如此,我经常看到这样的模式(为简洁起见跳过空检查):
@Entity
class A {
...
public void addB(B b) {
bs.add(b);
b.setA(this);
}
public void removeB(B b) {
if( bs.remove(b) ) {
b.setA(null);
}
}
}
JPA 端
JPA 2.1。规格,通道2.9 “实体关系”:
A bidirectional relationship has both an owning side and an inverse (non-owning) side. A unidirectional relationship has only an owning side. The owning side of a relationship determines the updates to the relationship in the database, as described in section 3.2.4.
- The inverse side of a bidirectional relationship must refer to its owning side by use of the
mappedBy
element
在问题的设置中,B.a
是拥有方,因为A.bs
指定mappedBy="a"
。规范说只有当 owning 端更新时,关系才会更新(即,将插入连接 table 中的条目)。这就是为什么做 b.setA(a)
更新连接 table.
完成上述并成功更新数据库后,从数据库读取相关的A object fresh应该获取正确的 bs
collection。可以肯定的是,首先尝试合并 B,提交事务,然后在不同的事务中获取(或刷新它)A。如果您希望 Java objects 的状态立即反映在同一笔交易中,您别无选择,只能同时设置 b.a
和 a.getBs().add(b)
.
如 EclipseLink 中
最简单的解决方案是预先将 B 设置到 A 的列表中。不过,这里的其他选项是使用 em.refresh(a) 或查询提示强制刷新 A,或者禁用共享缓存。