本文閱讀時(shí)間3分鐘。由作者三汪首發(fā)于簡(jiǎn)書。
幾個(gè)月前寫了一篇《JPA實(shí)體關(guān)系映射:@ManyToMany多對(duì)多關(guān)系、@OneToMany@ManyToOne一對(duì)多多對(duì)一關(guān)系和@OneToOne的深度實(shí)例解析》,簡(jiǎn)要闡述了我對(duì)JPA的幾個(gè)實(shí)體關(guān)系映射的理解。
其中有關(guān)mappedBy的部分是這樣寫的:
mappedBy聲明于關(guān)系的被維護(hù)方,聲明的值為關(guān)系的維護(hù)方的關(guān)系對(duì)象屬性名。
在實(shí)例中,mappedBy被聲明于Course類中,其值為Student類中的Set對(duì)象"courses"。即,Student為關(guān)系維護(hù)方,Course為被維護(hù)方。
這個(gè)表述其實(shí)是沒錯(cuò)的。但是我后面又補(bǔ)充了一句:
但是在實(shí)際操作中,我發(fā)現(xiàn)其實(shí)被維護(hù)方于維護(hù)方的概念并不那么重要。被維護(hù)方也可以對(duì)雙方關(guān)系進(jìn)行維護(hù)。下面通過一組測(cè)試用例來進(jìn)行說明。
然而今天測(cè)試小哥給我提了個(gè)bug,讓我發(fā)現(xiàn)到其實(shí)mappedBy其實(shí)并非不重要的。

事實(shí)上這是一個(gè)很簡(jiǎn)單的bug。
通過查看日志,發(fā)現(xiàn)hibernate打印出來的sql中,不僅有update語句,還出現(xiàn)了delete語句。
這是因?yàn)榍岸藗鬟^來的數(shù)據(jù)里是不帶關(guān)聯(lián)對(duì)象的。
所以只要從數(shù)據(jù)庫里把關(guān)聯(lián)找出來然后賦給編輯的對(duì)象就解決了。
像這樣:
修改前
@Override
@Transactional
public void saveLabelDict(LabelDictDomain labelDictDomain) {
if (labelDictDomain.getLabelType().equals(TYPE_UNIFORM)) {
if (StringUtil.isEmpty(labelDictDomain.getLabelGroup())) {
throw new BizException("統(tǒng)一標(biāo)簽標(biāo)簽分組不能為空!");
}
}else if(!labelDictDomain.getLabelType().equals(TYPE_PERSONAL)){
throw new BizException("標(biāo)簽類型非法!");
}
repository.save(labelDictDomain);
}
修改后
@Override
@Transactional
public void saveLabelDict(LabelDictDomain labelDictDomain) {
if (labelDictDomain.getLabelType().equals(TYPE_UNIFORM)) {
if (StringUtil.isEmpty(labelDictDomain.getLabelGroup())) {
throw new BizException("統(tǒng)一標(biāo)簽標(biāo)簽分組不能為空!");
}
}else if(!labelDictDomain.getLabelType().equals(TYPE_PERSONAL)){
throw new BizException("標(biāo)簽類型非法!");
}
if (StringUtil.isNotEmpty(labelDictDomain.getId())) {
LabelDictDomain domain = repository.findOne(labelDictDomain.getId());
labelDictDomain.getMemberDomains().addAll(domain.getMemberDomains());
}
repository.save(labelDictDomain);
}
這個(gè)修改引發(fā)了我對(duì)于關(guān)系映射的思考。
因?yàn)樵谂c之相關(guān)的另外一個(gè)實(shí)體編輯時(shí),并不需要這樣子手動(dòng)去配置關(guān)聯(lián)關(guān)系。
相關(guān)代碼如下
@Override
@Transactional
public MemberDomain addMember(MemberDomain memberDomain) {
//無關(guān)代碼略
memberDomain.getBonusPointDomains().clear();
memberDomain.getFavoriteInfoDomains().clear();
memberDomain.getLabelDictDomains().clear();
memberDomain.setPointTotal(null);
memberDomain = repository.save(memberDomain);
return memberDomain;
}
在這里我不僅沒有手動(dòng)配置關(guān)聯(lián)關(guān)系,還為了維護(hù)數(shù)據(jù)安全,把其他實(shí)體關(guān)聯(lián)給清空了。
但是這里卻不會(huì)產(chǎn)生delete語句影響數(shù)據(jù)庫里的內(nèi)容。
這是為什么呢。
在看了一眼實(shí)體中的關(guān)聯(lián)配置以后,我突然恍然大悟。
其實(shí)都是因?yàn)檫@個(gè)小小的沒有被注意到的mappedBy啊。
因?yàn)?code>LabelDictDomain中的memberDomains對(duì)象在MemberDomain中被配置為了關(guān)系的維護(hù)方。
所以labelDictDomains可以為空,而memberDomains一定不能為空。
否則就會(huì)出現(xiàn)關(guān)聯(lián)關(guān)系被清空的情況。
也就是說,被維護(hù)方不會(huì)主動(dòng)去維護(hù)關(guān)聯(lián)關(guān)系。
真正的關(guān)系維護(hù),掌握在維護(hù)方的手中。
關(guān)聯(lián)配置代碼如下
被維護(hù)方:
@ManyToMany(mappedBy = "memberDomains", cascade={CascadeType.MERGE,CascadeType.DETACH} , fetch = FetchType.EAGER)
private Set<LabelDictDomain> labelDictDomains = new HashSet<>();
維護(hù)方:
@ManyToMany(cascade={CascadeType.DETACH} , fetch = FetchType.LAZY)
@JoinTable(name="member_label",joinColumns = { @JoinColumn(name="LABEL_ID")},inverseJoinColumns = @JoinColumn(name="MEMBER_ID"))
private Set<MemberDomain> memberDomains = new HashSet<>();
以上。
希望我的文章對(duì)你能有所幫助。
我不能保證文中所有說法的百分百正確,但我能保證它們都是我的理解和感悟以及拒絕復(fù)制黏貼。
有什么意見、見解或疑惑,歡迎留言討論。