本篇介绍Hibernate的级联操作和加载机制
首先来看级联,级联在Hibernate中用cascade这一属性表示,首先看什么是级联。比如在关系映射中,无论是一对一还是一对多还是其他:对象A和对象B是关联的,那么在对A进行CRUD操作的时候,比如将A持久化到数据库中时,也把B一起持久化进去,或者只是持久化A而不关联B,这种就称之为级联操作。
下面来看一个具体的实例,映射关系就用一对多的双向关系,用之前的例子,问卷和问题,一份问卷中包含多个问题,但是一个问题只能属于一份问卷。我们先来回顾一下实体类的书写。
@Entity
@Table(name="t_questionnaire")
public class Questionnaire {
private Integer id;
private String name;
private String answers;
private List<Question> questions = new ArrayList<Question>();
public Questionnaire() {
super();
}
public Questionnaire(Integer id, String name, List<Question> questions) {
super();
this.id = id;
this.name = name;
this.questions = questions;
}
@Id
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAnswers() {
return answers;
}
public void setAnswers(String answers) {
this.answers = answers;
}
@OneToMany(mappedBy="questionnaire")
public List<Question> getQuestions() {
return questions;
}
public void setQuestions(List<Question> questions) {
this.questions = questions;
}
}
以上代码是问卷这个实体类。
@Entity
@Table(name="t_question")
public class Question {
private Integer id;
private String name;
private Questionnaire questionnaire;
public Question() {
super();
}
public Question(Integer id, String name, Questionnaire questionnaire) {
super();
this.id = id;
this.name = name;
this.questionnaire = questionnaire;
}
@Id
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ManyToOne
@JoinColumn(name="questionnaireId")
public Questionnaire getQuestionnaire() {
return questionnaire;
}
public void setQuestionnaire(Questionnaire questionnaire) {
this.questionnaire = questionnaire;
}
}
以上代码是问题这个实体类。
这是一个双向关联,所以设置了mappedBy属性,并且外键设置在了多的一方,也就是问题这一方。
那么我们现在来看测试用例:
@Test
public void testCreateQuestionnaire(){
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Questionnaire qn = new Questionnaire();
qn.setName("XXX公司3月转正考试");
Question q1 = new Question();
q1.setName("1、Java是一门OOP语言吗?A、是 B、不是");
q1.setQuestionnaire(qn);
Question q2 = new Question();
q2.setName("2、Java是哪个公司的作品?A、SUN B、MicroSoft");
q2.setQuestionnaire(qn);
qn.getQuestions().add(q1);
qn.getQuestions().add(q2);
session.saveOrUpdate(qn);
session.getTransaction().commit();
}
new一个问卷qn,设置了名字,new两个问题q1 q2,分别设置问题的题干内容,然后建立他们之间的关联,将q1 q1所属的问卷设置为qn,将qn的下属问题列表添加q1 q2进来。那么问题来了,我在保存实体的时候,我只保存qn,我希望Hibernate裙带关系帮我把连带的q1 q2也持久化进数据库,这就需要级联操作,我们看现在不设置级联会出现什么情况。
Hibernate:
insert
into
t_questionnaire
(name, answers)
values
(?, ?)
没报错,但是只insert into了问卷,没有帮我把连带的问题也持久化进去。那么要解决此问题,我们必须设置级联关系。实际情况是,在保存问卷的时候,希望连带保存问题,那么级联属性设置理当应该在问卷实体中的问题列表属性上。代码如下:
@OneToMany(mappedBy="questionnaire",cascade={CascadeType.ALL})
public List<Question> getQuestions() {
return questions;
}
用cascade属性设置了级联关系,cascade属性的取值是一个数组,用大括号阔起来,CascadeType是一个枚举型,取值有5种:
ALL:全部操作都会级联,包括增,删,改
PERSIST:在持久化也就是保存的时候产生级联
DELETE:在删除时候产生级联删除
最常用的就是这三个,另外还有两种取值:
MERGE:在合并时产生级联
REFRESH:刷新时候级联
这里我们用到ALL。下面看结果:
Hibernate:
insert
into
t_questionnaire
(name, answers)
values
(?, ?)
Hibernate:
insert
into
t_question
(name, questionnaireId)
values
(?, ?)
Hibernate:
insert
into
t_question
(name, questionnaireId)
values
(?, ?)
保存了问卷和两个问题,并且设置上了问题所属的问卷。
刚刚是保存问卷,我希望连带保存问题,那么反过来呢?
@Test
public void testCreateQuestion(){
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Questionnaire qn = new Questionnaire();
qn.setName("XXX公司3月转正考试");
Question q1 = new Question();
q1.setName("1、Java是一门OOP语言吗?A、是 B、不是");
q1.setQuestionnaire(qn);
Question q2 = new Question();
q2.setName("2、Java是哪个公司的作品?A、SUN B、MicroSoft");
q2.setQuestionnaire(qn);
session.saveOrUpdate(q1);
session.saveOrUpdate(q2);
session.getTransaction().commit();
new了一份问卷qn,new了两个问题q1 q2,设置上了q1 q2所属的问卷为qn,我保存q1 q1的时候希望连带把qn保存进去,我们看结果:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing
报错了,TransientObjectException是说透明的对象异常:对象引用了一个未保存的透明的对象,也就是说,我的问卷没有保存,而问题设置了所属问卷,保存时候就会报错,关于Hibernate的三种状态在这里顺便提一句:
第一种:transient,透明的状态,也就是new出来的对象,在Hibernate看来称之为transient。
第二种:persist,持久化的,也就是save进数据库了就称之为该状态
第三种:detached,托管的,也就是session关闭后,称之为托管态。
那么要解决该异常,就需要在问题这一方添加级联,代码如下:
@ManyToOne(cascade={CascadeType.ALL})
@JoinColumn(name="questionnaireId")
public Questionnaire getQuestionnaire() {
return questionnaire;
}
下面我们看结果:
Hibernate:
insert
into
t_questionnaire
(name, answers)
values
(?, ?)
Hibernate:
insert
into
t_question
(name, questionnaireId)
values
(?, ?)
Hibernate:
insert
into
t_question
(name, questionnaireId)
values
(?, ?)
OK,没问题,那么下一篇介绍加载时候的机制。