我有以下实体:
@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
视为“指针”)。- 用户可以向现有的
Content
添加新的ContentRevision
- 用户可以添加一个带有初始
ContentRevision
的新Content
(级联持久化) - 用户可以更改
Content.current
(移动“指针”) - 用户可以修改
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);
}
}
因为它们不一样:
if I enable cascading on both properties, an Exception is thrown
java.lang.IllegalStateException: Multiple representations of the same entity [it.shape.edea2.jpa.ContentRevision#1] are being merged. Detached: [ContentRevision@430639650]; Detached: [ContentRevision@1777954561]
if I disable cascade on current, the change get lost.
奇怪的是,在容器外运行此测试可以成功执行。
可能是懒加载(hibernate.enable_lazy_load_no_trans=true),也可能是其他原因,但绝对不安全。
我想知道是否有一种方法可以获得相同的实例。
order(revisionList)
的函数依赖,因此你必须确保顺序不会改变,或者如果改变了,你必须始终更新索引。 - Michele Mariotti