在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