hibernate多对多级联查询、新增、删除

本文以书籍和类别为例,介绍了Hibernate中多对多关系的映射配置及级联查询、新增和删除操作。重点讲解了中间表的使用,以及在级联操作时如何维护中间表数据。警告:级联删除可能导致意外数据丢失,需谨慎使用。
摘要由CSDN通过智能技术生成

以书籍与书籍类别为例:

一本书可以有多种类型,一种类型也可以对应多本书,书籍和书籍类别的关系是多对多的关系,他们的关系是在中间表里面的。

 

多对多通过一个表找到另一个表的数据的条件为:

中间表

中间表对应本表的外键

中间表对应关联表的外键

注:多对多的时候,两个表的关系是通过中间表建立的。

 

代码:

-- 书本类别表
create table t_hibernate_category
(
   category_id int primary key auto_increment,
   category_name varchar(50) not null
);
 
-- 书本表
create table t_hibernate_book
(
   book_id int primary key auto_increment,
   book_name varchar(50) not null,
   price float not null
);
 
 
-- 桥接表
-- 定义三个列,其实只要两个列
-- 一个类别对应多本书,一本书对应多个类别
create table t_hibernate_book_category
(
  bcid int primary key auto_increment,  
  bid int not null,
  cid int not null,
  foreign key(bid) references t_hibernate_book(book_id),
  foreign key(cid) references t_hibernate_category(category_id)
);
 
-- 插入书籍表数据
insert into t_hibernate_book(book_id, book_name, price) values(1,'西游记',50);
insert into t_hibernate_book(book_id, book_name, price) values(2,'红楼梦',50);
insert into t_hibernate_book(book_id, book_name, price) values(3,'水浒',50);
insert into t_hibernate_book(book_id, book_name, price) values(4,'三国演义',50);
 
-- 插入类型表数据
insert into t_hibernate_category(category_id, category_name) values(1,'古典');
insert into t_hibernate_category(category_id, category_name) values(2,'神话');
insert into t_hibernate_category(category_id, category_name) values(3,'历史');
 
-- 插入中间表数据
insert into t_hibernate_book_category(bid, cid) values(1,1);
insert into t_hibernate_book_category(bid, cid) values(1,2);
insert into t_hibernate_book_category(bid, cid) values(2,1);
insert into t_hibernate_book_category(bid, cid) values(3,1);
insert into t_hibernate_book_category(bid, cid) values(3,3);
insert into t_hibernate_book_category(bid, cid) values(4,1);
insert into t_hibernate_book_category(bid, cid) values(4,3);

实体类:

Book.java

package com.hibernate.entity;
 
import java.util.HashSet;
import java.util.Set;
 
public class Book {
 
	private Integer book_id;
	private String book_name;
	private Float price;
        /*
         *  这本书所对应的类型的集合
         */
	private Set<Category> categorys=new HashSet<>();
	public Set<Category> getCategorys() {
		return categorys;
	}
	public void setCategorys(Set<Category> categorys) {
		this.categorys = categorys;
	}
	public Integer getBook_id() {
		return book_id;
	}
	public void setBook_id(Integer book_id) {
		this.book_id = book_id;
	}
	public String getBook_name() {
		return book_name;
	}
	@Override
	public String toString() {
		return "Book [book_id=" + book_id + ", book_name=" + book_name + ", price=" + price + "]";
	}
	public void setBook_name(String book_name) {
		this.book_name = book_name;
	}
	public Float getPrice() {
		return price;
	}
	public void setPrice(Float price) {
		this.price = price;
	}
}

新建一个xml文件,配置书籍实体的映射 ,Book.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.hibernate.entity.Book" table="t_hibernate_book">
		<id name="book_id" type="java.lang.Integer" column="book_id">
			<generator class="increment"></generator>
		</id>
		<property name="book_name" type="java.lang.String" column="book_name"></property>
		<property name="price" type="java.lang.Float" column="price"></property>
		<!-- 以上为实体中的基本属性 -->
 
                <!-- 
                        set标签对应实体中的set集合,若实体中使用的是List集合,这里则使用list标签
			name: 指的是当前映射实体
			table: 对应的是中间表的表名 
		 -->
		<set name="categorys" table="t_hibernate_book_category">
		<!-- 中间表字段(与当前映射实体对应的表的主键相关联的那个字段) -->
			<key column="bid"></key>
			<!-- 
				class: ‘多’方的全限定名,这个多方也可以叫对方,也就是类别
				column: 中间表字段(与‘多’方主键相关联的字段)
			-->
			<many-to-many class="com.hibernate.entity.Category" column="cid"></many-to-many>
		</set>
	</class>
</hibernate-mapping>

Category.java

package com.hibernate.entity;
 
import java.util.HashSet;
import java.util.Set;
 
public class Category {
 
	private Integer category_id;
	private String category_name;
        /*
         *  该类型所对应的书籍的集合
         */
	private Set<Book> books=new HashSet<>();
	public Set<Book> getBooks() {
		return books;
	}
	public void setBooks(Set<Book> books) {
		this.books = books;
	}
	public Integer getCategory_id() {
		return category_id;
	}
	public void setCategory_id(Integer category_id) {
		this.category_id = category_id;
	}
	public String getCategory_name() {
		return category_name;
	}
	public void setCategory_name(String category_name) {
		this.category_name = category_name;
	}
	@Override
	public String toString() {
		return "Category [category_id=" + category_id + ", category_name=" + category_name + "]";
	}
}

新建一个xml文件,配置类别实体的映射,Category.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.hibernate.entity.Category" table="t_hibernate_category">
		<id name="category_id" type="java.lang.Integer" column="category_id">
			<generator class="increment"></generator>
		</id>
		<property name="category_name" type="java.lang.String" column="category_name"></property>
		<!-- 
                        set标签对应实体中的set集合,若实体中使用的是List集合,这里则使用list标签
			name: 指的是当前映射实体
			table: 对应的是中间表的表名 ,
			
			关联关系交与对方管理(中间表的数据是否交给对方维护)
			cascade
			inverse
		 -->
		<set name="books" table="t_hibernate_book_category" cascade="save-update" inverse="true">
			<!-- 中间表字段(与当前映射实体对应的表的主键相关联的那个字段) -->
			<key column="cid"></key>
			<!-- 
				class: ‘多’方的全限定名,这个多方也可以叫对方,也就是类别
				column: 中间表字段(与‘多’方主键相关联的字段)
			-->
			<many-to-many class="com.hibernate.entity.Book" column="bid"></many-to-many>
		</set>
	</class>
</hibernate-mapping>

将书籍与类别的实体映射文件配置到hibernate的核心配置文件中:
(核心配置文件也是新建的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="connection.username">root</property>
		<property name="connection.password">123</property>	
		<property name="connection.url">jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT</property>
		<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
		<!-- 配置sql语句生成的规则,配置数据库方言 -->
		<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
		<!-- 配置本地事务 -->
		<property name="hibernate.current_session_context_class">thread</property>
		<!-- 配置开发调试所用的配置show_sql,format_sql -->
		<property name="show_sql">true</property>
		<property name="format_sql">true</property>
		<!-- 配置映射文件 -->
		<mapping resource="com/hibernate/entity/Book.hbm.xml"/>
		<mapping resource="com/hibernate/entity/Category.hbm.xml"/>
	</session-factory>
</hibernate-configuration>

测试代码:

注:HibernateUtils,java 工具类在我之前写的博客中有代码,这里就不贴了:

hibernate中一对多双向关联的记录

1.查询

//需求:通过ID查询书籍的信息,同时查询书籍对应的类别的信息。
@Test
public void testGet() {
            //1.获得session会话
		Session session = HibernateUtils.getSession();
            //2.开启事务
		Transaction beginTransaction = session.beginTransaction();
            //3.数据库操作
		Book b=new Book();
		b.setBook_id(1);
		Book book = session.get(Book.class,b.getBook_id());
                System.out.println(book);
		book.getCategorys().stream().forEach(System.out::println);
            //4.关闭事务
		beginTransaction.commit();
            //5.关闭会话
		session.close();
}

输出结果:

 2.新增
由于我们之前在类别的映射文件Category.hbm.xml中设置了  cascade="save-update" inverse="true" ,设置这几个属性则表示新增与修改时,中间表的数据是否由对方维护,如果inverse设置为true,则表示由对方维护(即由书籍维护中间表数据)。所以我们这里的中间表数据就由书籍来维护。

//需求:通过新增书籍,同时在中间表中新增该书籍与其对应的类别。
@Test
public void testSave() {
	Session session = HibernateUtils.getSession();
	Transaction beginTransaction = session.beginTransaction();
 
      //实例化一个Book对象
        Book b=new Book();
      //初始化book对象
	b.setBook_name("斗破苍穹");
	b.setPrice(10f);
      //为Book对象设置它的书籍类别
	Category c=new Category();
	c.setCategory_id(1);		
        Category c2=new Category();
	c2.setCategory_id(2);
	b.getCategorys().add(c);
	b.getCategorys().add(c2);
      //执行新增操作
	Integer save = (Integer) session.save(b);
 
	beginTransaction.commit();
	session.close();
}

由于我们设置了中间表数据由书籍维护,在数据库中刷新一下数据会发现,书籍表中斗破苍穹添加成功了,中间表也将对应的书籍id以及类型id添加了进去,所以增加是成功的;不过这里还要举一个反面的例子:添加类别,且为类别中的Set集合加入书籍

需求:通过新增类别,同时在中间表中新增该类别与其对应的书籍。


@Test
	public void testSaveC() {
                Session session = HibernateUtils.getSession();
    		Transaction beginTransaction = session.beginTransaction()
              //实例化一个类型对象
		Category c=new Category();
              //初始化类型对象
		c.setCategory_name("穿越");
             //为Category对象设置它的书籍
		Book book=new Book();
		book.setBook_id(6);
		book.setBook_name("a");
		book.setPrice(1f);
		c.getBooks().add(book);
 
                Integer save = (Integer) session.save(c);
 
		beginTransaction.commit();
		session.close();
 
	}

 

注:由于中间表的数据是由书籍来维护的,所以新增类型时, 如果指定了书籍 ,中间表不仅不会添加数据,反而还会删除掉中间表中的数据。  故增加类别时,只做单纯的增加类别就行了,别指定书籍。

 

3.删除

//需求:根据书籍id删除书籍,同时删除中间表中该书籍所对应的数据
@Test
public void testDelete() {
	Session session = HibernateUtils.getSession();
	Transaction beginTransaction = session.beginTransaction();
      //执行删除操作
        Book book=new Book();
	book.setBook_id(5);
        session.delete(book);	
 
	beginTransaction.commit();
	session.close();
}

 删除与新增有着异曲同工之处,由于之前在类别的实体映射文件中配置了,中间表的数据由书籍去维护。

So,对Book进行删除时,中间表中该书籍对应的数据也会被删除掉,需求能够达成!

如果对类别进行删除,中间表有该类别的话是删除不了的(由于中间表关联了主外键),所以删除被控方(类别)会相对麻烦一点:


@Test
	public void testDeleteC() {
                Session session = HibernateUtils.getSession();
		Transaction beginTransaction = session.beginTransaction();
	    //要删除的类别id
                Category c=new Category();
		c.setCategory_id(4);
 
                /**
	         * 被控方的删除:
	         * 	1、先要获取到被控方的数据
	         * 	2、利用被控方获取到主控方来解除关联关系
	         * 	3、最后将被控方删除
	         */
	    	Category category = session.get(Category.class, c.getCategory_id());
		for (Book b : category.getBooks()) {
			b.getCategorys().remove(category);
		}
		session.delete(category);
 
		beginTransaction.commit();
		session.close();
	}

注:删除被控方数据时,如果中间表中关联了要删除的那条数据,则中间表中关联的数据也会被一同删除。

温馨提示:级联删除慎用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值