Hibernate多对多映射:深度解析与连接表最佳实践

这是文章《Hibernate多对多映射-连接表》的第1部分(共5部分)。

今天我们将学习如何在Hibernate中使用XML和注解配置进行多对多映射。在此之前,我们已经学习了如何在Hibernate中实现一对一和一对多的映射方式。

Hibernate 的多对多关系

通常在数据库中,多对多映射是通过连接表来实现的。例如,我们可以有购物车(Cart)表和商品(Item)表,以及用于多对多映射的购物车和商品的连接表(Cart_Items)。每个购物车可以包含多个商品,每个商品也可以属于多个购物车,因此这里存在一个多对多的映射关系。

设置数据库以实现Hibernate多对多映射

以下脚本可用于创建我们的多对多示例数据库表,这些脚本适用于MySQL数据库。如果您使用其他数据库,可能需要进行一些小的更改才能使其正常工作。

DROP TABLE IF EXISTS `Cart_Items`;
DROP TABLE IF EXISTS `Cart`;
DROP TABLE IF EXISTS `Item`;

CREATE TABLE `Cart` (
  `cart_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `cart_total` decimal(10,0) NOT NULL,
  PRIMARY KEY (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `Item` (
  `item_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `item_desc` varchar(20) NOT NULL,
  `item_price` decimal(10,0) NOT NULL,
  PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `Cart_Items` (
  `cart_id` int(11) unsigned NOT NULL,
  `item_id` int(11) unsigned NOT NULL,
  PRIMARY KEY (`cart_id`,`item_id`),
  CONSTRAINT `fk_cart` FOREIGN KEY (`cart_id`) REFERENCES `Cart` (`cart_id`),
  CONSTRAINT `fk_item` FOREIGN KEY (`item_id`) REFERENCES `Item` (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

请注意,Cart_Items表没有任何额外的列。实际上,在多对多映射表中添加额外列的意义不大。但是,如果您确实有额外的列,实现方式会略有变化,我们将在另一篇文章中详细介绍。下面的图示显示了这些表之间的实体关系。我们的数据库设置已经准备好了,现在让我们继续创建Hibernate多对多映射项目。

Hibernate多对多映射项目结构

在Eclipse或您喜欢的IDE中创建一个Maven项目,下图展示了应用程序的结构和不同的组件。我们将首先研究基于XML的映射实现,然后转向使用JPA注解。

Hibernate的Maven依赖

我们最终的pom.xml文件包含有最新版本4.3.5.Final的Hibernate依赖和MySQL驱动依赖。

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.Olivia.hibernate</groupId>
	<artifactId>HibernateManyToManyMapping</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencies>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>4.3.5.Final</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.0.5</version>
		</dependency>
	</dependencies>

</project>

在XML配置文件中,使用Hibernate实现多对多关系的模型类

Cart.java(购物车)

package com.Olivia.hibernate.model;

import java.util.Set;

public class Cart {

	private long id;
	private double total;

	private Set<Item> items;
	
	public double getTotal() {
		return total;
	}

	public void setTotal(double total) {
		this.total = total;
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public Set<Item> getItems() {
		return items;
	}

	public void setItems(Set<Item> items) {
		this.items = items;
	}

}

Item.java(商品)

package com.Olivia.hibernate.model;

import java.util.Set;

public class Item {

	private long id;
	private double price;
	private String description;

	private Set<Cart> carts;
	
	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public Set<Cart> getCarts() {
		return carts;
	}

	public void setCarts(Set<Cart> carts) {
		this.carts = carts;
	}
}

请注意,购物车(Cart)拥有一个商品(Item)集合,而商品(Item)则拥有一个购物车(Cart)集合,这样我们就实现了双向关联。这意味着我们可以配置它在保存购物车时同时保存商品,反之亦然。对于单向映射,通常我们只在其中一个模型类中设置集合。我们将使用注解来实现单向映射。

Hibernate的多对多映射XML配置

让我们为购物车(Cart)和商品(Item)创建Hibernate的多对多映射XML配置文件。我们将实现双向多对多映射。

cart.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
	"https://hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.Olivia.hibernate.model">
	<class name="Cart" table="CART">
		<id name="id" type="long">
			<column name="cart_id" />
			<generator class="identity" />
		</id>
		<property name="total" type="double" column="cart_total" />

		<set name="items" table="CART_ITEMS" fetch="select" cascade="all">
			<key column="cart_id" />
			<many-to-many class="Item" column="item_id" />
		</set>
	</class>

</hibernate-mapping>

请注意,该<set>元素映射到CART_ITEMS表。由于Cart是主要对象,所以cart_id是关键,并且使用Item类的item_id列进行多对多映射。

item.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 
"https://hibernate.org/dtd/hibernate-mapping-3.0.dtd" >

<hibernate-mapping package="com.Olivia.hibernate.model">

	<class name="Item" table="ITEM">
		<id name="id" type="long">
			<column name="item_id" />
			<generator class="identity" />
		</id>
		<property name="description" type="string" column="item_desc" />

		<property name="price" type="double" column="item_price" />

		<set name="carts" table="CART_ITEMS" fetch="select" cascade="all">
			<key column="item_id" />
			<many-to-many class="Cart" column="cart_id" />
		</set>

	</class>

</hibernate-mapping>

从上述配置可以看出,这种映射与购物车(Cart)的映射配置非常相似。

基于XML的多对多映射Hibernate配置

我们的Hibernate配置文件 hibernate.cfg.xml 如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
		"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
		"https://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.password">scdev123</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
		<property name="hibernate.connection.username">scdev</property>
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

		<property name="hibernate.current_session_context_class">thread</property>
		<property name="hibernate.show_sql">true</property>

		<mapping resource="cart.hbm.xml" />
		<mapping resource="item.hbm.xml" />
	</session-factory>
</hibernate-configuration>

基于XML映射的Hibernate SessionFactory工具类

HibernateUtil.java -> HibernateUtil类

package com.Olivia.hibernate.util;

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

public class HibernateUtil {

	private static SessionFactory sessionFactory;

	private static SessionFactory buildSessionFactory() {
		try {
			// 从 hibernate.cfg.xml 创建 SessionFactory
			Configuration configuration = new Configuration();
			configuration.configure("hibernate.cfg.xml");
			System.out.println("Hibernate 配置已加载");

			ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
					.applySettings(configuration.getProperties()).build();
			System.out.println("Hibernate 服务注册表已创建");

			SessionFactory sessionFactory = configuration
					.buildSessionFactory(serviceRegistry);

			return sessionFactory;
		} catch (Throwable ex) {
			System.err.println("初始 SessionFactory 创建失败。" + ex);
			ex.printStackTrace();
			throw new ExceptionInInitializerError(ex);
		}
	}

	public static SessionFactory getSessionFactory() {
		if (sessionFactory == null)
			sessionFactory = buildSessionFactory();
		return sessionFactory;
	}

}

这是一个简单的工具类,用作 SessionFactory 的工厂。

测试程序:使用XML配置方式进行Hibernate多对多映射配置的测试

我们的多对多映射设置已经准备就绪,现在让我们来测试一下。我们将编写两个程序:一个用于保存购物车并检查商品和购物车-商品信息是否也已保存;另一个用于保存商品数据并检查相应的购物车和购物车-商品是否已保存。测试程序名为 HibernateManyToManyMain.java

这是文章《Hibernate多对多映射-连接表》的第3部分(共5部分)。

package com.Olivia.hibernate.main;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.Olivia.hibernate.model.Cart;
import com.Olivia.hibernate.model.Item;
import com.Olivia.hibernate.util.HibernateUtil;

public class HibernateManyToManyMain {
	
	// 保存多对多关系,以Cart为主
	public static void main(String[] args) {
		
		Item iphone = new Item();
		iphone.setPrice(100); iphone.setDescription("iPhone");
		
		Item ipod = new Item();
		ipod.setPrice(50); ipod.setDescription("iPod");
		
		Set<Item> items = new HashSet<Item>();
		items.add(iphone); items.add(ipod);
		
		Cart cart = new Cart();
		cart.setItems(items);
		cart.setTotal(150);
		
		Cart cart1 = new Cart();
		Set<Item> items1 = new HashSet<Item>();
		items1.add(iphone);
		cart1.setItems(items1);
		cart1.setTotal(100);
		
		SessionFactory sessionFactory = null;
		try{
		sessionFactory = HibernateUtil.getSessionFactory();
		Session session = sessionFactory.getCurrentSession();
		Transaction tx = session.beginTransaction();
		session.save(cart);
		session.save(cart1);
		System.out.println("事务提交前");
		tx.commit();
		sessionFactory.close();
		
		System.out.println("购物车ID=" + cart.getId());
		System.out.println("购物车1 ID=" + cart1.getId());
		System.out.println("商品1 ID=" + iphone.getId());
		System.out.println("商品2 ID=" + ipod.getId());
		
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if(sessionFactory != null && !sessionFactory.isClosed()) sessionFactory.close();
		}
		
	}

}

当我们执行上述Hibernate多对多映射示例程序时,会得到以下输出结果:

Hibernate Configuration loaded
Hibernate serviceRegistry created
Hibernate: insert into CART (cart_total) values (?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into CART (cart_total) values (?)
事务提交前
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
购物车ID=1
购物车1 ID=2
商品1 ID=1
商品2 ID=2

请注意,在第一次添加购物车时保存了商品数据后,会生成item_id,而在保存第二个购物车时不会再次保存。另一个重要的注意点是,当我们提交事务时,才会保存多对多关联表的数据。这样做是为了在选择回滚事务时提高性能。

HibernateBiDirectionalManyToManyMain.java

package com.Olivia.hibernate.main;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.Olivia.hibernate.model.Cart;
import com.Olivia.hibernate.model.Item;
import com.Olivia.hibernate.util.HibernateUtil;

public class HibernateBiDirectionalManyToManyMain {

	// 保存多对多关系,以Item为主
	public static void main(String[] args) {
		
		Item iphone = new Item();
		iphone.setPrice(100); iphone.setDescription("iPhone");
		
		Item ipod = new Item();
		ipod.setPrice(50); ipod.setDescription("iPod");
		
		Cart cart = new Cart();
		cart.setTotal(150);
		
		Cart cart1 = new Cart();
		cart1.setTotal(100);
		
		Set<Cart> cartSet = new HashSet<Cart>();
		cartSet.add(cart);cartSet.add(cart1);
		
		Set<Cart> cartSet1 = new HashSet<Cart>();
		cartSet1.add(cart);
		
		iphone.setCarts(cartSet1);
		ipod.setCarts(cartSet);
		
		SessionFactory sessionFactory = null;
		try{
		sessionFactory = HibernateUtil.getSessionFactory();
		Session session = sessionFactory.getCurrentSession();
		Transaction tx = session.beginTransaction();
		session.save(iphone);
		session.save(ipod);
		tx.commit();
		sessionFactory.close();
		
		System.out.println("购物车ID=" + cart.getId());
		System.out.println("购物车1 ID=" + cart1.getId());
		System.out.println("商品1 ID=" + iphone.getId());
		System.out.println("商品2 ID=" + ipod.getId());
		
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if(sessionFactory != null && !sessionFactory.isClosed()) sessionFactory.close();
		}
		
	}

}

以上程序的输出为:

Hibernate Configuration loaded
Hibernate serviceRegistry created
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into CART (cart_total) values (?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into CART (cart_total) values (?)
Hibernate: insert into CART_ITEMS (item_id, cart_id) values (?, ?)
Hibernate: insert into CART_ITEMS (item_id, cart_id) values (?, ?)
Hibernate: insert into CART_ITEMS (item_id, cart_id) values (?, ?)
购物车ID=3
购物车1 ID=4
商品1 ID=3
商品2 ID=4

由于我们配置了双向映射,您可以很容易地将其与之前的测试程序联系起来。我们可以保存ItemCart,并且映射的数据将自动保存。

使用注解映射多对多关系的Hibernate

既然我们已经了解了如何使用Hibernate XML配置实现多对多映射,那么现在让我们来看一个通过注解实现它的例子。我们将使用JPA注解来实现单向多对多映射。

Hibernate 配置 XML 文件

我们基于注解的Hibernate配置文件如下所示:hibernate-annotation.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
		"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
		"https://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.password">scdev123</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
		<property name="hibernate.connection.username">scdev</property>
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

		<property name="hibernate.current_session_context_class">thread</property>
		<property name="hibernate.show_sql">true</property>

		<mapping class="com.Olivia.hibernate.model.Cart1" />
		<mapping class="com.Olivia.hibernate.model.Item1" />
	</session-factory>
</hibernate-configuration>

Hibernate SessionFactory 实用类

我们用于创建 SessionFactory 的实用类如下所示:HibernateAnnotationUtil.java

package com.Olivia.hibernate.util;

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

public class HibernateAnnotationUtil {

	private static SessionFactory sessionFactory;
	
	private static SessionFactory buildSessionFactory() {
        try {
            // 从 hibernate-annotation.cfg.xml 创建 SessionFactory
        	Configuration configuration = new Configuration();
        	configuration.configure("hibernate-annotation.cfg.xml");
        	System.out.println("Hibernate 注解配置已加载");
        	
        	ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
        	System.out.println("Hibernate 注解服务注册表已创建");
        	
        	SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
        	
            return sessionFactory;
        }
        catch (Throwable ex) {
            System.err.println("SessionFactory 初始化创建失败。" + ex);
            ex.printStackTrace();
            throw new ExceptionInInitializerError(ex);
        }
    }
	
	public static SessionFactory getSessionFactory() {
		if(sessionFactory == null) sessionFactory = buildSessionFactory();
        return sessionFactory;
    }
}

使用Hibernate注解实现多对多映射关系

这是基于注解映射最重要的部分。让我们先看一下Item表模型类,然后再看Cart表模型类。

Item1.java

package com.Olivia.hibernate.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="ITEM")
public class Item1 {

	@Id
	@Column(name="item_id")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private long id;
	
	@Column(name="item_price")
	private double price;
	
	@Column(name="item_desc")
	private String description;
	
// Getter Setter 方法
}

Item1类看起来很简单,这里没有进行任何关系映射。

Cart1.java

package com.Olivia.hibernate.model;

import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

@Entity
@Table(name = "CART")
public class Cart1 {

	@Id
	@Column(name = "cart_id")
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private long id;

	@Column(name = "cart_total")
	private double total;

	@ManyToMany(targetEntity = Item1.class, cascade = { CascadeType.ALL })
	@JoinTable(name = "CART_ITEMS", 
				joinColumns = { @JoinColumn(name = "cart_id") }, 
				inverseJoinColumns = { @JoinColumn(name = "item_id") })
	private Set<Item1> items;

//Getter Setter 方法
}

这里最重要的部分是使用了@ManyToMany注解和@JoinTable注解,我们在其中提供了连接表的名称以及用于多对多映射的列。

使用Hibernate注解实现多对多关系映射的测试程序

这是我们基于注解配置的Hibernate多对多映射的简单测试程序:HibernateManyToManyAnnotationMain.java

package com.Olivia.hibernate.main;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.Olivia.hibernate.model.Cart1;
import com.Olivia.hibernate.model.Item1;
import com.Olivia.hibernate.util.HibernateAnnotationUtil;

public class HibernateManyToManyAnnotationMain {

	public static void main(String[] args) {
		Item1 item1 = new Item1();
		item1.setDescription("samsung"); 
		item1.setPrice(300);
		Item1 item2 = new Item1();
		item2.setDescription("nokia"); 
		item2.setPrice(200);
		Cart1 cart = new Cart1();
		cart.setTotal(500);
		Set<Item1> items = new HashSet<Item1>();
		items.add(item1); 
		items.add(item2);
		cart.setItems(items);
		
		SessionFactory sessionFactory = null;
		try{
		sessionFactory = HibernateAnnotationUtil.getSessionFactory();
		Session session = sessionFactory.getCurrentSession();
		Transaction tx = session.beginTransaction();
		session.save(cart);
		System.out.println("Before committing transaction");
		tx.commit();
		sessionFactory.close();
		
		System.out.println("购物车ID=" + cart.getId());
		System.out.println("商品1 ID=" + item1.getId());
		System.out.println("商品2 ID=" + item2.getId());
		
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if(sessionFactory != null && !sessionFactory.isClosed()) {
				sessionFactory.close();
			}
		}
	}

}

当我们执行上述程序时,它会产生以下输出:

Hibernate 注解配置已加载
Hibernate 注解服务注册已创建
Hibernate: 插入到 CART 表 (cart_total) 值 (?)
Hibernate: 插入到 ITEM 表 (item_desc, item_price) 值 (?, ?)
Hibernate: 插入到 ITEM 表 (item_desc, item_price) 值 (?, ?)
事务提交前
Hibernate: 插入到 CART_ITEMS 表 (cart_id, item_id) 值 (?, ?)
Hibernate: 插入到 CART_ITEMS 表 (cart_id, item_id) 值 (?, ?)
购物车 ID=5
商品1 ID=6
商品2 ID=5

很明显,保存购物车也会将数据保存到商品表和购物车-商品关联表中。如果你只保存商品信息,你会发现购物车和购物车-商品关联数据没有保存。以上就是关于Hibernate多对多映射示例教程的全部内容,你可以从下方链接下载示例项目并进行深入学习。

下载Hibernate ManyToMany 映射项目。

bannerAds