【发布时间】:2015-12-02 09:45:20
【问题描述】:
我有这些实体:
@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视为“指针”)。 - 用户可以将新的
ContentRevision添加到现有的Content - 用户可以使用初始
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);
}
}
因为它们不一样:
-
如果我在两个属性上启用级联,则会引发异常
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),也可能是其他原因,但这绝对是不安全。
我想知道是否有办法获得相同的实例。
【问题讨论】:
-
恕我直言,对于第三点,我想说如果两者中的一个被延迟加载但如果两者都被急切地获取,我认为这取决于底层实现。很好的问题!
-
@Gab 谢谢 ;) 我认为这也取决于缓存......但是关于这个主题的规范非常模糊。
-
如果你实现你的指针需要用一个简单的 int 代替当前版本?您可以实现一个 getCurrentRevision,如 revisionList.get(currentRevision)。虽然它可能会使 CurrentRevision 上的 HQL 查询复杂化,但这取决于您的需求。
-
这不是最优的:你向
order(revisionList)引入了一个函数依赖,所以你必须保证顺序不会改变,或者如果改变你必须总是更新索引也是。
标签: java hibernate jpa jpa-2.1