双向多对多的关联关系

双向多对多的关联关系(抽象成A-B)具体体现:A中有B的集合的引用,同时B中也有对A的集合的引用。A、B两个实体对应的数据表靠一张中间表来建立连接关系。

同时我们还知道,双向多对多的关联关系可以拆分成三张表,两个双向多对一关联关系。拆分以后还是有一张中间表,其好处就是可以在中间表中添加某些属性用作其它。这个后面会讲解。而单纯的双向多对多关联关系的中间表有两个外键列,无法增加其它属性。

本节只讲单纯的双向多对多关联关系。从例子讲解配置方法和原理:

有“商品Item”和“类别Category”两个实体类。一中商品可以属于多种类别,同时一种类别可以包含多种商品,这是一个典型的双向多对多关联关系。“双边多对多”的关系体现在Category中有对Item的集合的引用,反过来也是一样的,Item中有对Category的集合的引用。从如下Item和Category属性定义可以很清晰的理解:

List_1. Category中有对Item的集合的引用
@Table(name="t_category")
@Entity
public class Category {

    private Integer id;
    private String name;
      //Category中有对Item的集合的引用
    private Set<Item> itemsSet = new HashSet<Item>();

    //省略getter、setter...
}    
List_2. Item中同样有对Category的集合的引用
@Table(name="t_item")
@Entity
public class Item {

    private Integer id;
    private String name;
      //Item中有对Category的集合的引用
    private Set<Category> categoriesSet = new HashSet<Category>();

    //省略getter、setter...
}

假设Category实体对应的数据表为t_category,Item实体对应的数据表为t_item。中间的连接表为category_item。下面讲讲中间表是如何表达这种多对多的关联关系的,下图是一个关联表:

Figure_1. 多对多关联关系实例

从Figure_1中可以看出,中间表只有两个外键列CATEGORY_ID和ITEM_ID。其中CATEGORY_ID参考t_category的主键列ID_ID,ITEM_ID则参考t_item的外键列ID。从中间表我们很容易看出以下的关联关系:

先看category的对item的关联情况:

①、category(4)中对item的集合itemsSet包含了2个item实体对象:item(1)和item(2)。   为了描述方便item(1)代表id=1的Item实体对象。

②、category(3)中的itemsSet包含了1个item实体对象:item(2)。

再看看item对category的关联情况:

③、item(1)中的categoriesSet包含了1个category实体对象:category(4)。

④、item(2)中的categoriesSet包含了2个category实体对象:category(3)和category(4)。


双向多对多关联关系的映射细节

更多的关于数据库基础的知识可以参考数据库书籍。下面讲讲JPA的实体类中如何配置这种映射关系。映射细节如下:

①、双向多对多关联关系的映射指的就是实体双方的集合属性的映射

  eg. Category实体类中的itemsSet属性,Item实体类中的CategoriesSet属性

②、双向多对多关联关系的实体双方是对称的,可以选择任意一方实体类作为映射主体来完成关键映射过程

  eg. 在Category和Item中我们选择Category作为映射主体类来完成关键的映射过程,主要就是对Category类中的itemsSet属性使用注解完成映射。

③、在非映射主体类中只需要简单的使用@ManyToMany(mappedBy="xxx")来指定由对方的哪个属性完成映射关系(xxx是属性名字)

  eg. 非映射主体类Item中使用@ManyToMany(mappedBy="itemsSet")来指定由对方(Category实体类)的itemsSet属性完成映射关系

从上面的①~③我们知道,映射主要在映射主体类中完成,而非主体类的映射过程十分简单。下面就详细讲解主体类中的映射步骤:

a、对映射主体的集合属性(或其getter方法)使用@ManyToMany注解,表明是多对多关联关系

  eg. Category实体类中的getItemsSet()方法上使用@ManyToMany注解,当然可以设置该注解的fetch等属性来修改默认策略(后面讲解)

b、然后,在集合属性的getter方法上使用@JoinTable注解来映射中间表两个实体类对应数据表外键参考关系,下面讲讲该注解的属性

  • name属性:用于指定中间表数据表的表名(eg. name="category_item"指定中间表的表名为category_item)
  • joinColumns属性:该注解用于指定映射主体类与中间表的映射关系。从javadoc中可以看到该属性的类型是JoinColumn[],也就是说是一个@JoinColumn注解的集合。其中,@JoinColumn注解的name属性用于指定中间表的一个外键列的列名,该外键列参考映射主体类对应数据表的的主键列(如果该主键列的列名不是ID的时候,需要用referencedColumnName属性指定主键列的列名。如Category中的主键为“ID_ID”);
  • inverseJoinColumns属性:该注解用于指定对方(非映射主体类)实体类与中间表的映射关系。它也是一个JoinColumn[]类型。该属性的用法与joinColumns是一致的。

下面用一个映射主体类的实例说明上面的过程,Category作为映射主体,其有一个Item实体的集合的引用itemsSet属性。我们在其getter方法上完成映射:

List_3. 映射主体类Category的映射过程
@JoinTable(name="category_item",
  joinColumns={@JoinColumn(name="CATEGORY_ID", referencedColumnName="ID_ID")},
  inverseJoinColumns={@JoinColumn(name="ITEM_ID", referencedColumnName="ID")})
@ManyToMany
public Set<Item> getItemsSet() {
  return itemsSet;
}

①、注解@ManyToMany指示多对多的关联关系(因为一对多也是一个集合,所以要用注解来进行区分)

②、@JoinTable指示中间表如何映射,该注解有三个属性:name、joinColumns、inverseJoinColumns。

  • name属性指定了中间表的表名为category_item;
  • joinColumns用于映射本实体类(Category)对应数据表与中间表如何进行映射。@JoinColumn的name属性指定了中间表的一个外键列,且列名为CATEGORY_ID,该外键类参考本实体类对应数据表的主键列(主键列的列名由@JoinColumn的referencedColumName属性进行指定,这里指定为“ID_ID”。后面会说到本实体类的数据表的外键列的列名为ID_ID);
  • inverseJoinColumns属性用于映射对方实体类(Item)数据表与中间表的映射关系。其配置方法与joinColumns相同。

③、在对方实体类中的映射很简单,使用@ManyToMany(mappedBy="itemsSet")来指定由映射主体类的itemsSet属性(或其getter方法)完成映射过程。也就是上面@JoinTable的inverseJoinColumns属性完成。非映射主体类Item一方的映射细节如List_4:

List_4. 非映射主体类的映射细节
@ManyToMany(mappedBy="itemsSet")
public Set<Category> getCategoriesSet() {
  return categoriesSet;
}

下面用图解的形式将映射主体的配置项与创建好的数据表进行对应起来,如Figure_2:

Figure_2. 下图中紫色代表映射主体相关,蓝色代表非映射主体相关


双向多对多关联关系的默认行为

默认检索策略和修改:

“双向多对多”中的“多”体现在实体双方实体类中都有一个集合属性,用前面讲解的结论得到“默认情况下,对集合属性的检索采用延迟加载”。所以,默认情况下,双向多对多关联关系中对集合的检索也采用延迟加载。可以通过设置@ManyToMany(fetch=FetchType.EAGER)将检索策略修改为立即加载策略(一般情况下不建议这么做)。

注意区分“解除关联关系”和“删除实体对象”这两个概念和不同的处理方法:

①、解除关联关系,其实际效果是删除中间表中的某条记录,而实体类对应数据表中的记录不会被删除。具体做法是调用集合属性的remove方法,如下:

List_5. 解除关联关系
Category ctg = em.find(Category.class, 3);

Item item = ctg.getItemsSet().iterator().next();
/**
 * 解除关联关系调用集合对象的remove方法
 * 集合的remove方法会删除的是关联关系,也就是删除中间表的某条记录
 * 但是,它不会删除实体类对应数据表中的记录
 */
ctg.getItemsSet().remove(item);

②、删除实体对象,这个和前面说的删除操作没有区别,同样是调用EntityManager的remove方法。但是,要注意的是由于中间表会对实体类对象的记录有引用关系,所以,在删除实体类记录之前先要解除所有和该记录相关的关联关系。否则,无法完成删除操作(除非,修改删除操作的默认行为)。

双向多对多关联关系相关的知识讲解完毕。下面列出实验代码:

List_6. Category实体类的定义及映射(主键列的列名为ID_ID)
 package com.magicode.jpa.doubl.many2many;

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

 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;

 @Table(name="t_category")
 @Entity
 public class Category {

     private Integer id;
     private String name;

     private Set<Item> itemsSet = new HashSet<Item>();

     /**
      * 专门将主键列的列名设置为 ID_ID
      */
     @Column(name="ID_ID")
     @GeneratedValue(strategy=GenerationType.AUTO)
     @Id
     public Integer getId() {
         return id;
     }

     /**
      * 1、多对多关联关系需要建立一个中间表,所以要用@JoinTable注解来设置中间表的映射关系。
      * 注解@JoinTable的几点说明:
      *  ①、name属性指定了中间表的表名;
      *  ②、joinColumns属性映射当前实体类中的“多”(集合)在中间表的映射关系,该属性是JoinColumn[]类型。所以,
      *     要用@JoinColumn注解的集合为其进行赋值。同时,@JoinColumn注解中name指定中间表
      *     的外键列的列名,referencedColumnName指定该外键列参照当前实体类对应数据表的那个列的列名。
      *     下面注解的意思是:中间表的外键列CATEGORY_ID引用当前实体类所对应数据表的ID_ID列(通常是主键列)。
      *  ③、inverseJoinColumns属性用于映射对方实体类中的“多”在中间表的映射关系。作用和joinColumns
      *     一致。
      *     注解的意思是:中间表的外键列ITEM_ID引用Item实体类对应数据表的ID列(ID是列名)
      *
      * 2、使用@ManyToMany映射多对多的关联关系。
      */
     @JoinTable(name="category_item",
             joinColumns={@JoinColumn(name="CATEGORY_ID", referencedColumnName="ID_ID")},
             inverseJoinColumns={@JoinColumn(name="ITEM_ID", referencedColumnName="ID")})
     @ManyToMany
     public Set<Item> getItemsSet() {
         return itemsSet;
     }

     @Column(name="NAME")
     public String getName() {
         return name;
     }

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

     public void setName(String name) {
         this.name = name;
     }

     public void setItemsSet(Set<Item> itemsSet) {
         this.itemsSet = itemsSet;
     }

 }
List_7. Item实体中关联关系的映射
 package com.magicode.jpa.doubl.many2many;

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

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

 @Table(name="t_item")
 @Entity
 public class Item {

     private Integer id;
     private String name;

     private Set<Category> categoriesSet = new HashSet<Category>();

     @Column(name="ID")
     @GeneratedValue(strategy=GenerationType.AUTO)
     @Id
     public Integer getId() {
         return id;
     }

     @Column(name="NAME", length=25)
     public String getName() {
         return name;
     }

     /**
      * 使用@ManyToMany映射双向关联关系。作为非映射主体一方,只需要简单的
      * 配置该注解的mappedBy="xxx"即可。xxx是对方实体(映射主体)中集合
      * 属性的名称表示由对方主体的哪个属性来完成映射关系。
      */
     @ManyToMany(mappedBy="itemsSet")
     public Set<Category> getCategoriesSet() {
         return categoriesSet;
     }

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

     public void setName(String name) {
         this.name = name;
     }

     public void setCategoriesSet(Set<Category> categoriesSet) {
         this.categoriesSet = categoriesSet;
     }

 }
List_8. 测试方法
 package com.magicode.jpa.doubl.many2many;

 import javax.persistence.EntityManager;
 import javax.persistence.EntityManagerFactory;
 import javax.persistence.EntityTransaction;
 import javax.persistence.Persistence;

 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;

 public class DoubleMany2ManyTest {

     private EntityManagerFactory emf = null;
     private EntityManager em = null;
     private EntityTransaction transaction = null;

     @Before
     public void before(){
         emf = Persistence.createEntityManagerFactory("jpa-1");
         em = emf.createEntityManager();
         transaction = em.getTransaction();
         transaction.begin();
     }

     @After
     public void after(){
         transaction.commit();
         em.close();
         emf.close();
     }

     @Test
     public void testPersist(){
         Category ctg1 = new Category();
         ctg1.setName("ctg-1");

         Category ctg2 = new Category();
         ctg2.setName("ctg-2");

         Item item1 = new Item();
         item1.setName("item-1");

         Item item2 = new Item();
         item2.setName("item-2");

         //建立关联关系
         ctg1.getItemsSet().add(item1);
         ctg1.getItemsSet().add(item2);
         ctg2.getItemsSet().add(item1);
         ctg2.getItemsSet().add(item2);

         item1.getCategoriesSet().add(ctg1);
         item1.getCategoriesSet().add(ctg2);
         item2.getCategoriesSet().add(ctg1);
         item2.getCategoriesSet().add(ctg2);

         //持久化操作
         em.persist(item1);
         em.persist(item2);
         em.persist(ctg1);
         em.persist(ctg2);
     }

     @Test
     public void testFind(){
         Category ctg = em.find(Category.class, 3);
         System.out.println(ctg.getItemsSet().size());

 //        Item item = em.find(Item.class, 1);
 //        System.out.println(item.getCategoriesSet().size());
     }

     @Test
     public void testRemove(){
         Category ctg = em.find(Category.class, 3);

         Item item = ctg.getItemsSet().iterator().next();
         /**
          * 集合的remove方法会删除的是关联关系,也就是删除中间表的某条记录
          */
         ctg.getItemsSet().remove(item);

         /**
          * 要删除Category或者是Item实体对应数据表的某条记录要用em.remove方法
          * 当然,要删除的记录不能被中间表引用,否则会删除失败
          */

     }
 }

10、JPA_映射双向多对多的关联关系的更多相关文章

  1. 6、JPA_映射单向多对一的关联关系(n的一方有1的引用,1的一方没有n的集合属性)

    单向多对一的关联关系 具体体现:n的一方有1的引用,1的一方没有n的集合属性 举个例子:订单Order对顾客Customer是一个单向多对一的关联关系.Order是n的一方,有对Customer的引用 ...

  2. 9、JPA_映射双向一对一的关联关系

    双向一对一的关联关系 举例说明:经理Manager和部门Department是双向一对一关联关系.则Manager实体类中有Department实体对象的引用,反之亦然. 其实体属性定义如下: Lis ...

  3. JPA 映射单向多对一的关联关系

    1.首先在多的一端加入一的一端的实体类 //映射单向n-1的关联关 //使用@ManyToOne 来映射多对一的关系 //使用@JoinColumn 来映射外键/可以使用@ManyToOne的fetc ...

  4. 8、双向一对多的关联关系(等同于双向多对一。1的一方有对n的一方的集合的引用,同时n的一方有对1的一方的引用)

    双向一对多关联关系 “双向一对多关联关系”等同于“双向多对一关联关系”:1的一方有对n的一方的集合的引用,同时n的一方有对1的一方的引用. 还是用客户Customer和订单Order来解释: “一对多 ...

  5. Hibernate框架之双向多对多关系映射

    昨天跟大家分享了Hibernate中单向的一对多.单向多对一.双向一对多的映射关系,今天跟大家分享下在Hibernate中双向的多对多的映射关系 这次我们以项目和员工举个栗子,因为大家可以想象得到,在 ...

  6. Hibernate框架双向多对多关联映射关系

    建立双向多对多关联关系    Project.java (项目表)                private Integer proid;                private Strin ...

  7. 017 多对多关联映射 双向(many-to-many)

    多对多关联映射 双向 两方都持有对象引用,修改对象模型,但数据的存储没有变化. 再修改映射文件: public class Role { private int id; private String ...

  8. hibernate(四) 双向多对多映射关系

    序言 莫名长了几颗痘,真TM疼,可能是现在运动太少了,天天对着电脑,决定了,今天下午花两小时去跑步了, 现在继上一章节的一对多的映射关系讲解后,今天来讲讲多对多的映射关系把,明白了一对多,多对多个人感 ...

  9. JPA学习笔记(8)——映射双向一对多关联关系

    双向一对多关联关系 前面的博客讲的都是单向的,而本问讲的是双向的(双向一对多 = 双向多对一) 什么是双向? 我们来对照一下单向和双向 单向/双向 User实体类中是否有List< Order& ...

随机推荐

  1. 服务端跨域处理 Cors

    1  添加 System.Web.Cors,System.Web.Http.Cors 2 global文件中 注册asp.net 管道事件 protected void Application_Beg ...

  2. Loadrunner11安装和破解方法

    公司很多项目都在做性能测试,打算把性能测试学习下.(不懂还可以问问公司大神,这么好的机会不要错过了O(∩_∩)O哈哈~)用了二周实践看了性能测试方面一些基本术语和概念,一直都还没自己动手实践,光看基本 ...

  3. Java实验2-数据库编程

    目标:掌握Java数据库编程 内容: 学生选课系统包括如下数据库表 学生表:Student(Sno,Sname,Ssex,Sage,Sdept) 课程表:Course(Cno,Cname,Ccredi ...

  4. Linux 下安装mysql 链接库

    1.mysql 客户端 开发 链接库 1.1)CentOS yum install mysql-devel

  5. JBoss和Tomcat版本、及Servlet、JSP规范版本对应一览 【转】

    原文地址:http://blog.csdn.net/hills/article/details/40896357 JBoss和Tomcat版本.及Servlet.JSP规范版本对应一览 JBossAS ...

  6. 程序设计模式 —— State 状态模式

    我应该如何阅读? 本文将使用优雅的文字风格来告诉你什么是状态模式. 注意: 1.在阅读本文之前请保证你已经掌控了 面对对象的思想与 多态的基本概念,否则将难以理解. 2.本文实现将用C++实现,你不一 ...

  7. SNMP ber 编码

    5.1 标识域(tag)的编码规则 标识域指明数据的类型,占用1个字节,常见的类型有:BOOL(0x01);INT(0x02);OCTSTR(0x04);NULL(0x05);OBJID(0x06); ...

  8. Redhat6.x下如何制作虚拟机快照和镜像封装

    一.虚拟机快照 1.确认你的物理机上的vg还有足够的剩余空间 [root@hacker ~]# vgs  VG        #PV #LV #SN Attr   VSize  VFree   vg_ ...

  9. 了解GDAL的图像处理/Python

    GDAL是一个操作各种栅格地理数据格式的库.包括读取.写入.转换.处理各种栅格数据格式(有些特定的格式对一些操作如写入等不支持).它使用了一个单一的抽象数据模型就支持了大多数的栅格数据(GIS对栅格, ...

  10. C语言 宏/macor/#define/

    C语言 宏/macor/#define 高级技巧 1.在进行调试的时候,需要进行打印/PRINT,可以通过define进行自定义.例如,自己最常用的DEBUG_PRINT() #define DEBU ...