多对多映射
前言
hibernate中既有一对多映射关系,也有直接映射的多对多关联关系,多对多的映射关系可以看作是两个一对多的映射!
多对多映射
以下的案例可以看成是一本书对应多个类别,一个类别对应多本书的之间的映射关系!
web.xml:
将原有的web版本修改为3.0的版本就好!
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
</web-app>
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wangqiuping</groupId>
<artifactId>hibernateManytoMany</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>hibernateManytoMany Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<!--junit依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--servlet依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!--mysql依赖 -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--Tomcat的jsp依赖 -->
<!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-jsp-api -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jsp-api</artifactId>
<version>8.5.0</version>
</dependency>
<!--hibernate依赖 -->
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.12.Final</version>
</dependency>
</dependencies>
<build>
<finalName>hibernateManytoMany</finalName>
<!--jdk依赖 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
1、实现思路
多对多:一张表包含另外一张表的set对象,中间表不需要新建实体类
2、使用到的数据库
数据库脚本
-- 书本类别表
create table t_category_hb
(
category_id int primary key auto_increment,
category_name varchar(50) not null
);
-- 书本表
create table t_book_hb
(
book_id int primary key auto_increment,
book_name varchar(50) not null,
price float not null
);
-- 桥接表
-- 定义三个列,其实只要两个列
-- 一个类别对应多本书,一本书对应多个类别
CREATE TABLE t_book_category_hb
(
bcid INT PRIMARY KEY AUTO_INCREMENT,
bid INT NOT NULL,
cid INT NOT NULL,
FOREIGN KEY(bid) REFERENCES t_book_hb(book_id),
FOREIGN KEY(cid) REFERENCES t_category_hb(category_id)
);
给数据库表格添加数据:
---给书籍表增加数据
insert into t_book_hb(book_id, book_name, price) values(1,'西游记',50);
insert into t_book_hb(book_id, book_name, price) values(2,'红楼梦',50);
insert into t_book_hb(book_id, book_name, price) values(3,'水浒',50);
insert into t_book_hb(book_id, book_name, price) values(4,'三国演义',50);
---给数据类别表增加数据
insert into t_category_hb(category_id, category_name) values(1,'古典');
insert into t_category_hb(category_id, category_name) values(2,'神话');
insert into t_category_hb(category_id, category_name) values(3,'历史');
---给中间表增加数据
insert into t_book_category_hb(bid, cid) values(1,1);
insert into t_book_category_hb(bid, cid) values(1,2);
insert into t_book_category_hb(bid, cid) values(2,1);
insert into t_book_category_hb(bid, cid) values(3,1);
insert into t_book_category_hb(bid, cid) values(3,3);
insert into t_book_category_hb(bid, cid) values(4,1);
insert into t_book_category_hb(bid, cid) values(4,3);
查询表格:
select * from t_book_hb;
select * from t_category_hb;
select * from t_book_category_hb;
3、执行后的数据库
书籍表
书籍类别表
书籍、书籍类别中间表
代码实现
1、实体类
Book类
放属性的set、get方法、有参、无参构造方法、toString方法
package com.wangqiuping.entity;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
* 书籍表的实体类
* 多种书籍对应多种书籍类型
* 多对多:一张表包含另外一张表的set对象
* @author wangqiuping
* 2020年7月27日 下午6:25:38
*/
public class Book implements Serializable{
private Integer bookId;
private String bookName;
private float price;
Set<Category> categories=new HashSet<Category>();
public Set<Category> getCategories() {
return categories;
}
public void setCategories(Set<Category> categories) {
this.categories = categories;
}
public Integer getBookId() {
return bookId;
}
public void setBookId(Integer bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public Book() {
super();
}
public Book(Integer bookId, String bookName, float price, Set<Category> categories) {
super();
this.bookId = bookId;
this.bookName = bookName;
this.price = price;
this.categories = categories;
}
@Override
public String toString() {
return "Book [bookId=" + bookId + ", bookName=" + bookName + ", price=" + price + ", categories=" + categories
+ "]";
}
}
Category:
package com.wangqiuping.entity;
import java.io.Serializable;
import java.util.HashSet;
/**
* 书籍类别表的实体类
* 一个书籍类型包含多本书
* @author wangqiuping
* 2020年7月27日 下午6:30:48
*/
import java.util.Set;
public class Category implements Serializable{
private Integer categoryId;
private String categoryName;
Set<Book> books=new HashSet<Book>();
public Set<Book> getBooks() {
return books;
}
public void setBooks(Set<Book> books) {
this.books = books;
}
public Integer getCategoryId() {
return categoryId;
}
public void setCategoryId(Integer categoryId) {
this.categoryId = categoryId;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public Category() {
super();
}
public Category(Integer categoryId, String categoryName, Set<Book> books) {
super();
this.categoryId = categoryId;
this.categoryName = categoryName;
this.books = books;
}
@Override
public String toString() {
return "Category [categoryId=" + categoryId + ", categoryName=" + categoryName + ", books=" + books + "]";
}
}
实体类映射配置文件
hibernate中只能有一个核hibernate.cfg.xml,但是可以有多个实体类映射文件!
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="connection.username">root</property>
<!--连接账户密码-->
<property name="connection.password">123</property>
<!--连接的绝对路径 -->
<property name="connection.url">jdbc:mysql://localhost:3306/t243?&useUnicode=true&characterEncoding=UTF-8&userSSL=false
</property>
<!-- 驱动的绝对路径 -->
<property name="connection.driver_class">
com.mysql.jdbc.Driver
</property>
<!--数据库方言的相关配置 -->
<property name="dialect">org.hibernate.dialect.MySQLDialect
</property>
<!--调试相关配置 -->
<!-- hibernate运行过程是否站后四自动生成的SQL代码 -->
<property name="show_sql">true</property>
<!-- 是否规范化输出sql代码 -->
<property name="format_sql">true</property>
<!--实体映射相关配置 -->
<mapping resource="com/wangqiuping/entity/Book.hbm.xml"/>
<mapping resource="com/wangqiuping/entity/Category.hbm.xml"/>
</session-factory>
</hibernate-configuration>
实体类的相关配置一定要加进去,否则配置文件无法读取到实体类的配置文件!
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.wangqiuping.entity.Book" table="t_book_hb">
<id name="bookId" type="java.lang.Integer" column="book_id">
<generator class="increment"></generator>
</id>
<property name="bookName" type="java.lang.String" column="book_name"></property>
<property name="price" type="java.lang.Float" column="price"></property>
<!-- 多对多映射关系 -->
<!--
name:一方包含多方的属性对象名称 指向多方
cascade:级联操作 save-update/none/all=delete+save-update
inverse:是否是主控方 false表示对方不是主控方
true表示对方是主控方 由对方来维护中间表
table:表示中间表的名称
-->
<set name="categories"
cascade="save-update"
inverse="false"
table="t_book_category_hb"
>
<!--column:指向己方在中间表的外键字段 -->
<key column="bid"></key>
<!-- class:对方实例的完整路径
column:对方在中间表中的外键字段
-->
<many-to-many
class="com.wangqiuping.entity.Category"
column="cid">
</many-to-many>
</set>
</class>
</hibernate-mapping>
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.wangqiuping.entity.Category" table="t_category_hb">
<id name="categoryId" type="java.lang.Integer" column="category_id">
<generator class="increment"></generator>
</id>
<property name="categoryName" type="java.lang.String" column="category_name"></property>
<set name="books"
cascade="save-update"
inverse="true"
table="t_book_category_hb">
<key column="cid"></key>
<many-to-many
class="com.wangqiuping.entity.Book"
column="bid">
</many-to-many>
</set>
</class>
</hibernate-mapping>
2、dao方法
BookDao
package com.wangqiuping.dao;
import org.hibernate.Session;
import org.hibernate.Transaction;
import com.wangqiuping.entity.Book;
import com.wangqiuping.util.SessionFactoryUtils;
/**
* 书籍的dao方法
* @author wangqiuping
* 2020年7月27日 下午8:25:31
*/
public class BookDao {
public void addBook(Book book) {
Session session = SessionFactoryUtils.openSession();
Transaction ts = session.beginTransaction();
session.save(book);
ts.commit();
SessionFactoryUtils.closeSession();
}
}
SessionFactoryUtils工具类
package com.wangqiuping.util;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/**
* 提供session的开启和关闭
* @author wangqiuping
* 2020年7月28日 下午4:01:33
*/
public class SessionFactoryUtils {
private static final String
HIBERNATE_CONFIG_FILE="hibernate.cfg.xml";
private static ThreadLocal<Session> threadLocal=
new ThreadLocal<Session>();
//创建数据库的会话工厂
private static SessionFactory sessionFactory;
//读取hibernate核心配置
private static Configuration configuration;
static {
try {
configuration=new Configuration();
configuration.configure(HIBERNATE_CONFIG_FILE);
//创建Session会话工厂
sessionFactory=configuration.buildSessionFactory();
} catch (Exception e) {
e.printStackTrace();
}
}
public static Session openSession() {
Session session = threadLocal.get();
if(null==session) {
session=sessionFactory.openSession();
threadLocal.set(session);
}
return session;
}
public static void closeSession() {
Session session = threadLocal.get();
if(null!=session) {
if(session.isOpen())
session.close();
threadLocal.set(null);
}
}
public static void main(String[] args) {
Session session = SessionFactoryUtils.openSession();
System.out.println("Session状态:"+session.isOpen());
System.out.println("Session会话已打开");
SessionFactoryUtils.closeSession();
System.out.println("Session会话已关闭");
}
}
3、junit测试代码
package com.wangqiuping.dao;
import com.wangqiuping.entity.Book;
import com.wangqiuping.entity.Category;
import junit.framework.TestCase;
/**
*
* @author wangqiuping
* 2020年7月28日 下午5:32:08
*/
public class BookDaoTest extends TestCase {
BookDao bookDao=new BookDao();
Book book=null;
protected void setUp() throws Exception {
book=new Book();
super.setUp();
}
public void testAddBook() {
book.setBookName("皇帝的新衣");
book.setPrice(100f);
Category category=new Category();
category.setCategoryName("寓言");
Category category1=new Category();
category1.setCategoryName("故事");
book.getCategories().add(category);
book.getCategories().add(category1);
category.getBooks().add(book);
category1.getBooks().add(book);
bookDao.addBook(book);
}
}
两次一样的查询、添加数据的效果都是一样的,如图:
书籍表
书籍类别表
书籍类别中间表
书籍查询
原有的书籍表会增加一条数据,书籍类别表未发生改变,书籍、书籍类别中间表会新增一条数据!
CategoryDao
package com.wangqiuping.dao;
import org.hibernate.Session;
import org.hibernate.Transaction;
import com.wangqiuping.entity.Category;
import com.wangqiuping.util.SessionFactoryUtils;
/**
*
* @author wangqiuping
* 2020年7月28日 下午5:58:53
*/
public class CategoryDao {
public Category getCategoryById(Category category) {
Session session = SessionFactoryUtils.openSession();
Transaction ts = session.beginTransaction();
//根据对象和id获取
Category c = session.get(Category.class,category.getCategoryId());
ts.commit();
SessionFactoryUtils.closeSession();
return c;//返回查询后的结果集
}
}
junit测试代码
package com.wangqiuping.dao;
import com.wangqiuping.entity.Book;
import com.wangqiuping.entity.Category;
import junit.framework.TestCase;
public class CategoryDaoTest extends TestCase {
CategoryDao categoryDao=null;
BookDao bookDao=new BookDao();
protected void setUp() throws Exception {
categoryDao=new CategoryDao();
}
public void testGetCategoryById() {
Book book=new Book();
book.setBookName("解忧杂货店");
book.setPrice(100f);
Category category=new Category();
category.setCategoryId(4);
Category c = categoryDao.getCategoryById(category);
book.getCategories().add(c);
bookDao.addBook(book);
}
}
数据库的展示效果:
书籍表
书籍类别表:
仍然未发生改变!
书籍、书籍类别中间表:
数据库的多对多
1、 数据库中不能直接映射多对多
处理:创建一个中间表,将一个多对多关系转换成两个一对多
数据库多表联接查询永远就是两个表的联接查询
当然也可以进行交叉连接,以及左联或者右联!
注意事项
1、 一定要定义一个主控方
因为主控方会影响中间表产生的行数,两张表中间只能有一个主控方,两张表都做主控方的话,中间表的影响行数会增加一倍!
2、多对多删除,主控方直接删除,被控方先通过主控方解除多对多关系,再删除被控方
3、 禁用级联删除
4、关联关系编辑,不需要直接操作桥接表,hibernate的主控方会自动维护
总结
hibernate的多对多映射其实就是多个一对多映射,弄清楚主从表之间的关系,谁为主控方,以及映射之间的关联,基本上大致的流程就出来了!