表与表之间的关系
(1)一对一
例子:在中国,一个男人只能有一个妻子,一个女人只能有一个丈夫。
(2)一对多
例子:学生和班级的关系,一个班级可以有多个学生,而一个学生只能属于一个班级。
一对多建表:通过外键建立关系,在多的那一方创建字段作为外键,指向一的那一方的主键。
(3)多对多
例子:学生和课程的关系,一个学生可以选择多个课程,一个课程可以有多个学生。
多对多建表:创建第三章表维护关系,至少有两个字段作为外键,指向两个表的主键。
Hibernate的一对多操作(重点)
(1)一对多的映射配置
以上述班级和学生的关系为例:班级是一,学生是多。
第一步:创建两个实体类,学生类和班级类
public class Student {
private Integer sid;
private String sname;
private String address;
private String phone;
private String sex;
private Classes classes;
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Classes getClasses() {
return classes;
}
public void setClasses(Classes classes) {
this.classes = classes;
}
}
public class Classes {
private Integer cid;
private String cname;
private String location;
private Set<Student> cStudents=new HashSet<Student>();
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public Set<Student> getcStudents() {
return cStudents;
}
public void setcStudents(Set<Student> cStudents) {
this.cStudents = cStudents;
}
}
第二步:让两个实体类之间互相表示
在班级类里面表示多个学生,即一个班级里有多个学生:
注意:在hibernate中要求使用集合表示多的数据,使用set集合
在学生类里面表示所属班级,即一个学生只能属于一个班级:
第三步:配置映射关系
- 一般一个实体类对应一个映射文件
- 首先完成最基本的配置,然后配置一对多的关系
在班级映射文件中,配置所有学生:(hibernate机制:双向维护外键,即一对多双方都需要配置外键)
使用<set>标签标示所有学生,其中<set>标签里面的name属性标示班级类中学生的set集合名称。
使用<key>标签表示外键(一对多建表,有外键),其中column属性标示外键名称
<one-to-many>标签表示一对多的关系,其中class属性标示学生类全路径
<hibernate-mapping>
<class name="cn.itcast.entity.Classes" table="classes">
<id name="cid" column="cid">
<generator class="native"></generator>
</id>
<property name="cname" column="cname"></property>
<property name="location" column="location"></property>
<set name="cStudents">
<key column="csid"></key>
<one-to-many class="cn.itcast.entity.Student"/>
</set>
</class>
</hibernate-mapping>
在学生映射文件中,配置所属班级:(hibernate机制:双向维护外键,即一对多双方都需要配置外键)
使用<many-to-one>标签表示多对一的关系,其中name属性表示班级类对象名称,class属性表示班级类全路径,column属性表示外键名称,与上述<key>标签中column属性一致。
<hibernate-mapping>
<class name="cn.itcast.entity.Student" table="student">
<id name="sid" column="sid">
<generator class="native"></generator>
</id>
<property name="sname" column="sname"></property>
<property name="address" column="address"></property>
<property name="phone" column="phone"></property>
<property name="sex" column="sex"></property>
<many-to-one name="classes" class="cn.itcast.entity.Classes" column="csid"></many-to-one>
</class>
</hibernate-mapping>
第四步: 创建核心配置文件,把映射文件引入到核心配置文件中
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///test1016</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<mapping resource="cn/itcast/entity/classes.hbm.xml"/>
<mapping resource="cn/itcast/entity/student.hbm.xml"/>
</session-factory>
</hibernate-configuration>
第五步:编写测试代码
public class Operation {
public void addStudent() {
Configuration cfg=new Configuration();
cfg.configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
Student student=new Student();
student.setSname("张三");
student.setSex("男");
student.setPhone("56154");
student.setAddress("北京");
student.setClasses(null);
session.save(student);
tx.commit();
session.close();
sessionFactory.close();
}
public void addClasses(){
Configuration cfg=new Configuration();
cfg.configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
Classes classes=new Classes();
classes.setCname("一班");
classes.setLocation("左家垅");
classes.setcStudents(null);
session.save(classes);
tx.commit();
session.close();
sessionFactory.close();
}
}
结果:
(2)一对多的级联操作
级联操作:
级联操作是指当主控方执行保存,更新或者删除操作时,其关联对象(被控方)也执行相同的操作。
- 级联保存:添加一个班级,为这个班级添加多个学生
- 级联删除:删除一个班级,这个班级里面的所有学生也删除
一对多级联保存
需求:添加班级,为这个班级添加一个学生
复杂写法:(底层写法)
public void testDemo(){
Configuration cfg=new Configuration();
cfg.configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction transaction=session.beginTransaction();
//创建班级对象
Classes classes=new Classes();
classes.setCname("火箭班");
classes.setLocation("china");
//创建学生对象
Student student=new Student();
student.setSname("张三");
student.setSex("男");
student.setPhone("1123");
student.setAddress("左家垅");
//建立班级和学生对象的关系,即把学生对象放到班级对象中,把班级对象放到学生对象汇总
classes.getcStudents().add(student);
student.setClasses(classes);
//保存到数据库
session.save(classes);
session.save(student);
transaction.commit();
session.close();
sessionFactory.close();
}
简化写法:
需求:上述复杂写法,需要维护双向关系,即班级对象维护学生对象,学生对象维护班级对象。当一个班级中有很多学生对象时,需要挨个进行维护。我们在班级映射文件中,使用cascade属性设置控制方对关联对象采用级联操作,只需关注主控方即可。
第一步:在班级映射文件中的<set>标签进行配置
第二步:创建班级和学生对象,只需要把学生对象放入到班级对象中,最后保存班级对象即可。
public void testDemo(){
Configuration cfg=new Configuration();
cfg.configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction transaction=session.beginTransaction();
//创建班级对象
Classes classes=new Classes();
classes.setCname("就业班");
classes.setLocation("香港");
//创建学生对象
Student student=new Student();
student.setSname("王富贵");
student.setSex("男");
student.setPhone("99999");
student.setAddress("瓜瓢山");
//将学生对象添加到班级对象中
classes.getcStudents().add(student);
//只需保存班级对象
session.save(classes);
transaction.commit();
session.close();
sessionFactory.close();
}
总结:一对多的关系中,一是主控方,所以在主控方的映射文件中配置被控方的级联操作。其次将被控方对象加入到主控方对象中,最后也不需提交主控方对象即可。(只需关注主控方即可,主控方配置,主控方维护,主控方保存)
一对多级联删除
需求:删除某个班级,把班级中的所有学生删除
具体实现:
- 第一步:在班级映射文件<set>标签,使用cascade属性值delete进行配置
- 第二步:根据id查询对象那个,调用session中的delete方法删除班级对象
代码:
<hibernate-mapping>
<class name="cn.itcast.entity.Classes" table="classes">
<id name="cid" column="cid">
<generator class="native"></generator>
</id>
<property name="cname" column="cname"></property>
<property name="location" column="location"></property>
<set name="cStudents" cascade="save-update,delete">
<key column="csid"></key>
<one-to-many class="cn.itcast.entity.Student"/>
</set>
</class>
</hibernate-mapping>
public void testDemo(){
Configuration cfg=new Configuration();
cfg.configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction transaction=session.beginTransaction();
Classes classes=session.get(Classes.class,6);
session.delete(classes);
transaction.commit();
session.close();
sessionFactory.close();
}
结果:
执行过程:
(1)根据id查询班级
(2)根据外键id值查询学生
(3)把学生外键设置为null
(4)删除班级和学生
总结:一对多的关系中,只需关注主控方即可。级联保存的时候,在主控方进行配置级联操作save-update,同时在主控方添加被控方对象,最后提交主控方对象即可。级联删除的时候,在主控方进行配置级联操作delete,同时查询主控方对象,并删除即可实现级联删除。
双向关联产生多余的SQL语句(使用inverse属性,让“一”方放弃外键维护权)
需求:让李翠花所属班级调整为火箭班
代码:
public void testDemo(){
Configuration cfg=new Configuration();
cfg.configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction transaction=session.beginTransaction();
Classes classes=session.get(Classes.class, 3);
Student student=session.get(Student.class, 4);
student.setClasses(classes);
classes.getcStudents().add(student);
transaction.commit();
session.close();
sessionFactory.close();
}
结果:
问题:Hibernate双向维护外键,在班级和学生里面都需要维护外键,修改班级的时候,修改一次外键;修改学生的时候,修改一次外键,造成效率问题。
上述修改操作,执行了两次update语句------------产生原因是因为双向维护了关系,更新班级的时候会修改一次外键,更新学生的时候也会修改一次外键,这样就产生了多余的SQL。
解决:其中一方放弃外键维护权即可。通常在一对多的关系中,让“一”的一方放弃外键维护权。举个例子:一个班级对应多个学生,一个学生只属于一个班级,这是典型的一对多的关系。要让一个班级记住所有学生的名字是很难的,但是如果让每个学生记住班级的名字就很简单。所以在一对多的关系中,“一”的一方放弃外键的维护权。
实现:使用inverse属性进行配置,inverse属性默认值是false,即不放弃外键维护权;配置为true,则代表放弃外键维护权。
代码:
<hibernate-mapping>
<class name="cn.itcast.entity.Classes" table="classes">
<id name="cid" column="cid">
<generator class="native"></generator>
</id>
<property name="cname" column="cname"></property>
<property name="location" column="location"></property>
<set name="cStudents" cascade="save-update,delete" inverse="true">
<key column="csid"></key>
<one-to-many class="cn.itcast.entity.Student"/>
</set>
</class>
</hibernate-mapping>
public void testDemo(){
Configuration cfg=new Configuration();
cfg.configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction transaction=session.beginTransaction();
//根据id查询调整后的班级对象
Classes classes=session.get(Classes.class, 4);
//根据id查询要调整的学生对象
Student student=session.get(Student.class, 3);
//维护双向关系---将学生添加到班级中,将班级添加到学生中
student.setClasses(classes);
classes.getcStudents().add(student);
transaction.commit();
session.close();
sessionFactory.close();
}
结果:(只进行了一次update)
Hibernate的多对多操作(重点)
(1)多对多的映射配置
以上述学生和课程的关系为例:学生是多,课程也是多。
第一步:创建两个实体类,学生类和课程类
public class Course {
private Integer cid;
private String cname;
private String description;
private Set<Student> cStudents=new HashSet<Student>();
public Set<Student> getcStudents() {
return cStudents;
}
public void setcStudents(Set<Student> cStudents) {
this.cStudents = cStudents;
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
public class Student {
private Integer sid;
private String sname;
private String address;
private String phone;
private String sex;
private Set<Course> sCourses=new HashSet<Course>();
public Set<Course> getsCourses() {
return sCourses;
}
public void setsCourses(Set<Course> sCourses) {
this.sCourses = sCourses;
}
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
第二步:让两个实体类互相表示
一个学生里面表示所有课程,使用set集合
一个课程里面表示所有学生,使用set集合
第三步:配置映射关系
- 一般一个实体类对应一个映射文件
- 首先完成最基本的配置,然后配置多对多的关系
在学生映射文件中,配置所有课程:
<hibernate-mapping>
<class name="cn.itcast.entity.Student" table="student">
<id name="sid" column="sid">
<generator class="native"></generator>
</id>
<property name="sname" column="sname"></property>
<property name="address" column="address"></property>
<property name="phone" column="phone"></property>
<property name="sex" column="sex"></property>
<!-- 在学生里面表示所有课程,使用set标签 -->
<!-- name属性:课程set集合名称 -->
<!-- table属性:第三张表名称(维护两张表的关系) -->
<set name="sCourses" table="student_course">
<!-- key标签配置当前映射文件在第三张表中外键的名称 -->
<key column="studentid"></key>
<!-- many-to-many标签配置多对多的关系 -->
<!-- class属性:课程类全路径 -->
<!-- column属性:课程在第三张表外键名称,与课程类配置文件中的key标签属性一致 -->
<many-to-many class="cn.itcast.entity.Course" column="courseid"></many-to-many>
</set>
</class>
</hibernate-mapping>
在课程映射文件中,配置所有学生:
<hibernate-mapping>
<class name="cn.itcast.entity.Course" table="course">
<id name="cid" column="cid">
<generator class="native"></generator>
</id>
<property name="cname" column="cname"></property>
<property name="description" column="description"></property>
<!-- 在课程里面表示所有学生,使用set标签 -->
<!-- name属性:学生set集合名称 -->
<!-- table属性:第三张表名称(维护两张表的关系) -->
<set name="cStudents" table="student_course">
<!-- key标签配置当前映射文件在第三张表中外键的名称 -->
<key column="courseid"></key>
<!-- many-to-many标签配置多对多的关系 -->
<!-- class属性:学生类全路径 -->
<!-- column属性:学生在第三张表外键名称,与学生类配置文件中的key标签属性一致 -->
<many-to-many class="cn.itcast.entity.Student" column="studentid"></many-to-many>
</set>
</class>
</hibernate-mapping>
第四步: 创建核心配置文件,把映射文件引入到核心配置文件中
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///test1017</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<mapping resource="cn/itcast/entity/course.hbm.xml"/>
<mapping resource="cn/itcast/entity/student.hbm.xml"/>
</session-factory>
</hibernate-configuration>
第五步:编写测试代码
public void testDemo(){
Configuration cfg=new Configuration();
cfg.configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
Student student1=new Student();
student1.setSname("王富贵");
student1.setSex("男");
student1.setPhone("185648411");
student1.setAddress("左家垅");
Student student2=new Student();
student2.setSname("王翠花");
student2.setSex("女");
student2.setPhone("8435184");
student2.setAddress("瓜瓢山");
Course course1=new Course();
course1.setCname("高数");
course1.setDescription("这门课非常难");
Course course2=new Course();
course2.setCname("物联网");
course2.setDescription("考试非常简单");
Course course3=new Course();
course3.setCname("计算机网络");
course3.setDescription("非常有意思");
student1.getsCourses().add(course1);
student1.getsCourses().add(course3);
student2.getsCourses().add(course3);
student1.getsCourses().add(course2);
session.save(student1);
session.save(student2);
tx.commit();
session.close();
sessionFactory.close();
}
结果:
(2)多对多的级联操作
级联操作:
级联操作是指当主控方执行保存,更新或者删除操作时,其关联对象(被控方)也执行相同的操作。
- 级联保存:根据学生保存课程,或者根据课程保存学生
- 级联删除:
多对多级联保存
需求:根据学生保存课程
简化写法:
第一步:在学生映射文件中的<set>标签进行配置,cascade属性值设置为save-update
第二步:创建学生和课程对象,把课程放到学生里面,最终保存学生就可以了
public void testDemo(){
Configuration cfg=new Configuration();
cfg.configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
Student student1=new Student();
student1.setSname("王富贵");
student1.setSex("男");
student1.setPhone("185648411");
student1.setAddress("左家垅");
Student student2=new Student();
student2.setSname("王翠花");
student2.setSex("女");
student2.setPhone("8435184");
student2.setAddress("瓜瓢山");
Course course1=new Course();
course1.setCname("高数");
course1.setDescription("这门课非常难");
Course course2=new Course();
course2.setCname("物联网");
course2.setDescription("考试非常简单");
Course course3=new Course();
course3.setCname("计算机网络");
course3.setDescription("非常有意思");
student1.getsCourses().add(course1);
student1.getsCourses().add(course3);
student2.getsCourses().add(course3);
student1.getsCourses().add(course2);
session.save(student1);
session.save(student2);
tx.commit();
session.close();
sessionFactory.close();
}
需求:根据课程保存学生
简化写法:
第一步:在课程映射文件中的<set>标签进行配置,cascade属性值设置为save-update
第二步:创建学生和课程对象,把学生放到课程里面,最终保存课程就可以了
public void testDemo(){
Configuration cfg=new Configuration();
cfg.configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
Student student1=new Student();
student1.setSname("王富贵");
student1.setSex("男");
student1.setPhone("185648411");
student1.setAddress("左家垅");
Student student2=new Student();
student2.setSname("王翠花");
student2.setSex("女");
student2.setPhone("8435184");
student2.setAddress("瓜瓢山");
Course course1=new Course();
course1.setCname("高数");
course1.setDescription("这门课非常难");
Course course2=new Course();
course2.setCname("物联网");
course2.setDescription("考试非常简单");
Course course3=new Course();
course3.setCname("计算机网络");
course3.setDescription("非常有意思");
course1.getcStudents().add(student1);
course1.getcStudents().add(student2);
course2.getcStudents().add(student1);
course3.getcStudents().add(student2);
session.save(course1);
session.save(course2);
session.save(course3);
tx.commit();
session.close();
sessionFactory.close();
}
多对多级联删除(一般不用,了解即可)
需求:删除某个课程,把课程中的所有学生也删除
第一步:在<set>标签中进行配置,其中cascade属性值设为delete
第二步:删除课程
(3)维护第三张表关系
多对多的关系主要是靠第三张表来维护的
需求:让某个学生选择某个课程
第一步:根据id查询学生和课程
第二步:把课程放到学生里面
public void testDemo(){
Configuration cfg=new Configuration();
cfg.configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
Student student=session.get(Student.class, 1);
Course course=session.get(Course.class, 6);
student.getsCourses().add(course);
tx.commit();
session.close();
sessionFactory.close();
}
需求:让某个学生放弃某个课程
第一步:根据id查询学生和课程
第二步:在学生中将课程去掉
public void testDemo(){
Configuration cfg=new Configuration();
cfg.configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
// Student student=session.get(Student.class, 1);
// Course course=session.get(Course.class, 6);
// student.getsCourses().add(course);
Student student2=session.get(Student.class, 1);
Course course2=session.get(Course.class, 2);
student2.getsCourses().remove(course2);
tx.commit();
session.close();
sessionFactory.close();
}