数据库表与表之间的关系
一对多:一个学校可以有多个学生,一个学生只能有一个学校
多对多:一个学生可以有多个老师,一个老师可以教多个学生
一对一:一个人只能有一个身份证号,一个身份证号只能找到一个人
一对多关系
创建学生和学校表
create table school( sch_id int PRIMARY KEY auto_increment , sch_name VARCHAR(30), sch_address VARCHAR(200) ); create table student( sid int PRIMARY KEY auto_increment, s_sch int , sname VARCHAR(30), FOREIGN KEY(s_sch) REFERENCES school(sch_id) );
根据表创建实体类和映射文件
一的一方
School.java
1 package com.qf.entity; 2 3 import java.util.HashSet; 4 import java.util.Set; 5 6 public class School { 7 private long sch_id; 8 private String sch_name; 9 private String sch_address; 10 //一个学校有多个学生,应该是一对多关系中多的一方的集合,hibernate默认使用set集合 11 private Set<Student> stus = new HashSet<Student>(); 12 13 //get、set方法 14 public long getSch_id() { 15 return sch_id; 16 } 17 public void setSch_id(long sch_id) { 18 this.sch_id = sch_id; 19 } 20 public String getSch_name() { 21 return sch_name; 22 } 23 public void setSch_name(String sch_name) { 24 this.sch_name = sch_name; 25 } 26 public String getSch_address() { 27 return sch_address; 28 } 29 public void setSch_address(String sch_address) { 30 this.sch_address = sch_address; 31 } 32 public Set<Student> getStus() { 33 return stus; 34 } 35 public void setStus(Set<Student> stus) { 36 this.stus = stus; 37 } 38 39 @Override 40 public String toString() { 41 return "School [sch_id=" + sch_id + ", sch_name=" + sch_name + ", sch_address=" + sch_address + ", stus=" + stus 42 + "]"; 43 } 44 public School() { 45 super(); 46 } 47 48 }
School.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <!-- 配置表与实体的映射关系 --> <class name="com.qf.entity.School" table="school"> <id name="sch_id" column="sch_id"> <!-- 主键生成策略 --> <generator class="native"></generator> </id> <property name="sch_name" column="sch_name"/> <property name="sch_address" column="sch_address"/> <!-- name:多的一方的集合属性的名称 --> <set name="stus" > <!-- column:多的一方表的外键名称--> <key column="s_sch"></key> <!-- class:多的一方的类的全路径 --> <one-to-many class="com.qf.entity.Student"/> </set> </class> </hibernate-mapping>
多的一方
Student.java
1 package com.qf.entity; 2 3 public class Student { 4 5 private Long sid; 6 private String sname; 7 //一个学生只能有一个学校 8 private School sch; 9 10 public Student() { 11 super(); 12 } 13 14 public Long getSid() { 15 return sid; 16 } 17 public void setSid(Long sid) { 18 this.sid = sid; 19 } 20 public String getSname() { 21 return sname; 22 } 23 public void setSname(String sname) { 24 this.sname = sname; 25 } 26 public School getSch() { 27 return sch; 28 } 29 public void setSch(School sch) { 30 this.sch = sch; 31 } 32 @Override 33 public String toString() { 34 return "Student [sid=" + sid + ", sname=" + sname + ", sch=" + sch + "]"; 35 } 36 37 }
Student.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <!-- 配置表与实体的映射关系 --> <class name="com.qf.entity.Student" table="student"> <id name="sid" column="sid"> <!-- 主键生成策略 --> <generator class="native"></generator> </id> <property name="sname" column="sname"/> <!-- name:一的一方的对象的属性 class:一的一方的对象全路径 column:多的一方表的外键名称 --> <many-to-one name="sch" class="com.qf.entity.School" column="s_sch"></many-to-one> </class> </hibernate-mapping>
配置核心配置文件hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class" >com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url" >jdbc:mysql:///test02</property> <property name="hibernate.connection.username" >root</property> <property name="hibernate.connection.password" >root</property> <property name="hibernate.dialect" >org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.hbm2ddl.auto" >update</property> <property name="hibernate.show_sql" >true</property> <property name="hibernate.format_sql" >true</property> <mapping resource="com/qf/entity/School.hbm.xml"/> <mapping resource="com/qf/entity/Student.hbm.xml"/> </session-factory> </hibernate-configuration>
测试类
TestDemo.java
1 package com.qf.test; 2 3 import org.hibernate.Session; 4 import org.hibernate.Transaction; 5 import org.junit.Test; 6 7 import com.qf.entity.School; 8 import com.qf.entity.Student; 9 import com.qf.util.SessionFactoryUtil; 10 11 public class TestDemo { 12 @Test 13 public void test() { 14 Session session = SessionFactoryUtil.getSession(); 15 Transaction tx = session.beginTransaction(); 16 17 School sch1 = new School(); 18 sch1.setSch_name("怀远一中"); 19 School sch2 = new School(); 20 sch2.setSch_name("包集中学"); 21 22 Student stu1 = new Student(); 23 stu1.setSname("张三"); 24 Student stu2 = new Student(); 25 stu2.setSname("李四"); 26 Student stu3 = new Student(); 27 stu3.setSname("王五"); 28 29 stu1.setSch(sch1); 30 stu2.setSch(sch2); 31 stu3.setSch(sch1); 32 sch1.getStus().add(stu1); 33 sch1.getStus().add(stu3); 34 sch2.getStus().add(stu2); 35 36 session.save(stu1); 37 session.save(stu2); 38 session.save(stu3); 39 session.save(sch1); 40 session.save(sch2); 41 42 tx.commit(); 43 } 44 }
查看数据库结果
student表
sid | sname | s_sch |
1 | 张三 | 1 |
2 | 李四 | 2 |
3 | 王五 | 1 |
school表
sch_id | sch_name | sch_address |
1 | 怀远一中 | |
2 | 包集中学 |
级联操作
级联,指的是操作一个对象的同时操作该对象相关联的对象
级联分类
- 级联保存或者更新
- 级联删除
级联保存或者更新
1. 操作一的一方
- 修改一的一方的映射文件
- School.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <!-- 配置表与实体的映射关系 --> <class name="com.qf.entity.School" table="school"> <id name="sch_id" column="sch_id"> <!-- 主键生成策略 --> <generator class="native"></generator> </id> <property name="sch_name" column="sch_name"/> <property name="sch_address" column="sch_address"/> <!-- name:多的一方的集合属性的名称 --> <set name="stus" cascade="save-update"> <!-- column:多的一方表的外键名称--> <key column="s_sch"></key> <!-- class:多的一方的类的全路径 --> <one-to-many class="com.qf.entity.Student"/> </set> </class> </hibernate-mapping>
- School.hbm.xml
- 测试代码
- 测试方法
1 @Test 2 /** 3 *保存学校是否会保存学生信息 4 *保存的主体是学校,所以在学校的映射文件中添加级联配置 5 *<set name="stus" cascade="save-update"> 6 */ 7 public void test() { 8 Session session = SessionFactoryUtil.getSession(); 9 Transaction tx = session.beginTransaction(); 10 11 School sch1 = new School(); 12 sch1.setSch_name("包集中学"); 13 14 Student stu1 = new Student(); 15 stu1.setSname("李四"); 16 17 stu1.setSch(sch1); 18 sch1.getStus().add(stu1); 19 20 session.save(sch1); 21 22 tx.commit(); 23 }
- 测试方法
- 查看数据库中结果
-
student表
sid sname s_sch 1 李四 1 school表
sch_id sch_name sch_address 1 包集中学
-
2. 操作多的一方
- 操作多的一方的映射文件
- Student.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <!-- 配置表与实体的映射关系 --> <class name="com.qf.entity.Student" table="student"> <id name="sid" column="sid"> <!-- 主键生成策略 --> <generator class="native"></generator> </id> <property name="sname" column="sname"/> <!-- name:一的一方的对象的属性 class:一的一方的对象全路径 column:多的一方表的外键名称 --> <many-to-one cascade="save-update" name="sch" class="com.qf.entity.School" column="s_sch"></many-to-one> </class> </hibernate-mapping>
- Student.hbm.xml
- 测试代码
- 测试方法
@Test /** *保存学生是否会保存学校信息 *保存的主体是学生,所以在学生的映射文件中添加级联配置 *<many-to-one cascade="save-update" name="sch" class="com.qf.entity.School" column="s_sch"/> */ public void test() { Session session = SessionFactoryUtil.getSession(); Transaction tx = session.beginTransaction(); School sch1 = new School(); sch1.setSch_name("怀远一中"); Student stu1 = new Student(); stu1.setSname("张三"); stu1.setSch(sch1); sch1.getStus().add(stu1); session.save(stu1); tx.commit(); }
- 测试方法
- 查看数据库中结果
-
student表
sid sname s_sch 1 张三 1 school表
sch_id sch_name sch_address 1 怀远一中
-
3. 可以在多的一方和一的一方分别配置级联,这样操作一的一方、多的一方,同时都会操作另一方
1 @Test 2 /** 3 *保存学校是否会保存学生信息 4 *保存的主体是学校,所以在学校的映射文件中添加级联配置 5 *<set name="stus" cascade="save-update"> 6 */ 7 public void test() { 8 Session session = SessionFactoryUtil.getSession(); 9 Transaction tx = session.beginTransaction(); 10 11 School sch1 = new School(); 12 sch1.setSch_name("包集中学"); 13 14 Student stu1 = new Student(); 15 stu1.setSname("李四"); 16 Student stu2 = new Student(); 17 stu2.setSname("王五"); 18 Student stu3 = new Student(); 19 stu3.setSname("张三"); 20 21 stu1.setSch(sch1); 22 sch1.getStus().add(stu2); 23 sch1.getStus().add(stu3); 24 25 /* 26 * 学校插入一条记录,学生插入三条记录 27 * 因为保存stu1,级联保存sch1,保存sch1,级联保存stu2、stu3 28 */ 29 session.save(stu1); 30 /* 31 * 学生插入一条记录 32 * 因为保存stu2,stu2并没有其它关联的,所以只保存stu2 33 */ 34 //session.save(stu2); 35 /* 36 * 学校插入一条记录,学生插入两条记录 37 * 因为保存sch1,级联保存stu2、stu3 38 */ 39 //session.save(sch1); 40 41 tx.commit(); 42 }
级联删除
通常jdbc操作数据库情况下,如果要删除表中学校信息,直接删除学校是无法成功的,必须先将该学校下所有的学生信息删除完,才能删除学校信息
级联删除可以让我们在删除学校的同时级联删除相关的学生信息
1. 配置级联删除之前测试
1 @Test 2 public void delete() { 3 Session session = SessionFactoryUtil.getSession(); 4 Transaction tx = session.beginTransaction(); 5 6 /* 7 * 先查询再删除 8 */ 9 School school = session.get(School.class, 1L); 10 session.delete(school); 11 12 tx.commit(); 13 }
console输出
Hibernate: select school0_.sch_id as sch_id1_0_0_, school0_.sch_name as sch_name2_0_0_, school0_.sch_address as sch_addr3_0_0_ from school school0_ where school0_.sch_id=? Hibernate: update student set s_sch=null where s_sch=? Hibernate: delete from school where sch_id=?
结论
- 删除学校信息时,默认先修改学生信息的外键(置为null),再删除学校信息
- hibernate删除一对多关系中一的一方某记录信息时,默认先将多的一方引用一的一方相关信息的外键置为null,再删除一的一方记录信息
2. 配置删除学校时级联删除学生
- 因为删除主体是学校,所以需要在学校的映射文件中做级联删除配置<set name="stus" cascade="delete">
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <!-- 配置表与实体的映射关系 --> <class name="com.qf.entity.School" table="school"> <id name="sch_id" column="sch_id"> <!-- 主键生成策略 --> <generator class="native"></generator> </id> <property name="sch_name" column="sch_name"/> <property name="sch_address" column="sch_address"/> <!-- name:多的一方的集合属性的名称 --> <set name="stus" cascade="delete"> <!-- column:多的一方表的外键名称--> <key column="s_sch"></key> <!-- class:多的一方的类的全路径 --> <one-to-many class="com.qf.entity.Student"/> </set> </class> </hibernate-mapping>
-
测试方法(必须先查询再删除,如果自己创建对象删除的话,自己创建的对象里信息可能不全,例如school中可能没有学生集合信息)
@Test public void delete() { Session session = SessionFactoryUtil.getSession(); Transaction tx = session.beginTransaction(); /* * 先查询再删除 */ School school = session.get(School.class, 1L); session.delete(school); tx.commit(); }
-
console输出
Hibernate: select school0_.sch_id as sch_id1_0_0_, school0_.sch_name as sch_name2_0_0_, school0_.sch_address as sch_addr3_0_0_ from school school0_ where school0_.sch_id=? Hibernate: select stus0_.s_sch as s_sch3_1_0_, stus0_.sid as sid1_1_0_, stus0_.sid as sid1_1_1_, stus0_.sname as sname2_1_1_, stus0_.s_sch as s_sch3_1_1_ from student stus0_ where stus0_.s_sch=? Hibernate: update student set s_sch=null where s_sch=? Hibernate: delete from student where sid=? Hibernate: delete from student where sid=? Hibernate: delete from student where sid=? Hibernate: delete from school where sch_id=?
-
结论
- 删除学校信息时级联删除了相关学生信息
3. 删除学生信息级联删除学校信息(并不符合常理,一般不会使用)
- 配置学生映射文件
- <many-to-one name="sch" cascade="delete" class="com.qf.entity.School" column="s_sch"/>
维护关系inverse
inverse主要作用是指定哪一方来维护关系
- 取值是boolean,默认inverse="false"
- 如果一方的映射文件中设置为true,说明在映射关系中让另一方来维护关系;如果为false,就自己来维护关系
- 只能在一的一方设置
测试方法
2号学生本来是2号学校的,改变成1号学校
@Test public void test() { Session session = SessionFactoryUtil.getSession(); Transaction tx = session.beginTransaction(); Student stu = session.get(Student.class, 2L); School sch = session.get(School.class, 1L); stu.setS_sch(2); sch.getStus().add(stu); tx.commit(); }
1.不设置,使用默认值
School.hbm.xml
<set name="stus" cascade="save-update delete" inverse="false" >
<!-- column:多的一方表的外键名称-->
<key column="s_sch"></key>
<!-- class:多的一方的类的全路径 -->
<one-to-many class="com.qf.entity.Student"/>
</set>
Hibernate: select student0_.sid as sid1_1_0_, student0_.sname as sname2_1_0_, student0_.s_sch as s_sch3_1_0_ from student student0_ where student0_.sid=? Hibernate: select school0_.sch_id as sch_id1_0_0_, school0_.sch_name as sch_name2_0_0_, school0_.sch_address as sch_addr3_0_0_ from school school0_ where school0_.sch_id=? Hibernate: select stus0_.s_sch as s_sch3_1_0_, stus0_.sid as sid1_1_0_, stus0_.sid as sid1_1_1_, stus0_.sname as sname2_1_1_, stus0_.s_sch as s_sch3_1_1_ from student stus0_ where stus0_.s_sch=? Hibernate: update student set sname=?, s_sch=? where sid=? Hibernate: update student set s_sch=? where sid=?
2.设置另一方维护关系
School.hbm.xml
<set name="stus" cascade="save-update delete" inverse="true" >
<!-- column:多的一方表的外键名称-->
<key column="s_sch"></key>
<!-- class:多的一方的类的全路径 -->
<one-to-many class="com.qf.entity.Student"/>
</set>
Hibernate: select student0_.sid as sid1_1_0_, student0_.sname as sname2_1_0_, student0_.s_sch as s_sch3_1_0_ from student student0_ where student0_.sid=? Hibernate: select school0_.sch_id as sch_id1_0_0_, school0_.sch_name as sch_name2_0_0_, school0_.sch_address as sch_addr3_0_0_ from school school0_ where school0_.sch_id=? Hibernate: select stus0_.s_sch as s_sch3_1_0_, stus0_.sid as sid1_1_0_, stus0_.sid as sid1_1_1_, stus0_.sname as sname2_1_1_, stus0_.s_sch as s_sch3_1_1_ from student stus0_ where stus0_.s_sch=? Hibernate: update student set sname=?, s_sch=? where sid=?
结论:
- 设置另一方维护关系时,少发送一次更新语句
- inverse="true"指的是由双向关联另一方维护该关联,己方不维护该关联
- Inverse默认为false,双向关系的两端都能控制,会造成一些问题(更新的时候会因为两端都控制关系,于是重复更新),可以在一端将inverse值设为true