在hibernate注解(三)中,我提高過一對一(@OneToOne)懶加載失效的問題。雖然給出了解決方法,但並沒有給出完整的解決方案。今天我專門針對該問題進行討論。至於懶加載失效的原因,在之前的文章中已經我已經敘述過了,就不再重復了,不明白的可以去看看。
一、測試環境
數據庫:myqsl
代碼:主:Student,從:Card
表:
-
DROP
TABLE
IF
EXISTS
`student`;
-
CREATE
TABLE
`student` (
-
`ID`
int(
11)
NOT
NULL,
-
`NAME`
varchar(
50)
NOT
NULL,
-
`CARD_ID`
int(
11)
DEFAULT
NULL,
-
PRIMARY
KEY (
`ID`),
-
KEY
`PK_CARD_ID` (
`CARD_ID`),
-
CONSTRAINT
`PK_CARD_ID` FOREIGN
KEY (
`CARD_ID`)
REFERENCES
`card` (
`ID`)
ON
DELETE
NO
ACTION
ON
UPDATE
NO
ACTION
-
)
ENGINE=
InnoDB
DEFAULT
CHARSET=utf8;
-
-
DROP
TABLE
IF
EXISTS
`card`;
-
CREATE
TABLE
`card` (
-
`ID`
int(
11)
NOT
NULL,
-
`CODE`
varchar(
32)
NOT
NULL,
-
PRIMARY
KEY (
`ID`)
-
)
ENGINE=
InnoDB
DEFAULT
CHARSET=utf8;
代碼:
-
package com.po;
-
-
import javax.persistence.CascadeType;
-
import javax.persistence.Column;
-
import javax.persistence.Entity;
-
import javax.persistence.FetchType;
-
import javax.persistence.GeneratedValue;
-
import javax.persistence.GenerationType;
-
import javax.persistence.Id;
-
import javax.persistence.JoinColumn;
-
import javax.persistence.OneToOne;
-
import javax.persistence.Table;
-
-
@Entity
-
@Table(name =
"Student")
-
public
class Student {
-
-
private
int id;
-
private String name;
-
private Card card;
-
-
@Id
-
@GeneratedValue(strategy = GenerationType.IDENTITY)
-
@Column(name =
"ID", unique =
true, nullable =
false)
-
public int getId() {
-
return id;
-
}
-
-
public void setId(int id) {
-
this.id = id;
-
}
-
-
@Column(name =
"NAME", nullable =
false, length =
50)
-
public String getName() {
-
return name;
-
}
-
-
public void setName(String name) {
-
this.name = name;
-
}
-
-
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
-
@JoinColumn(name =
"CARD_ID")
-
public Card getCard() {
-
return card;
-
}
-
-
public void setCard(Card card) {
-
this.card = card;
-
}
-
}
-
package com.po;
-
-
import javax.persistence.CascadeType;
-
import javax.persistence.Column;
-
import javax.persistence.Entity;
-
import javax.persistence.FetchType;
-
import javax.persistence.GeneratedValue;
-
import javax.persistence.GenerationType;
-
import javax.persistence.Id;
-
import javax.persistence.OneToOne;
-
import javax.persistence.Table;
-
-
@Entity
-
@Table(name =
"card")
-
public
class Card {
-
-
private
int id;
-
private String code;
-
private Student student;
-
-
@Id
-
@GeneratedValue(strategy = GenerationType.IDENTITY)
-
@Column(name =
"ID", unique =
true, nullable =
false)
-
public int getId() {
-
return id;
-
}
-
-
public void setId(int id) {
-
this.id = id;
-
}
-
-
@Column(name =
"CODE", length =
32, nullable =
false)
-
public String getCode() {
-
return code;
-
}
-
-
public void setCode(String code) {
-
this.code = code;
-
}
-
-
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy =
"card")
-
public Student getStudent() {
-
return student;
-
}
-
-
public void setStudent(Student student) {
-
this.student = student;
-
}
-
}
方案一
在card表增加一個student表的外鍵字段STUDENT_ID,並在Card類的@OneToOne下增加@JoinColumn(name = "STUDENT_ID"),去掉mappedBy = "card",即
-
DROP
TABLE
IF
EXISTS
`card`;
-
CREATE
TABLE
`card` (
-
`ID`
int(
11)
NOT
NULL,
-
`CODE`
varchar(
32)
NOT
NULL,
-
`STUDENT_ID`
int(
11)
DEFAULT
NULL,
-
PRIMARY
KEY (
`ID`),
-
KEY
`PK_STUDENT_ID` (
`STUDENT_ID`),
-
CONSTRAINT
`PK_STUDENT_ID` FOREIGN
KEY (
`STUDENT_ID`)
REFERENCES
`student` (
`ID`)
ON
DELETE
NO
ACTION
ON
UPDATE
NO
ACTION
-
)
ENGINE=
InnoDB
DEFAULT
CHARSET=utf8;
-
public
class Card {
-
-
// ... 略
-
-
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
-
@JoinColumn(name =
"STUDENT_ID")
-
public Student getStudent() {
-
return student;
-
}
-
-
// ... 略
-
}
優點:不改變Student與Card在代碼中的對應關系(一對一)
缺點:需要同時維護Student和Card的兩個外鍵。
方案二
改為主鍵關聯。
-
DROP
TABLE
IF
EXISTS
`student`;
-
CREATE
TABLE
`student` (
-
`ID`
int(
11)
NOT
NULL,
-
`NAME`
varchar(
50)
NOT
NULL,
-
PRIMARY
KEY (
`ID`),
-
CONSTRAINT
`PK_CARD_ID` FOREIGN
KEY (
`ID`)
REFERENCES
`card` (
`ID`)
ON
DELETE
NO
ACTION
ON
UPDATE
NO
ACTION
-
)
ENGINE=
InnoDB
DEFAULT
CHARSET=utf8;
-
public
class Student {
-
-
// ... 略
-
-
@Id
-
@GenericGenerator(name =
"PK_Card", strategy =
"foreign", parameters =
@Parameter(name =
"property", value =
"card"))
-
@GeneratedValue(generator =
"PK_Card")
-
@Column(name =
"ID", unique =
true, nullable =
false)
-
public int getId() {
-
return id;
-
}
-
-
// ... 略
-
-
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional =
false)
-
@PrimaryKeyJoinColumn
-
public Card getCard() {
-
return card;
-
}
-
-
// ... 略
-
}
-
public
class Card {
-
-
// ... 略
-
-
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy =
"card", optional =
false)
-
public Student getStudent() {
-
return student;
-
}
-
-
// ... 略
-
}
除了改變student表的主鍵、外鍵結構外,Student類和Card類也要做相應修改,尤其注意“optional”,要設置為false,否則無法實現懶加載。
優點:不改變Student與Card在代碼中的對應關系(一對一)
缺點:改動較大,且使用主鍵關聯具有局限性。
PS:主鍵關聯的局限性
使用主鍵關聯會影響數據存儲結構,主鍵關聯是一種強耦合,以上述為例:Card存在時,Student才能存在,Card消亡時,Student也隨之消失。這是因為Student的主鍵依賴於Card主鍵,Student無法獨立存在(就是說必須先有學生卡,才能有學生)。
方案三
將Card類中的OneToOne改為OneToMany(一對多)。
-
public
class Card {
-
-
private Set students;
-
-
// ... 略
-
-
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy =
"card")
-
public Set<Student> getStudents() {
-
return students;
-
}
-
-
public void setStudents(Set students) {
-
this.students = students;
-
}
-
-
// ... 略
-
}
優點:數據庫不用修改
缺點:需要修改Student與Card在代碼中的對應關系
方案四
放棄用注解的方式,改為Xml方式來實現hibernate模型設計,並在Card Xml的OneToOne標簽中添加constrained屬性,靠注解解決的辦法已經沒有了(instrument增強就算了吧,很麻煩)。
最后,我們來評估下以上方案的可行性。
方案一:從可讀性來講,是最容易理解的,但需要維護兩個外鍵,如果程序控制不好的話,容易出問題,即關聯錯誤。
方案二:主鍵關聯雖然有些約束,但也取決於業務需求,比如訂單和訂單詳情,采用主鍵關聯也挺合適的,只是不適合相對靈活的對象關系。
方案三:改動在我看來是最小的了,犧牲了一定的可讀性(關系從Card角度看變為了一對多),我個人比較喜歡該種方案,因此推薦。
方案四:如果不采用注解,而采用Xml的話,我是很推薦這種方案的,注解雖然優點多,也趨於主流,但最傳統的Xml,功能還是最強大的。但如果你僅為了解決該問題,而將注解和Xml混合使用的話,我建議你還是放棄吧。
原文地址:https://blog.csdn.net/wangpeng047/article/details/19624795