一、一对多关系
1.概念
一对多关系是关系型数据库中两个表之间的一种关系。通常在数据库层级中,两表之间是有主外键关系的。在ORM中,如何通过对象描述表之间的关系,是ORM核心。
2.Hibernate的一对多关联映射【重点】
2.1表关系的分析
MySql语句
CREATE TABLE `t_category` (
`cid` int(11) NOT NULL AUTO_INCREMENT,
`cname` varchar(255) DEFAULT NULL,
PRIMARY KEY (`cid`)
);
CREATE TABLE `t_product` (
`pid` int(11) NOT NULL AUTO_INCREMENT,
`pname` varchar(255) DEFAULT NULL,
`price` double DEFAULT NULL,
`cid` int(11) DEFAULT NULL,
PRIMARY KEY (`pid`),
KEY `FKq8yr5sflwtcj3jqp58x0oy7lx` (`cid`),
CONSTRAINT `FKq8yr5sflwtcj3jqp58x0oy7lx` FOREIGN KEY (`cid`) REFERENCES `t_category` (`cid`)
);
2.2创建持久化类
- Category.java(一的一方)
public class Category {
private Integer cid;
private String cname;
//在一的一方,用一个集合表示和product的关系
private Set<Product> products = new HashSet<Product>();
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 Set<Product> getProducts() {
return products;
}
public void setProducts(Set<Product> products) {
this.products = products;
}
}
- Product.java(多的一方)
public class Product {
private Integer pid;
private String pname;
private double price;
//用一个对象表示,当前商品属于哪个类别
private Category category;
public Integer getPid() {
return pid;
}
public void setPid(Integer pid) {
this.pid = pid;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
}
2.3创建映射
- 分类的映射
<hibernate-mapping>
<class name="com.pri.bean.Category" table="t_category">
<!--一,主键属性 -->
<id name="cid" column="cid">
<generator class="native"></generator>
</id>
<!-- 二,其它属性 -->
<property name="cname" column="cname"/>
<!-- 三,表示和商品的关系 -->
<!--3.1 set标签的name属性:多的一方的集合的名字 -->
<set name="products">
<!--3.2 key的 column表示多的一方外键名 -->
<key column="cid"/>
<!--3.3 one-to-many的class属性表示多的一方类的全限定名 -->
<one-to-many class="com.pri.bean.Product"/>
</set>
</class>
</hibernate-mapping>
- 商品的映射
<hibernate-mapping>
<class name="com.pri.bean.Product" table="t_product">
<!--一,主键属性 -->
<id name="pid" column="pid">
<generator class="native"></generator>
</id>
<!-- 二,其它属性 -->
<property name="pname" column="pname"/>
<property name="price" column="price"/>
<!-- 三,表示和分类的关系 -->
<!--3.1name:一的一方对象的名字
class: 一的一方类的全限定名
column:外键的名字
-->
<many-to-one name="category" class="com.pri.bean.Category" column="cid"/>
</class>
</hibernate-mapping>
2.4将映射添加到配置文件
<!-- 三、加载映射关系配置 -->
<mapping resource="com/pri/bean/Product.hbm.xml"/>
<mapping resource="com/pri/bean/Category.hbm.xml"/>
2.5写代码测试
- TestDb
public class TestDb {
@Test
public void fun01(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
transaction.commit();
session.close();
}
}
3.级联操作
3.1概念
如果我们对某一个表进行了操作,那么也会顺带对关联表进行操作。或者说当对主对象进行某种操作时是也对其关联的从对象也作类似的操作.
eg:如果我们删除了一个公司,那么就得要求,员工表也得把属于这个公司的所有员工也删除掉。
如果我们删除了一个类别, 那么该类别下的商品是否要删除或者不删除只删除外键,那么这样的操作就属于级联操作了.
3.2 级联保存和修改
3.2.1 操作一方级联保存多方【掌握】
-
保存Category(一的一方)的时候级联保存Product(多的一方)
-
应用场景: 保存一方的同时也需要保存多方(并且多方的数据比较多). eg: 订单和订单项
<!-- 三、表示和商品的关系 -->
<!-- 3.1 set标签的name属性:多的一方的集合的名字 -->
<set name="products" cascade="save-update"/>
<!-- 3.2 key的column表示多的一方外键名 -->
<key column="cid"/>
<!-- 3.3 one-to-many的class属性表示多的一方类的全限定名 -->
<one-to-many class="com.pri.bean.Product"/>
</set>
/**
* 只保存分类,通过级联把当前分类下的商品也保存
* 配置: 在category.hbm.xml下设置:
* <set cascade="save-update">
*/
@Test
public void fun02(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Category c1 = new Category();
c1.setCname("水果");
Product p1 = new Product();
p1.setPname("苹果");
p1.setPrice(5);
//c1和p1 产生关系
c1.getProducts().add(p1);
//p1和c1产生关系
p1.setCategory(c1);
session.save(c1);
//session.save(p1);不需要了,通过级联保存特性自动保存
transaction.commit();
session.close();
}
3.2.2操作多方级联保存一方【了解】
<!--三.表示和分类的关系 -->
<!-- 3.1 name:一的一方对象的名字
class: 一的一方类的全限定名
column:外键的名字
-->
<many-to-one name="category" class="com.pri.bean.Category" column="cid" cascade="save-update"/>
- 保存Product(多的一方的)的时候级联保存Category(一的一方)
/**
* 只保存商品,通过级联的特性保存商品对应的分类
* 配置: 在product.hbm.xml下设置:
* <many-to-one cascade="save-update"/>
*/
@Test
public void fun03(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Category c1 = new Category();
c1.setCname("水果");
Product p1 = new Product();
p1.setPname("苹果");
p1.setPrice(5);
//p1和c1发生关系
p1.setCategory(c1);
session.save(p1);
//session.save(c1); 不需要了
transaction.commit();
session.close();
}
3.3级联删除
3.3.1删除一方级联删除多方【掌握】
- 删除Category(一的一方)的时候级联删除Product(多的一方)
- 场景: 删除一方的时候确保多方的数据没有任何用途的时候的才会用. eg: 公司和员工
- 实际开发里面很少删除用户的数据. 通常用一个字段标识的
<!-- 三.表示和商品的关系 -->
<!-- 3.1 set标签的name属性:多的一方的集合的名字 -->
<set name="products" cascade="delete">
<!--3.2 key 的column表示多的一方外键名 -->
<key column="cid"/>
<!-- 3.3 one-to-many的class属性表示多的一方类的全限定名 -->
<one-to-many class="com.pri.bean.Product"/>
</set>
@Test
//级联删除商品: 删除id为1的类别,级联删除当前类别下的所有商品
//因为删除的是类别,所有在Category.hbm.xml配置:<set name="products" cascade="delete">
public void fun03(){
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
//先查 id为1的类别
Category category = session.get(Category.class, 1);
session.delete(category);
transaction.commit();
}
3.3.2 删除多方级联删除一方【了解】
- 删除Product(多的一方)的时候级联删除Category(一的一方)
<!--三.表示和分类的关系 -->
<!-- 3.1 name:一的一方对象的名字
class: 一的一方类的全限定名
column:外键的名字
-->
<many-to-one name="category" class="com.pri.bean.Category" column="cid" cascade="delete"/>
@Test
//级联删除分类: 删除id为1的商品,级联删除这个商品所属的类别(了解)
//把当前id为1商品删除,把这个商品所属的类别删除, 商品表里面该类别其它的商品的记录的外键置为null;
public void fun04(){
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
//先查 id为1的类别
Product product = session.get(Product.class, 1);
session.delete(product);
transaction.commit();
}
4.外键维护
4.1 双向关联产生多余的SQL语句
一旦存在主外键关系,那么外键就由双方对象去维护了, 那么就会造成对这个外键执行了多次操作, 有一次操作其实可以避免的。 也就是说这个外键只要让一个人去维护即可。 外键属于谁,就让谁去维护。
eg:修改产品对应的类别
4.2inverse标签属性
4.2.1概念
inverse:外键维护,默认为false。代表一方不去维护多方外键。(inverse有反转的意思)外键属于谁, 谁就负责维护.
4.2.2一方的放弃外键维护
只能在一的一方放弃外键维护
<set inverse="true">
...
</set>
<!-- 三.表示和商品的关系 -->
<!-- 3.1 set标签的name属性:多的一方的集合的名字 -->
<set name="products" cascade="delete" inverse="true">
<!--3.2 key 的column表示多的一方外键名 -->
<key column="cid"/>
<!-- 3.3 one-to-many的class属性表示多的一方类的全限定名 -->
<one-to-many class="com.pri.bean.Product"/>
</set>
/**
* 把苹果(p1)的类别设置为水果(c2)
* 衣服是(c1)
*/
@Test
public void fun02(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//获得苹果
Product p1 = session.get(Product.class, 1);
//获得食物类别
Category c2 = session.get(Category.class, 2);
//p1和c2关联
p1.setCategory(c2);
//c2和p1关联
c2.getProducts().add(p1);
transaction.commit();
session.close();
}
二、多对多的关系
1.概念
多对多关系是关系数据库中两个表之间的一种数据关系,为了维护这种关系,通常会存在一张中间关系表。两张表都只和关系表间建立主外键关系。
2.Hibernate的多对多关联映射【重点】
2.1表关系的分析
MySql语句
2.2创建实体
- Student.java
public class Student {
private Integer sid;
private String sname;
private Set<Course> courses = new HashSet<Course>();
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 Set<Course> getCourses() {
return courses;
}
public void setCourses(Set<Course> courses) {
this.courses = courses;
}
}
public class Course {
private Integer cid;
private String cname;
private Set<Student> students = 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 Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
}
2.3创建映射
- 学生的映射
<hibernate-mapping>
<class name="com.itheima.bean.Student" table="t_student">
<!--一,主键属性 -->
<id name="sid" column="sid">
<generator class="native"></generator>
</id>
<!-- 二,其它属性 -->
<property name="sname" column="sname"/>
<!-- 三,表示和课程的关系 -->
<!--3.1 set标签的name属性:当前类中集合的名字
table:第三方表名
-->
<set name="courses" table="s_c_tab">
<!--3.2 key的 column表示当前类在中间表中的外键 -->
<key column="sid" />
<!--3.3 many-to-many表示多对多关系
column:表示另一方在中间表中的外键
class:表示另一方类的全限定名
-->
<many-to-many column="cid" class="com.itheima.bean.Course" ></many-to-many>
</set>
</class>
</hibernate-mapping>
- 课程的映射
<hibernate-mapping>
<class name="com.itheima.bean.Course" table="t_course">
<!--一,主键属性 -->
<id name="cid" column="cid">
<generator class="native"></generator>
</id>
<!-- 二,其它属性 -->
<property name="cname" column="cname"/>
<!-- 三,表示和课程的关系 -->
<!--3.1 set标签的name属性:当前类中集合的名字
table:第三方表名
-->
<set name="students" table="s_c_tab">
<!--3.2 key的 column表示当前类在中间表中的外键 -->
<key column="cid"/>
<!--3.3 many-to-many表示多对多关系
column:表示另一方在中间表中的外键
class:表示另一方类的全限定名
-->
<many-to-many column="sid" class="com.itheima.bean.Student"></many-to-many>
</set>
</class>
</hibernate-mapping>
2.4将映射添加到配置文件
<!-- 三.加载映射关系配置 -->
<mapping resource="com/pri/bean/Student.hbm.xml"/>
<mapping resource="com/pri/bean/Course.hbm.xml"/>
2.5写代码测试
- 保存
/**
* 正常保存
*/
@Test
public void fun01(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Student s1 = new Student();
s1.setSname("张三");
Student s2 = new Student();
s2.setSname("李四");
Course c1 = new Course();
c1.setCname("C语言");
Course c2 = new Course();
c2.setCname("Java");
Course c3 = new Course();
c3.setCname("前端");
//s1选择了c1和c2
s1.getCourses().add(c1);
s1.getCourses().add(c2);
//s2选择了c2 c3
s2.getCourses().add(c2);
s2.getCourses().add(c3);
//c1被s1选了
c1.getStudents().add(s1);
//c2被s1,s2选了
c2.getStudents().add(s1);
c2.getStudents().add(s2);
//c3被s2选了
c3.getStudents().add(s2);
//如果双向关联,一定要一方放弃主键维护
session.save(s1);
session.save(s2);
session.save(c1);
session.save(c2);
session.save(c3);
transaction.commit();
session.close();
}
3.级联操作
3.1级联保存【掌握】
- 保存Student级联保存Course
<set name="courses" table="s_c_table" cascade="save-update,delete">
<!-- 3.2 key的column表示当前类在中间表的外键 -->
<key column="sid"/>
<!-- 3.3 many-to-many 表示多对对关系
column:表示另一方在中间表中的外键
class:表示另一方类的全限定名
-->
<many-to-many class="com.pri.bean.Course" column="cid"/>
</set>
/**
* 保存Student级联保存Course
* 在student.hbm.xml里面配置:<set name="courses" table="s_c_tab" cascade="save-update">
*/
@Test
public void fun02(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Student s1 = new Student();
s1.setSname("张三");
Student s2 = new Student();
s2.setSname("李四");
Course c1 = new Course();
c1.setCname("C语言");
Course c2 = new Course();
c2.setCname("Java");
Course c3 = new Course();
c3.setCname("前端");
//s1选择了c1和c2
s1.getCourses().add(c1);
s1.getCourses().add(c2);
//s2选择了c2 c3
s2.getCourses().add(c2);
s2.getCourses().add(c3);
//c1被s1选了
c1.getStudents().add(s1);
//c2被s1,s2选了
c2.getStudents().add(s1);
c2.getStudents().add(s2);
//c3被s2选了
c3.getStudents().add(s2);
//如果双向关联,一定要一方放弃外键维护
session.save(s1);
session.save(s2);
/* session.save(c1);
session.save(c2);
session.save(c3);*/
transaction.commit();
session.close();
}
- 保存Course级联保存Student
<!-- 三.表示和课程的关系 -->
<!-- 3.1 set标签的name属性: 当前类中集合的名字 -->
<set name="students" table="s_c_table" cascade="save-update" inverse="true">
<!-- 3.2 key的column表示当前类在中间表的外键 -->
<key column="cid"/>
<!-- 3.3 many-to-many 表示多对对关系
column:表示另一方在中间表中的外键
class:表示另一方类的全限定名
-->
<many-to-many class="com.pri.bean.Student" column="sid"/>
</set>
/**
* 保存Course级联保存Student
* 在Course.hbm.xml里面配置:<set cascade="save-update">
*/
@Test
public void fun03(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Student s1 = new Student();
s1.setSname("张三");
Student s2 = new Student();
s2.setSname("李四");
Course c1 = new Course();
c1.setCname("C语言");
Course c2 = new Course();
c2.setCname("Java");
Course c3 = new Course();
c3.setCname("前端");
//s1选择了c1和c2
s1.getCourses().add(c1);
s1.getCourses().add(c2);
//s2选择了c2 c3
s2.getCourses().add(c2);
s2.getCourses().add(c3);
//c1被s1选了
c1.getStudents().add(s1);
//c2被s1,s2选了
c2.getStudents().add(s1);
c2.getStudents().add(s2);
//c3被s2选了
c3.getStudents().add(s2);
//如果双向关联,一定要一方放弃主键维护
/*session.save(s1);
session.save(s2);*/
session.save(c1);
session.save(c2);
session.save(c3);
transaction.commit();
session.close();
}
注意的地方:
如果双向关联,一定要一方放弃主键维护
3.2级联删除【了解】
- 删除Student,级联删除Course 高数
/**
* 配置了级联删除 在Student.hbm.xml配置<set name="courses" table="s_c_tab" cascade="delete">
* 把id为1的学生删除,
* id为1的学生之前选过: id位1,2的课程.
* 级联删除既会删除正向多方数据库表中的记录,也会删除反向多方表中的记录和中间关系表中的记录
*/
@Test
public void fun02(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Student student = session.get(Student.class, 1);
session.delete(student);
transaction.commit();
session.close();
}
注意的地方:
非级联删除只会删除正向多方数据库表和中间关系表中的记录,不会删除反向多方表中的记录
级联删除既会删除正向多方数据库表中的记录,也会删除反向多方表中的记录和中间关系表中的记录
开发过程中用的比较少
3.3其它操作
- 某个学生退课
/**
* 某个学生退课
* id为1的学生之前选过: id为1,2的课程.
* 现在的操作是:id为1的学生退掉ID为2的课程
*/
@Test
public void fun01(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//获得id为1的学生
Student s1 = session.get(Student.class, 1);
//获得id为2的课程
Course c2 = session.get(Course.class, 2);
s1.getCourses().remove(c2);
transaction.commit();
session.close();
}
- 某个学生改课
/**
* 某个学生退课
* id为1的学生之前选过: id为1,2的课程.
* 现在的操作是:id为1的学生把id为2的课程改成id为3的课程
*/
@Test
public void fun02(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//获得id为1的学生
Student s1 = session.get(Student.class, 1);
//获得id为2的课程
Course c2 = session.get(Course.class, 2);
Course c3 = session.get(Course.class, 3);
s1.getCourses().remove(c2);
s1.getCourses().add(c3);
transaction.commit();
session.close();
}