JPA与同一实体的双重关系
JPA double relation with the same Entity
我有这些实体:
@Entity
public class Content extends AbstractEntity
{
@NotNull
@OneToOne(optional = false)
@JoinColumn(name = "CURRENT_CONTENT_REVISION_ID")
private ContentRevision current;
@OneToMany(mappedBy = "content", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ContentRevision> revisionList = new ArrayList<>();
}
@Entity
public class ContentRevision extends AbstractEntity
{
@NotNull
@ManyToOne(optional = false)
@JoinColumn(name = "CONTENT_ID")
private Content content;
@Column(name = "TEXT_DATA")
private String textData;
@Temporal(TIMESTAMP)
@Column(name = "REG_DATE")
private Date registrationDate;
}
这是数据库映射:
CONTENT
+-----------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------------+--------------+------+-----+---------+----------------+
| ID | bigint(20) | NO | PRI | NULL | auto_increment |
| CURRENT_CONTENT_REVISION_ID | bigint(20) | NO | MUL | NULL | |
+-----------------------------+--------------+------+-----+---------+----------------+
CONTENT_REVISION
+-----------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------------+--------------+------+-----+---------+----------------+
| ID | bigint(20) | NO | PRI | NULL | auto_increment |
| REG_DATE | datetime | YES | | NULL | |
| TEXT_DATA | longtext | YES | | NULL | |
| CONTENT_ID | bigint(20) | NO | MUL | NULL | |
+-----------------------------+--------------+------+-----+---------+----------------+
我也有这些要求:
Content.current
始终是 Content.revisionList
的成员(将 Content.current
视为 "pointer")。
- 用户可以将新的
ContentRevision
添加到现有的 Content
- 用户可以添加一个新的
Content
和一个初始的 ContentRevision
(级联持续)
- 用户可以更改
Content.current
(移动"pointer")
- 用户可以修改
Content.current.textData
,但保存Content
(级联合并)
- 用户可以删除
ContentRevision
- 用户可以删除
Content
(级联删除到 ContentRevision
)
现在,我的问题是:
- 这是最好的方法吗?有什么最佳做法吗?
- 当同一个实体被引用两次时,级联合并安全吗?
(Content.current
也是 Content.revisionList[i]
)
Content.current
和Content.revisionList[i]
是同一个实例吗?
(Content.current
== Content.revisionList[i]
?)
谢谢
@jabu.10245 非常感谢你的努力。真的谢谢你。
但是,您的测试中存在一个有问题(缺失)的案例:当您 运行 使用 CMT 在容器中时:
@RunWith(Arquillian.class)
public class ArquillianTest
{
@PersistenceContext
private EntityManager em;
@Resource
private UserTransaction utx;
@Deployment
public static WebArchive createDeployment()
{
// Create deploy file
WebArchive war = ShrinkWrap.create(WebArchive.class, "test.war");
war.addPackages(...);
war.addAsResource("persistence-arquillian.xml", "META-INF/persistence.xml");
war.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
// Show the deploy structure
System.out.println(war.toString(true));
return war;
}
@Test
public void testDetached()
{
// find a document
Document doc = em.find(Document.class, 1L);
System.out.println("doc: " + doc); // Document@1342067286
// get first content
Content content = doc.getContentList().stream().findFirst().get();
System.out.println("content: " + content); // Content@511063871
// get current revision
ContentRevision currentRevision = content.getCurrentRevision();
System.out.println("currentRevision: " + currentRevision); // ContentRevision@1777954561
// get last revision
ContentRevision lastRevision = content.getRevisionList().stream().reduce((prev, curr) -> curr).get();
System.out.println("lastRevision: " + lastRevision); // ContentRevision@430639650
// test equality
boolean equals = Objects.equals(currentRevision, lastRevision);
System.out.println("1. equals? " + equals); // true
// test identity
boolean same = currentRevision == lastRevision;
System.out.println("1. same? " + same); // false!!!!!!!!!!
// since they are not the same, the rest makes little sense...
// make it dirty
currentRevision.setTextData("CHANGED " + System.currentTimeMillis());
// perform merge in CMT transaction
utx.begin();
doc = em.merge(doc);
utx.commit(); // --> ERROR!!!
// get first content
content = doc.getContentList().stream().findFirst().get();
// get current revision
currentRevision = content.getCurrentRevision();
System.out.println("currentRevision: " + currentRevision);
// get last revision
lastRevision = content.getRevisionList().stream().reduce((prev, curr) -> curr).get();
System.out.println("lastRevision: " + lastRevision);
// test equality
equals = Objects.equals(currentRevision, lastRevision);
System.out.println("2. equals? " + equals);
// test identity
same = currentRevision == lastRevision;
System.out.println("2. same? " + same);
}
}
因为它们不一样:
如果我在两个属性上启用级联,则会抛出异常
java.lang.IllegalStateException:
Multiple representations of the same entity [it.shape.edea2.jpa.ContentRevision#1] are being merged.
Detached: [ContentRevision@430639650];
Detached: [ContentRevision@1777954561]
如果我禁用电流级联,更改将丢失。
奇怪的是,运行在容器外执行此测试会导致成功执行。
也许是延迟加载 (hibernate.enable_lazy_load_no_trans=true),也许是其他原因,但绝对是 不安全.
我想知道是否有办法获得相同的实例。
Is it safe to cascade merge when the same entity is referenced twice?
是的。如果您管理 Content
的实例,那么它的 Content.revisionList
和 Content.current
也会被管理。刷新实体管理器时,其中任何一个的更改都将保留。您不必手动调用 EntityManager.merge(...)
,除非您正在处理需要合并的临时对象。
如果您创建一个新的 ContentRevision
,则使用该新实例调用 persist(...)
而不是 merge(...)
,并确保它具有对父 Content
的托管引用, 也将其添加到内容列表中。
Are Content.current and Content.revisionList[i] the same instance?
是的,应该是。测试一下确定。
Content.current is always a member of Content.revisionList (think about Content.current as a "pointer").
您可以使用检查约束在 SQL 中进行检查;或在 Java 中,但您必须确保已获取 revisionList
。默认情况下,它是延迟获取的,这意味着如果您访问 getRevisionList()
方法,Hibernate 将 运行 对该列表进行另一个查询。为此,您需要 运行ning 交易,否则您将获得 LazyInitializationException
.
您可以改为加载列表 eagerly, if that's what you want. Or you could define a entity graph,以便能够在不同的查询中支持这两种策略。
Users can modify Content.current.textData, but saves Content (cascade merge)
参见我上面的第一段,Hibernate 应该自动保存对任何托管实体的更改。
Users can delete ContentRevision
if (content.getRevisionList().remove(revision))
entityManager.remove(revision);
if (revision.equals(content.getCurrentRevision())
content.setCurrentRevision(/* to something else */);
Users can delete Content (cascade remove to ContentRevision)
在这里我更愿意确保在数据库模式中,例如
FOREIGN KEY (content_id) REFERENCES content (id) ON DELETE CASCADE;
更新
应要求,我写了一个测试。有关我使用的 Content
和 ContentRevision
的实现,请参见 this gist。
我不得不做一个重要的改变:Content.current
不能真的是 @NotNull
,尤其是 DB 字段,因为如果是,那么我们就不能在同时,因为两者都还没有 ID。因此,该字段最初必须允许 NULL
。
作为解决方法,我将以下方法添加到 Content
:
@Transient // ignored in JPA
@AssertTrue // javax.validation
public boolean isCurrentRevisionInList() {
return current != null && getRevisionList().contains(current);
}
此处验证器确保总是有一个非空 current
修订 和 它包含在修订列表中。
下面是我的测试。
这个证明引用是一样的(问题3)和 坚持content
where current
和[=40就够了=] 引用同一个实例(问题 2):
@Test @InSequence(0)
public void shouldCreateContentAndRevision() throws Exception {
// create java objects, unmanaged:
Content content = Content.create("My first test");
assertNotNull("content should have current revision", content.getCurrent());
assertSame("content should be same as revision's parent", content, content.getCurrent().getContent());
assertEquals("content should have 1 revision", 1, content.getRevisionList().size());
assertSame("the list should contain same reference", content.getCurrent(), content.getRevisionList().get(0));
// persist the content, along with the revision:
transaction.begin();
entityManager.joinTransaction();
entityManager.persist(content);
transaction.commit();
// verify:
assertEquals("content should have ID 1", Long.valueOf(1), content.getId());
assertEquals("content should have one revision", 1, content.getRevisionList().size());
assertNotNull("content should have current revision", content.getCurrent());
assertEquals("revision should have ID 1", Long.valueOf(1), content.getCurrent().getId());
assertSame("current revision should be same reference", content.getCurrent(), content.getRevisionList().get(0));
}
下一个确保加载实体后仍然为真:
@Test @InSequence(1)
public void shouldLoadContentAndRevision() throws Exception {
Content content = entityManager.find(Content.class, Long.valueOf(1));
assertNotNull("should have found content #1", content);
// same checks as before:
assertNotNull("content should have current revision", content.getCurrent());
assertSame("content should be same as revision's parent", content, content.getCurrent().getContent());
assertEquals("content should have 1 revision", 1, content.getRevisionList().size());
assertSame("the list should contain same reference", content.getCurrent(), content.getRevisionList().get(0));
}
甚至在更新它时:
@Test @InSequence(2)
public void shouldAddAnotherRevision() throws Exception {
transaction.begin();
entityManager.joinTransaction();
Content content = entityManager.find(Content.class, Long.valueOf(1));
ContentRevision revision = content.addRevision("My second revision");
entityManager.persist(revision);
content.setCurrent(revision);
transaction.commit();
// re-load and validate:
content = entityManager.find(Content.class, Long.valueOf(1));
// same checks as before:
assertNotNull("content should have current revision", content.getCurrent());
assertSame("content should be same as revision's parent", content, content.getCurrent().getContent());
assertEquals("content should have 2 revisions", 2, content.getRevisionList().size());
assertSame("the list should contain same reference", content.getCurrent(), content.getRevisionList().get(1));
}
SELECT * FROM content;
id | version | current_content_revision_id
----+---------+-----------------------------
1 | 2 | 2
更新 2
很难在我的机器上重现这种情况,但我让它工作了。这是我到目前为止所做的:
我更改了所有 @OneToMany
关系以使用延迟获取(默认)并重新运行 以下测试用例:
@Test @InSequence(3)
public void shouldChangeCurrentRevision() throws Exception {
transaction.begin();
entityManager.joinTransaction();
Document document = entityManager.find(Document.class, Long.valueOf(1));
assertNotNull(document);
assertEquals(1, document.getContentList().size());
Content content = document.getContentList().get(0);
assertNotNull(content);
ContentRevision revision = content.getCurrent();
assertNotNull(revision);
assertEquals(2, content.getRevisionList().size());
assertSame(revision, content.getRevisionList().get(1));
revision.setTextData("CHANGED");
document = entityManager.merge(document);
content = document.getContentList().get(0);
revision = content.getCurrent();
assertSame(revision, content.getRevisionList().get(1));
assertEquals("CHANGED", revision.getTextData());
transaction.commit();
}
延迟抓取测试通过。请注意,延迟获取需要在事务内执行。
出于某种原因,您正在编辑的内容修订实例与一对多列表中的实例不相同。为了重现我已经修改我的测试如下:
@Test @InSequence(4)
public void shouldChangeCurrentRevision2() throws Exception {
transaction.begin();
Document document = entityManager.find(Document.class, Long.valueOf(1));
assertNotNull(document);
assertEquals(1, document.getContentList().size());
Content content = document.getContentList().get(0);
assertNotNull(content);
ContentRevision revision = content.getCurrent();
assertNotNull(revision);
assertEquals(2, content.getRevisionList().size());
assertSame(revision, content.getRevisionList().get(1));
transaction.commit();
// load another instance, different from the one in the list:
revision = entityManager.find(ContentRevision.class, revision.getId());
revision.setTextData("CHANGED2");
// start another TX, replace the "current revision" but not the one
// in the list:
transaction.begin();
document.getContentList().get(0).setCurrent(revision);
document = entityManager.merge(document); // here's your error!!!
transaction.commit();
content = document.getContentList().get(0);
revision = content.getCurrent();
assertSame(revision, content.getRevisionList().get(1));
assertEquals("CHANGED2", revision.getTextData());
}
在那里,我完全理解你的错误。然后我修改了 @OneToMany
映射上的级联设置:
@OneToMany(mappedBy = "content", cascade = { PERSIST, REFRESH, REMOVE }, orphanRemoval = true)
private List<ContentRevision> revisionList;
错误消失了 :-) ... 因为我删除了 CascadeType.MERGE
.
我有这些实体:
@Entity
public class Content extends AbstractEntity
{
@NotNull
@OneToOne(optional = false)
@JoinColumn(name = "CURRENT_CONTENT_REVISION_ID")
private ContentRevision current;
@OneToMany(mappedBy = "content", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ContentRevision> revisionList = new ArrayList<>();
}
@Entity
public class ContentRevision extends AbstractEntity
{
@NotNull
@ManyToOne(optional = false)
@JoinColumn(name = "CONTENT_ID")
private Content content;
@Column(name = "TEXT_DATA")
private String textData;
@Temporal(TIMESTAMP)
@Column(name = "REG_DATE")
private Date registrationDate;
}
这是数据库映射:
CONTENT
+-----------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------------+--------------+------+-----+---------+----------------+
| ID | bigint(20) | NO | PRI | NULL | auto_increment |
| CURRENT_CONTENT_REVISION_ID | bigint(20) | NO | MUL | NULL | |
+-----------------------------+--------------+------+-----+---------+----------------+
CONTENT_REVISION
+-----------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------------+--------------+------+-----+---------+----------------+
| ID | bigint(20) | NO | PRI | NULL | auto_increment |
| REG_DATE | datetime | YES | | NULL | |
| TEXT_DATA | longtext | YES | | NULL | |
| CONTENT_ID | bigint(20) | NO | MUL | NULL | |
+-----------------------------+--------------+------+-----+---------+----------------+
我也有这些要求:
Content.current
始终是Content.revisionList
的成员(将Content.current
视为 "pointer")。- 用户可以将新的
ContentRevision
添加到现有的Content
- 用户可以添加一个新的
Content
和一个初始的ContentRevision
(级联持续) - 用户可以更改
Content.current
(移动"pointer") - 用户可以修改
Content.current.textData
,但保存Content
(级联合并) - 用户可以删除
ContentRevision
- 用户可以删除
Content
(级联删除到ContentRevision
)
现在,我的问题是:
- 这是最好的方法吗?有什么最佳做法吗?
- 当同一个实体被引用两次时,级联合并安全吗?
(Content.current
也是Content.revisionList[i]
) Content.current
和Content.revisionList[i]
是同一个实例吗?
(Content.current
==Content.revisionList[i]
?)
谢谢
@jabu.10245 非常感谢你的努力。真的谢谢你。
但是,您的测试中存在一个有问题(缺失)的案例:当您 运行 使用 CMT 在容器中时:
@RunWith(Arquillian.class)
public class ArquillianTest
{
@PersistenceContext
private EntityManager em;
@Resource
private UserTransaction utx;
@Deployment
public static WebArchive createDeployment()
{
// Create deploy file
WebArchive war = ShrinkWrap.create(WebArchive.class, "test.war");
war.addPackages(...);
war.addAsResource("persistence-arquillian.xml", "META-INF/persistence.xml");
war.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
// Show the deploy structure
System.out.println(war.toString(true));
return war;
}
@Test
public void testDetached()
{
// find a document
Document doc = em.find(Document.class, 1L);
System.out.println("doc: " + doc); // Document@1342067286
// get first content
Content content = doc.getContentList().stream().findFirst().get();
System.out.println("content: " + content); // Content@511063871
// get current revision
ContentRevision currentRevision = content.getCurrentRevision();
System.out.println("currentRevision: " + currentRevision); // ContentRevision@1777954561
// get last revision
ContentRevision lastRevision = content.getRevisionList().stream().reduce((prev, curr) -> curr).get();
System.out.println("lastRevision: " + lastRevision); // ContentRevision@430639650
// test equality
boolean equals = Objects.equals(currentRevision, lastRevision);
System.out.println("1. equals? " + equals); // true
// test identity
boolean same = currentRevision == lastRevision;
System.out.println("1. same? " + same); // false!!!!!!!!!!
// since they are not the same, the rest makes little sense...
// make it dirty
currentRevision.setTextData("CHANGED " + System.currentTimeMillis());
// perform merge in CMT transaction
utx.begin();
doc = em.merge(doc);
utx.commit(); // --> ERROR!!!
// get first content
content = doc.getContentList().stream().findFirst().get();
// get current revision
currentRevision = content.getCurrentRevision();
System.out.println("currentRevision: " + currentRevision);
// get last revision
lastRevision = content.getRevisionList().stream().reduce((prev, curr) -> curr).get();
System.out.println("lastRevision: " + lastRevision);
// test equality
equals = Objects.equals(currentRevision, lastRevision);
System.out.println("2. equals? " + equals);
// test identity
same = currentRevision == lastRevision;
System.out.println("2. same? " + same);
}
}
因为它们不一样:
如果我在两个属性上启用级联,则会抛出异常
java.lang.IllegalStateException: Multiple representations of the same entity [it.shape.edea2.jpa.ContentRevision#1] are being merged. Detached: [ContentRevision@430639650]; Detached: [ContentRevision@1777954561]
如果我禁用电流级联,更改将丢失。
奇怪的是,运行在容器外执行此测试会导致成功执行。
也许是延迟加载 (hibernate.enable_lazy_load_no_trans=true),也许是其他原因,但绝对是 不安全.
我想知道是否有办法获得相同的实例。
Is it safe to cascade merge when the same entity is referenced twice?
是的。如果您管理 Content
的实例,那么它的 Content.revisionList
和 Content.current
也会被管理。刷新实体管理器时,其中任何一个的更改都将保留。您不必手动调用 EntityManager.merge(...)
,除非您正在处理需要合并的临时对象。
如果您创建一个新的 ContentRevision
,则使用该新实例调用 persist(...)
而不是 merge(...)
,并确保它具有对父 Content
的托管引用, 也将其添加到内容列表中。
Are Content.current and Content.revisionList[i] the same instance?
是的,应该是。测试一下确定。
Content.current is always a member of Content.revisionList (think about Content.current as a "pointer").
您可以使用检查约束在 SQL 中进行检查;或在 Java 中,但您必须确保已获取 revisionList
。默认情况下,它是延迟获取的,这意味着如果您访问 getRevisionList()
方法,Hibernate 将 运行 对该列表进行另一个查询。为此,您需要 运行ning 交易,否则您将获得 LazyInitializationException
.
您可以改为加载列表 eagerly, if that's what you want. Or you could define a entity graph,以便能够在不同的查询中支持这两种策略。
Users can modify Content.current.textData, but saves Content (cascade merge)
参见我上面的第一段,Hibernate 应该自动保存对任何托管实体的更改。
Users can delete ContentRevision
if (content.getRevisionList().remove(revision))
entityManager.remove(revision);
if (revision.equals(content.getCurrentRevision())
content.setCurrentRevision(/* to something else */);
Users can delete Content (cascade remove to ContentRevision)
在这里我更愿意确保在数据库模式中,例如
FOREIGN KEY (content_id) REFERENCES content (id) ON DELETE CASCADE;
更新
应要求,我写了一个测试。有关我使用的 Content
和 ContentRevision
的实现,请参见 this gist。
我不得不做一个重要的改变:Content.current
不能真的是 @NotNull
,尤其是 DB 字段,因为如果是,那么我们就不能在同时,因为两者都还没有 ID。因此,该字段最初必须允许 NULL
。
作为解决方法,我将以下方法添加到 Content
:
@Transient // ignored in JPA
@AssertTrue // javax.validation
public boolean isCurrentRevisionInList() {
return current != null && getRevisionList().contains(current);
}
此处验证器确保总是有一个非空 current
修订 和 它包含在修订列表中。
下面是我的测试。
这个证明引用是一样的(问题3)和 坚持content
where current
和[=40就够了=] 引用同一个实例(问题 2):
@Test @InSequence(0)
public void shouldCreateContentAndRevision() throws Exception {
// create java objects, unmanaged:
Content content = Content.create("My first test");
assertNotNull("content should have current revision", content.getCurrent());
assertSame("content should be same as revision's parent", content, content.getCurrent().getContent());
assertEquals("content should have 1 revision", 1, content.getRevisionList().size());
assertSame("the list should contain same reference", content.getCurrent(), content.getRevisionList().get(0));
// persist the content, along with the revision:
transaction.begin();
entityManager.joinTransaction();
entityManager.persist(content);
transaction.commit();
// verify:
assertEquals("content should have ID 1", Long.valueOf(1), content.getId());
assertEquals("content should have one revision", 1, content.getRevisionList().size());
assertNotNull("content should have current revision", content.getCurrent());
assertEquals("revision should have ID 1", Long.valueOf(1), content.getCurrent().getId());
assertSame("current revision should be same reference", content.getCurrent(), content.getRevisionList().get(0));
}
下一个确保加载实体后仍然为真:
@Test @InSequence(1)
public void shouldLoadContentAndRevision() throws Exception {
Content content = entityManager.find(Content.class, Long.valueOf(1));
assertNotNull("should have found content #1", content);
// same checks as before:
assertNotNull("content should have current revision", content.getCurrent());
assertSame("content should be same as revision's parent", content, content.getCurrent().getContent());
assertEquals("content should have 1 revision", 1, content.getRevisionList().size());
assertSame("the list should contain same reference", content.getCurrent(), content.getRevisionList().get(0));
}
甚至在更新它时:
@Test @InSequence(2)
public void shouldAddAnotherRevision() throws Exception {
transaction.begin();
entityManager.joinTransaction();
Content content = entityManager.find(Content.class, Long.valueOf(1));
ContentRevision revision = content.addRevision("My second revision");
entityManager.persist(revision);
content.setCurrent(revision);
transaction.commit();
// re-load and validate:
content = entityManager.find(Content.class, Long.valueOf(1));
// same checks as before:
assertNotNull("content should have current revision", content.getCurrent());
assertSame("content should be same as revision's parent", content, content.getCurrent().getContent());
assertEquals("content should have 2 revisions", 2, content.getRevisionList().size());
assertSame("the list should contain same reference", content.getCurrent(), content.getRevisionList().get(1));
}
SELECT * FROM content;
id | version | current_content_revision_id
----+---------+-----------------------------
1 | 2 | 2
更新 2
很难在我的机器上重现这种情况,但我让它工作了。这是我到目前为止所做的:
我更改了所有 @OneToMany
关系以使用延迟获取(默认)并重新运行 以下测试用例:
@Test @InSequence(3)
public void shouldChangeCurrentRevision() throws Exception {
transaction.begin();
entityManager.joinTransaction();
Document document = entityManager.find(Document.class, Long.valueOf(1));
assertNotNull(document);
assertEquals(1, document.getContentList().size());
Content content = document.getContentList().get(0);
assertNotNull(content);
ContentRevision revision = content.getCurrent();
assertNotNull(revision);
assertEquals(2, content.getRevisionList().size());
assertSame(revision, content.getRevisionList().get(1));
revision.setTextData("CHANGED");
document = entityManager.merge(document);
content = document.getContentList().get(0);
revision = content.getCurrent();
assertSame(revision, content.getRevisionList().get(1));
assertEquals("CHANGED", revision.getTextData());
transaction.commit();
}
延迟抓取测试通过。请注意,延迟获取需要在事务内执行。
出于某种原因,您正在编辑的内容修订实例与一对多列表中的实例不相同。为了重现我已经修改我的测试如下:
@Test @InSequence(4)
public void shouldChangeCurrentRevision2() throws Exception {
transaction.begin();
Document document = entityManager.find(Document.class, Long.valueOf(1));
assertNotNull(document);
assertEquals(1, document.getContentList().size());
Content content = document.getContentList().get(0);
assertNotNull(content);
ContentRevision revision = content.getCurrent();
assertNotNull(revision);
assertEquals(2, content.getRevisionList().size());
assertSame(revision, content.getRevisionList().get(1));
transaction.commit();
// load another instance, different from the one in the list:
revision = entityManager.find(ContentRevision.class, revision.getId());
revision.setTextData("CHANGED2");
// start another TX, replace the "current revision" but not the one
// in the list:
transaction.begin();
document.getContentList().get(0).setCurrent(revision);
document = entityManager.merge(document); // here's your error!!!
transaction.commit();
content = document.getContentList().get(0);
revision = content.getCurrent();
assertSame(revision, content.getRevisionList().get(1));
assertEquals("CHANGED2", revision.getTextData());
}
在那里,我完全理解你的错误。然后我修改了 @OneToMany
映射上的级联设置:
@OneToMany(mappedBy = "content", cascade = { PERSIST, REFRESH, REMOVE }, orphanRemoval = true)
private List<ContentRevision> revisionList;
错误消失了 :-) ... 因为我删除了 CascadeType.MERGE
.