2.1、映射简介
对于所有的对象实体而言,有如下三种关系:
1:1,1:n,m:n
一种双向,一种单向。
2.2、一对多映射
多对一单向:many-to-one单向,指的是在多的这一端增加关联。
配置文件的写法:
<hibernate-mapping package="org.pm.hibernate.model">
<class name="Student" table="t_stu">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<property name="no"/>
<!-- many-to-one用来映射多对一,name表示对象中的属性名称,column用来表示数据库中的外键的名称 -->
<!-- 当设置了cascade的时候,会自动完成关联,如果添加时没有关联对象,会自动创建一个关联对象 -->
<!-- 最佳实践:如果没有特殊情况,不要使用cascade,特别注意:可能使用cascade的地方
一般都是1的一方进行删除时使用,特殊需求才会使用cascade的add,
正常情况add方法都是应该由程序员完成添加 -->
<many-to-one name="classroom" column="cid"/>
</class>
</hibernate-mapping>
1、添加方式1:先创建many再创建one
//先添加many的一方之后才添加one的一方
/**
* 创建两个学生,并且保存
*/
Student stu1 = new Student();
stu1.setName("小红帽");
stu1.setNo("003");
session.save(stu1);
Student stu2 = new Student();
stu2.setName("小红帽");
stu2.setNo("004");
session.save(stu2);
/**
* 创建班级对象并且保存
*/
Classroom c = new Classroom();
c.setGrade(2015);
c.setName("计算机应用技术");
session.save(c);
//设置两个学生的班级信息,此时由于是持久化对象,被session所管理,所以会发出两条update
stu1.setClassroom(c);
stu2.setClassroom(c);
//这个例子的问题就是:先发出了3条insert之后又会发出2条update
//最佳实践:一定要先添加一的一方,之后再添加多的一方
session.getTransaction().commit();
2、添加方式2:先创建one之后再创建many,这种是最佳实践。
//先添加one的一方之后才添加many的一方
Classroom c = new Classroom();
c.setGrade(2015);
c.setName("计算机网络技术");
session.save(c);
Student stu1 = new Student();
stu1.setClassroom(c);
stu1.setName("小明");
stu1.setNo("001");
session.save(stu1);
Student stu2 = new Student();
stu2.setClassroom(c);
stu2.setName("小明");
stu2.setNo("001");
session.save(stu2);
//只会发出3条insert语句,所以最佳实践就是先创建one之后才创建many
session.getTransaction().commit();
3、load和get和延迟加载
session = HibernateUtil.openSession();
session.beginTransaction();
//load会存在延迟加载
Student stu = session.load(Student.class, 1);
//此时仅仅只是发一条sql
System.out.println(stu.getName());
//many-to-one也存在延迟加载问题,只有在使用到one这一方时才发出sql查询
//此时student的关联对象classroom也是延迟加载的,会再发一条sql来取对象
System.out.println(stu.getClassroom().getName());
session.getTransaction().commit();
session = HibernateUtil.openSession();
session.beginTransaction();
Student stu = session.get(Student.class, 1);
//get只是对自己不会进行延迟加载,但是在进行many-to-one时也会对one的对象进行延迟加载
System.out.println(stu.getName());
//对于get而言,如果要加载one的一方依然和load一样,都是有延迟加载
//此时student的关联对象classroom也是延迟加载的,会再发一条sql来取对象
System.out.println(stu.getClassroom().getName());
session.getTransaction().commit();
4、更新
session = HibernateUtil.openSession();
session.beginTransaction();
//使用离线对象进行更新
Student stu = new Student();
stu.setId(1);
stu.setName("张三");
Classroom cla = new Classroom();
cla.setId(2);
stu.setClassroom(cla);
session.update(stu);
session.getTransaction().commit();
session = HibernateUtil.openSession();
session.beginTransaction();
//使用持久化对象进行更新
Student stu = session.load(Student.class, 3);
Classroom cla = new Classroom();
cla.setId(1);
stu.setClassroom(cla);
session.getTransaction().commit();
5、cascade
<!-- 当设置了cascade的时候,会自动完成关联,如果添加时没有关联对象,会自动创建一个关联对象 -->
<!-- 最佳实践:如果没有特殊情况,不要使用cascade,特别注意:可能使用cascade的地方
一般都是一的一方进行删除时使用,特殊需求才会使用cascade的add,
正常情况add方法都是应该由程序员完成添加 -->
<many-to-one name="classroom" column="cid" cascade="all"/>
session = HibernateUtil.openSession();
session.beginTransaction();
Classroom c = new Classroom();
c.setGrade(2015);
c.setName("计算机信息管理");
//此时classroom没有存储,所以在添加student的时候没有外键,会抛出异常
//如果在many-to-one的配置选项中设置cascade="all"之后就可以完成级联更新
//但是不建议这样使用
Student stu1 = new Student();
stu1.setName("小三");
stu1.setNo("005");
session.save(stu1);
Student stu2 = new Student();
stu2.setName("小四");
stu2.setNo("006");
session.save(stu2);
stu1.setClassroom(c);
stu2.setClassroom(c);
session.getTransaction().commit();
6、delete
session = HibernateUtil.openSession();
session.beginTransaction();
//如果使用了cascade在删除stu时会自动级联删除classroom
//而classroom还是外键,无法删除,会抛出异常
Student stu = session.load(Student.class, 7);
session.delete(stu);
session.getTransaction().commit();
one-to-many单向
在one的这个对象中插入一个many的列表:
<!-- name="comments"对应实体类中的属性名称 -->
<set name="comments">
<!-- key用来指定在对方表中的外键的名称 -->
<key column="mid"/>
<!-- class用来设置列表中的对象类型 -->
<one-to-many class="Comment"/>
</set>
使用one-to-many来维护关系时,效率不高
session = HibernateUtil.openSession();
session.beginTransaction();
Student stu1 = new Student();
stu1.setName("张三");
stu1.setNo("007");
session.save(stu1);
Student stu2 = new Student();
stu2.setName("赵四");
stu2.setNo("009");
session.save(stu2);
Classroom cla = new Classroom();
cla.setGrade(2018);
cla.setName("计算机应用技术");
Set<Student> stus = new HashSet<Student>();
stus.add(stu1);
stus.add(stu2);
cla.setStudents(stus);
session.save(cla);
//此时会发出5条sql,3条insert,2条update,效率不高
//所以使用one的这一方来维护关系非常不推荐
//以上写法可以完成一种改进,在Classroom对象中添加一个addStudent方法
session.getTransaction().commit();
session = HibernateUtil.openSession();
session.beginTransaction();
Comment c1 = new Comment();
c1.setContent("记事本1");
Comment c2 = new Comment();
c2.setContent("记事本2");
session.save(c1);
session.save(c2);
Message msg = new Message();
msg.setTitle("开大会");
msg.setContent("大家开始畅所欲言");
msg.addComment(c1);
msg.addComment(c2);
session.save(msg);
//在Message对象中创建一个addComment的方法来添加Comment,这样灵活性会高一些
//但是依然也会发出5条sql,3条插入,2条更新,效率没有得到提高
session.getTransaction().commit();
特别注意:oneToMany在添加和维护关系时比较麻烦,所以在开发过程中不建议使用oneToMany的单向
最佳实践:不要在one的这一方来维护关系。
2、load:在进行load时,如果仅仅只是为了取出many这一端的数据,默认情况也会发出查找list的sql语
句,这样效率不高,所以在映射文件的set中可以设置lazy属性为extra,会自动根据我们查找的内容发
出不同的sql语句,效率会高一些。
<!-- 使用了lazy="extra"之后会稍微智能一些,会根据取的值的不同来判断是调用count还是获取投影 -->
<set name="comments" lazy="extra">
<!-- key用来指定在对方的外键的名称 -->
<key column="mid"/>
<!-- class用来设置列表中的对象类型 -->
<one-to-many class="Comment"/>
</set>
Message msg = session.load(Message.class, 1);
//执行了2条sql,把值输出
//仅仅只是取出size并且设置了lazy="extra",此时会发出
//select count(id) from t_comment where mid =?
System.out.println(msg.getComments().size());
session.getTransaction().commit();
Message msg = session.load(Message.class, 1);
System.out.println(msg.getContent());
//此时要取出的是数据,会自动发出select xxx,xxx的list的语句
for(Comment c:msg.getComments()) {
System.out.println(c.getContent());
}
session.getTransaction().commit();
最佳实践:在set中设置属性lazy="extra"。
双向关联:就是把以上两者结合起来。
<hibernate-mapping package="org.pm.hibernate.model">
<class name="Classroom" table="t_cla">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<property name="grade"/>
<!-- 1这一方 -->
<set name="students" lazy="extra" inverse="true">
<key column="cid"/>
<one-to-many class="Student"/>
</set>
</class>
</hibernate-mapping>
<hibernate-mapping package="org.pm.hibernate.model">
<class name="Student" table="t_stu">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<property name="no"/>
<!-- 多的一方 -->
<many-to-one name="classroom" column="cid"/>
</class>
</hibernate-mapping>
<key column="cid"/>与<many-to-one column="cid">两个外键最好保持一致,可以不一致,如果设置为不一
致很难维护关系。
1、add
session = HibernateUtil.openSession();
session.beginTransaction();
//先创建1的这一端的对象
Classroom cla = new Classroom();
cla.setGrade(2018);
cla.setName("计算机网络技术");
session.save(cla);
//创建n的这一端
Student stu1 = new Student();
stu1.setName("张三");
stu1.setNo("007");
stu1.setClassroom(cla);
session.save(stu1);
Student stu2 = new Student();
stu2.setName("赵四");
stu2.setNo("009");
stu2.setClassroom(cla);
session.save(stu2);
//此时只会发出3条sql
session.getTransaction().commit();
这是一种非常正常的操作(实际开发中的操作)。
2、update
<!-- inverse="true"表示不在自己这一端维护关系 -->
<set name="students" lazy="extra" inverse="true">
<key column="cid"/>
<one-to-many class="Student"/>
</set>
session = HibernateUtil.openSession();
session.beginTransaction();
/**
* 因为cla是离线对象Set为null,此时当进行更新操作的时候
* 会清空所有的Student对象。
* 再次证明不应该允许在1的这一方来维护关系。
* 可以通过在1的这一方设置一个inverse="true"的属性强制要求自己不维护关系,而由对方维护。
*/
Classroom cla = new Classroom();
cla.setId(4);
cla.setName("计算机科学与技术");
cla.setGrade(2018);
session.update(cla);
//设置了inverse="true",此时不会由Classroom来维护关系,Set中的内容不会更新
session.getTransaction().commit();
最佳实践:在双向关联中,在set标签中设置inverse="true"来表明自己不维护关系。
3、load
//此处发2条sql:1条取Student,1条取Classroom,这里的Classroom的id是Student
//为13所对应的Classroom的id为6
//已经把id为6的Classroom放到session中
Student stu = session.load(Student.class, 13);
System.out.println(stu.getName()+","+stu.getClassroom().getGrade());
Message msg = session.load(Message.class, 1);
//执行了2条sql,把值输出
//仅仅只是取出size并且设置了lazy="extra",此时会发出
//select count(id) from t_comment where mid =?
System.out.println(msg.getComments().size());
//此处发2条sql:1条取Student,1条取Classroom,这里的Classroom的id是Student
//为13所对应的Classroom的id为6
//已经把id为6的Classroom放到session中
Student stu = session.load(Student.class, 13);
System.out.println(stu.getName()+","+stu.getClassroom().getGrade());
Classroom cla = session.load(Classroom.class, 6);
System.out.println(cla.getStudents().size());
//会发出select count(id),因为set设置了lazy="extra"
for(Student s:cla.getStudents()) {
//此处会发出1条sql:因为id为6的Classroom已经在session的管理中,
//所以不会再发sql去取Classroom,仅仅发1条sql取这个cla中的Student
System.out.println(s.getName());
}
session.getTransaction().commit();
//此处发2条sql:1条取Student,1条取Classroom,这里的Classroom的id是Student
//为13所对应的Classroom的id为6
//已经把id为6的Classroom放到session中
Student stu = session.load(Student.class, 13);
System.out.println(stu.getName()+","+stu.getClassroom().getGrade());
//注意:此处取的是id为1的Classroom
Classroom cla = session.load(Classroom.class, 1);
// System.out.println(cla.getStudents().size());
//会发出select count(id),因为set设置了lazy="extra"
for(Student s:cla.getStudents()) {
//此处会发出2条sql:因为要取的id为1的Classroom不存在session的管理中,
//所以会多发1条sql取Classroom
System.out.println(s.getName());
}
session.getTransaction().commit();
2.3、一对一映射
1:1单向:
一对一有两种关联方式。
1、主键关联(两个实体的主键完全一致,使用不是很多)
2、外键关联(在任意一张表中增加另一张表的外键,和多对一类似)
外键关联:
<hibernate-mapping package="org.pm.hibernate.model">
<class name="IDCard" table="t_id_card">
<id name="id">
<generator class="native"/>
</id>
<property name="no"/>
<!-- 外键关联使用mang-to-one完成
one2one和oneToMany类似,只用增加unique="true"说明只能有一个对应关系 -->
<many-to-one name="person" column="pid" unique="true"/>
</class>
</hibernate-mapping>
add:
session = HibernateUtil.openSession();
session.beginTransaction();
Person person = new Person();
person.setName("宝宝");
session.save(person);
IDCard card = new IDCard();
card.setNo("150000000");
//由有外键的IDCard来维护关系
card.setPerson(person);
session.save(card);
session.getTransaction().commit();
session = HibernateUtil.openSession();
session.beginTransaction();
//获取已经存在的person
Person person = session.load(Person.class, 1);
//再次为该person添加IDCard
IDCard card = new IDCard();
card.setNo("260000000");
card.setPerson(person);
session.save(card);
/*
* 由于使用了unique,所以一个Person只能有一个IDCard,这里就会报错
*/
session.getTransaction().commit();
1:1双向:
<hibernate-mapping package="org.pm.hibernate.model">
<class name="Person" table="t_person">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<!-- name表示自己的属性的名称,property-ref是自己在另一端的属性名称,
表示由对端维护关系 -->
<one-to-one name="idCard" property-ref="person"/>
</class>
</hibernate-mapping>
特别注意:在没有外键的这一端无法维护关系。
add:
session = HibernateUtil.openSession();
session.beginTransaction();
/*
* 此时,由于使用的是IDCard来维护关系(外键在哪一端就由哪一端来维护)
* 通过person.setIdCard()就无效,所以外键关系不会更新
*/
IDCard idCard = new IDCard();
idCard.setNo("123");
session.save(idCard);
Person person = new Person();
person.setName("one2one-双向");
person.setIdCard(idCard);
session.save(person);
session.getTransaction().commit();
session = HibernateUtil.openSession();
session.beginTransaction();
Person person = new Person();
person.setName("one2one-双向-成功");
session.save(person);
IDCard idCard = new IDCard();
idCard.setNo("123456");
//idCard来维护关系,外键才会更新成功
idCard.setPerson(person);
session.save(idCard);
session.getTransaction().commit();
update:
session = HibernateUtil.openSession();
session.beginTransaction();
Person person = new Person();
person.setName("update");
person.setId(1);
/**
* 此处person是离线对象,并且没有设置IDCard,
* 但是由于person这一端没有维护关系,所以person和IDCard的关系依然存在
*/
session.update(person);
session.getTransaction().commit();
load:
session = HibernateUtil.openSession();
session.beginTransaction();
Person person = session.load(Person.class, 3);
//只要取出的是没有维护关系的这一方,会自动将关联对象取出,会发出1条sql
//由于person端没有维护关系,所以不会进行延迟加载,所以1条sql就搞定了
System.out.println(person.getName()+","+person.getIdCard().getNo());
session.getTransaction().commit();
session = HibernateUtil.openSession();
session.beginTransaction();
//特别注意:如果没有双向,此时会发出2条sql,一条取IDCard,一条延迟加载取person
//此时会发出3条sql语句
IDCard idCard = session.load(IDCard.class, 4);
//此时没有使用IDCard的Person,会延迟加载,目前只是发出1条sql
System.out.println(idCard.getNo());
//要去取person同时也会取出这个person的IDCard,这里就不会使用join来取,所以会发出2条sql
System.out.println(idCard.getPerson().getName());
session.getTransaction().commit();
最佳实践就是,one2one的时候最好不要使用双向关联,如果使用双向关联,尽可能在没有维护
关系的一端取数据,Hibernate会自动完成join,仅仅只会发1条sql,如果使用维护关系端取
数据,在通过延迟加载取关联对象时会同时再去取person的IDCard关联,所以会发3条sql语句。
2.4、多对多
m:n单向:
m:n的这种关系,必须在中间增加关联表。
<hibernate-mapping package="org.pm.hibernate.model">
<class name="Admin" table="t_admin">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<!-- table表示指明映射的中间表 -->
<set name="roles" table="t_admin_role" lazy="extra">
<!-- key和one-to-many中的key一样,表示自己在对方的外键名称 -->
<key column="aid"/>
<!-- 使用many-to-many完成映射,这里要增加column表示所放置的元素的外键名称 -->
<many-to-many class="Role" column="rid"/>
</set>
</class>
</hibernate-mapping>
add:
session = HibernateUtil.openSession();
session.beginTransaction();
Admin a1 = new Admin();
a1.setName("张三");
session.save(a1);
Admin a2 = new Admin();
a2.setName("李四");
session.save(a2);
Role r1 = new Role();
r1.setName("超级管理员");
r1.add(a1);
session.save(r1);
Role r2 = new Role();
r2.setName("财务管理人员");
r2.add(a1);
r2.add(a2);
session.save(r2);
session.getTransaction().commit();
/*
* 使用many-to-many不论在哪一方来维护关系都比较的麻烦,而且很多时候关联表中需要加入其它的
* 属性,所以在开发中,经常使用两个一对多来替代多对多
*/
load:
session = HibernateUtil.openSession();
session.beginTransaction();
Admin a = session.load(Admin.class, 1);
System.out.println(a.getName());
for(Role r:a.getRoles()) {
System.out.println(r.getName());
}
session.getTransaction().commit();
m:n双向:
m:n双向关联就是两个单向关联的集合,两端配置基本一致。
<hibernate-mapping package="org.pm.hibernate.model">
<class name="Role" table="t_role">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="admins" table="t_admin_role" lazy="extra">
<key column="rid"/>
<many-to-many class="Admin" column="aid"/>
</set>
</class>
</hibernate-mapping>
<hibernate-mapping package="org.pm.hibernate.model">
<class name="Admin" table="t_admin">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<!-- table表示指明映射的中间表 -->
<set name="roles" table="t_admin_role" lazy="extra">
<!-- key和one-to-many中的key一样,表示自己在对方的外键名称 -->
<key column="aid"/>
<!-- 使用many-to-many完成映射,这里要增加column表示所放置的元素的外键名称 -->
<many-to-many class="Role" column="rid"/>
</set>
</class>
</hibernate-mapping>
在开发中名称对应必须一样。
add:
session = HibernateUtil.openSession();
session.beginTransaction();
//由TeacherCourse完成关系映射关联
Teacher t1 = new Teacher();
t1.setName("张老师");
session.save(t1);
Teacher t2 = new Teacher();
t2.setName("王老师");
session.save(t2);
Course c1 = new Course();
c1.setName("数据结构");
session.save(c1);
Course c2 = new Course();
c2.setName("C语法基础");
session.save(c2);
TeacherCourse tc1 = new TeacherCourse();
tc1.setAchievement(89);
tc1.setTeacher(t1);
tc1.setCourse(c1);
session.save(tc1);
tc1 = new TeacherCourse();
tc1.setAchievement(66);
tc1.setTeacher(t1);
tc1.setCourse(c2);
session.save(tc1);
tc1 = new TeacherCourse();
tc1.setAchievement(99);
tc1.setTeacher(t2);
tc1.setCourse(c1);
session.save(tc1);
tc1 = new TeacherCourse();
tc1.setAchievement(79);
tc1.setTeacher(t2);
tc1.setCourse(c2);
session.save(tc1);
session.getTransaction().commit();
load:
session = HibernateUtil.openSession();
session.beginTransaction();
Teacher t = session.load(Teacher.class, 1);
//load的时候由于延迟加载,会根据不同的情况取相应的关联对象,所以会发出大量的sql
/**
* 总体来说:最佳实践就是,一般不使用双向关联,特别不建议使用1的这一方的关联,
* 因为从1的这一端取关联对象很有可能会涉及到分页操作,所以基本不会使用
* 在设计的时候不是特殊情况不要使用双向关联
*/
System.out.println(t.getName());
for(TeacherCourse tc:t.getTcs()) {
System.out.println(tc.getCourse().getName()+","+tc.getAchievement());
}
session.getTransaction().commit();
最佳实践:由于使用多对多不论在哪一方来维护关系都非常的麻烦,在具体的开发中基本不会使用,而是使
用两个一对多来完成映射。
2.5、对多对多的变形
由于多对多的关系不论在哪一方来维护都非常的麻烦,所以一般情况都是把多对多拆分为两个一对多。
<hibernate-mapping package="org.pm.hibernate.model">
<class name="Course" table="t_course">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="tcs" lazy="extra" inverse="true">
<key column="cid"/>
<one-to-many class="TeacherCourse"/>
</set>
</class>
</hibernate-mapping>
<hibernate-mapping package="org.pm.hibernate.model">
<class name="Teacher" table="t_teacher">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="tcs" lazy="extra" inverse="true">
<key column="tid"/>
<one-to-many class="TeacherCourse"/>
</set>
</class>
</hibernate-mapping>
<hibernate-mapping package="org.pm.hibernate.model">
<class name="TeacherCourse" table="t_teacher_course">
<id name="id">
<generator class="native"/>
</id>
<property name="achievement"/>
<many-to-one name="teacher" column="tid"/>
<many-to-one name="course" column="cid"/>
</class>
</hibernate-mapping>
整个写法和one-to-many双向类似。