Hibernate多对多映射–连接表
今天,我们将研究使用XML和注释配置的Hibernate多对多映射。
之前,我们研究了如何在Hibernate中实现一对一和一对多映射。
Hibernate多对多
通常使用联接表在数据库中实现多对多映射。
例如,我们可以使用"购物车"和"商品"表以及"购物车商品"表进行多对多映射。
每个购物车可以有多个商品,每个商品可以是多个购物车的一部分,因此我们在这里有多对多映射。
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批注。
HibernateMaven依赖关系
我们最终的pom.xml包含最新版本为4.3.5的Hibernate依赖项.Final和mysql驱动程序依赖项。
pom.xml
<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.theitroad.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>
Hibernate多对多XML配置模型类
Cart.java
package com.theitroad.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.theitroad.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; } }
注意,购物车具有商品集,商品具有购物车集,这样我们就实现了双向关联。
这意味着我们可以在保存购物车时将其配置为保存商品,反之亦然。
对于单向映射,通常我们设置了一个模型类。
我们将使用注释进行单向映射。
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.theitroad.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>
请注意,项目集已映射到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.theitroad.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>
从上面可以看到,映射与购物车映射配置非常相似。
基于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">hyman123</property> <property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property> <property name="hibernate.connection.username">hyman</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>
Hibernate SessionFactory实用程序类,用于基于XML的映射
HibernateUtil.java
package com.theitroad.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 { //Create the SessionFactory from hibernate.cfg.xml Configuration configuration = new Configuration(); configuration.configure("hibernate.cfg.xml"); System.out.println("Hibernate Configuration loaded"); ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() .applySettings(configuration.getProperties()).build(); System.out.println("Hibernate serviceRegistry created"); SessionFactory sessionFactory = configuration .buildSessionFactory(serviceRegistry); return sessionFactory; } catch (Throwable ex) { System.err.println("Initial SessionFactory creation failed." + ex); ex.printStackTrace(); throw new ExceptionInInitializerError(ex); } } public static SessionFactory getSessionFactory() { if (sessionFactory == null) sessionFactory = buildSessionFactory(); return sessionFactory; } }
这是一个简单的实用工具类,可作为" SessionFactory"的工厂。
Hibernate多对多映射XML配置测试程序
我们Hibernate的多对多映射设置已准备就绪,让我们对其进行测试。
我们将编写两个程序,一个程序是保存Cart,并看到Item和Cart_Items信息也得到保存。
另一个保存项目数据并检查是否保存了相应的Cart和Cart_Items。
HibernateManyToManyMain.java
package com.theitroad.hibernate.main; import java.util.HashSet; import java.util.Set; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import com.theitroad.hibernate.model.Cart; import com.theitroad.hibernate.model.Item; import com.theitroad.hibernate.util.HibernateUtil; public class HibernateManyToManyMain { //Saving many-to-many where Cart is primary 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("Before committing transaction"); tx.commit(); sessionFactory.close(); System.out.println("Cart ID="+cart.getId()); System.out.println("Cart1 ID="+cart1.getId()); System.out.println("Item1 ID="+iphone.getId()); System.out.println("Item2 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 (?) Before committing transaction 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 (?, ?) Cart ID=1 Cart1 ID=2 Item1 ID=1 Item2 ID=2
请注意,一旦通过第一个购物车保存了商品数据,就会生成item_id,并且在保存第二个购物车时不会再次保存。
另一个需要注意的重要点是,在提交事务时,将保存多对多联接表数据。
如果我们选择回滚交易,则可以提高性能。
HibernateBiDirectionalManyToManyMain.java
package com.theitroad.hibernate.main; import java.util.HashSet; import java.util.Set; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import com.theitroad.hibernate.model.Cart; import com.theitroad.hibernate.model.Item; import com.theitroad.hibernate.util.HibernateUtil; public class HibernateBiDirectionalManyToManyMain { //Saving many-to-many where Item is primary 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("Cart ID="+cart.getId()); System.out.println("Cart1 ID="+cart1.getId()); System.out.println("Item1 ID="+iphone.getId()); System.out.println("Item2 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 (?, ?) Cart ID=3 Cart1 ID=4 Item1 ID=3 Item2 ID=4
您可以轻松地将其与早期的测试程序相关联,因为我们已经配置了双向映射,所以我们可以保存Item或者Cart,并且映射的数据将自动保存。
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">hyman123</property> <property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property> <property name="hibernate.connection.username">hyman</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.theitroad.hibernate.model.Cart1" <mapping class="com.theitroad.hibernate.model.Item1" </session-factory> </hibernate-configuration>
Hibernate SessionFactory实用程序类
我们用于创建SessionFactory的实用程序类如下所示。
HibernateAnnotationUtil.java
package com.theitroad.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 { //Create the SessionFactory from hibernate-annotation.cfg.xml Configuration configuration = new Configuration(); configuration.configure("hibernate-annotation.cfg.xml"); System.out.println("Hibernate Annotation Configuration loaded"); ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build(); System.out.println("Hibernate Annotation serviceRegistry created"); SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry); return sessionFactory; } catch (Throwable ex) { System.err.println("Initial SessionFactory creation failed." + 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.theitroad.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 methods }
Item1类看起来很简单,这里没有关系映射。
Cart1.java
package com.theitroad.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 methods }
这里最重要的部分是使用" ManyToMany"注释和" JoinTable"注释,我们其中提供表名和列以用于多对多映射。
Hibernate多对多注释映射测试程序
这是一个简单的测试程序,用于我们的Hibernate多对多基于映射注释的配置。
HibernateManyToManyAnnotationMain.java
package com.theitroad.hibernate.main; import java.util.HashSet; import java.util.Set; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import com.theitroad.hibernate.model.Cart1; import com.theitroad.hibernate.model.Item1; import com.theitroad.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("Cart ID="+cart.getId()); System.out.println("Item1 ID="+item1.getId()); System.out.println("Item2 ID="+item2.getId()); }catch(Exception e){ e.printStackTrace(); }finally{ if(sessionFactory != null && !sessionFactory.isClosed()) sessionFactory.close(); } } }
当我们执行以上程序时,它会产生以下输出。
Hibernate Annotation Configuration loaded Hibernate Annotation 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 (?, ?) Before committing transaction Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?) Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?) Cart ID=5 Item1 ID=6 Item2 ID=5
很明显,保存购物车还将数据保存到Item和Cart_Items表中。
如果仅保存项目信息,则会注意到Cart和Cart_Items数据没有得到保存。