10、JPA_映射双向多对多的关联关系

摘要:
同时我们还知道,双向多对多的关联关系可以拆分成三张表,两个双向多对一关联关系。而单纯的双向多对多关联关系的中间表有两个外键列,无法增加其它属性。一中商品可以属于多种类别,同时一种类别可以包含多种商品,这是一个典型的双向多对多关联关系。双向多对多关联关系的映射细节更多的关于数据库基础的知识可以参考数据库书籍。

双向多对多的关联关系

双向多对多的关联关系(抽象成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 classCategory {
    
    privateInteger id;
    privateString name;
    
//Category中有对Item的集合的引用
private Set<Item> itemsSet = new HashSet<Item>(); //省略getter、setter... }
List_2. Item中同样有对Category的集合的引用
@Table(name="t_item")
@Entity
public classItem {
    
    privateInteger id;
    privateString name;
    
//Item中有对Category的集合的引用
private Set<Category> categoriesSet = new HashSet<Category>(); //省略getter、setter... }

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

Figure_1. 多对多关联关系实例

10、JPA_映射双向多对多的关联关系第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() {
returnitemsSet;
}

①、注解@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() {
returncategoriesSet;
}

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

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

10、JPA_映射双向多对多的关联关系第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)
1 packagecom.magicode.jpa.doubl.many2many;
2 
3 importjava.util.HashSet;
4 importjava.util.Set;
5 
6 importjavax.persistence.Column;
7 importjavax.persistence.Entity;
8 importjavax.persistence.GeneratedValue;
9 importjavax.persistence.GenerationType;
10 importjavax.persistence.Id;
11 importjavax.persistence.JoinColumn;
12 importjavax.persistence.JoinTable;
13 importjavax.persistence.ManyToMany;
14 importjavax.persistence.Table;
15 
16 @Table(name="t_category")
17 @Entity
18 public classCategory {
19     
20     privateInteger id;
21     privateString name;
22     
23     private Set<Item> itemsSet = new HashSet<Item>();
24 
25     /**
26 * 专门将主键列的列名设置为 ID_ID 
27      */
28     @Column(name="ID_ID")
29     @GeneratedValue(strategy=GenerationType.AUTO)
30 @Id
31     publicInteger getId() {
32         returnid;
33 }
34 
35     /**
36 * 1、多对多关联关系需要建立一个中间表,所以要用@JoinTable注解来设置中间表的映射关系。
37 * 注解@JoinTable的几点说明:
38 *  ①、name属性指定了中间表的表名;
39 *  ②、joinColumns属性映射当前实体类中的“多”(集合)在中间表的映射关系,该属性是JoinColumn[]类型。所以,
40 *     要用@JoinColumn注解的集合为其进行赋值。同时,@JoinColumn注解中name指定中间表
41 *     的外键列的列名,referencedColumnName指定该外键列参照当前实体类对应数据表的那个列的列名。
42 *     下面注解的意思是:中间表的外键列CATEGORY_ID引用当前实体类所对应数据表的ID_ID列(通常是主键列)。
43 *  ③、inverseJoinColumns属性用于映射对方实体类中的“多”在中间表的映射关系。作用和joinColumns
44 *     一致。
45 *     注解的意思是:中间表的外键列ITEM_ID引用Item实体类对应数据表的ID列(ID是列名)
46 *     
47 * 2、使用@ManyToMany映射多对多的关联关系。
48      */
49     @JoinTable(name="category_item",
50             joinColumns={@JoinColumn(name="CATEGORY_ID", referencedColumnName="ID_ID")},
51             inverseJoinColumns={@JoinColumn(name="ITEM_ID", referencedColumnName="ID")})
52 @ManyToMany
53     public Set<Item>getItemsSet() {
54         returnitemsSet;
55 }
56     
57     @Column(name="NAME")
58     publicString getName() {
59         returnname;
60 }
61 
62     public voidsetId(Integer id) {
63         this.id =id;
64 }
65 
66     public voidsetName(String name) {
67         this.name =name;
68 }
69 
70     public void setItemsSet(Set<Item>itemsSet) {
71         this.itemsSet =itemsSet;
72 }
73 
74 }
List_7. Item实体中关联关系的映射
1 packagecom.magicode.jpa.doubl.many2many;
2 
3 importjava.util.HashSet;
4 importjava.util.Set;
5 
6 importjavax.persistence.Column;
7 importjavax.persistence.Entity;
8 importjavax.persistence.GeneratedValue;
9 importjavax.persistence.GenerationType;
10 importjavax.persistence.Id;
11 importjavax.persistence.ManyToMany;
12 importjavax.persistence.Table;
13 
14 @Table(name="t_item")
15 @Entity
16 public classItem {
17     
18     privateInteger id;
19     privateString name;
20     
21     private Set<Category> categoriesSet = new HashSet<Category>();
22 
23     @Column(name="ID")
24     @GeneratedValue(strategy=GenerationType.AUTO)
25 @Id
26     publicInteger getId() {
27         returnid;
28 }
29 
30     @Column(name="NAME", length=25)
31     publicString getName() {
32         returnname;
33 }
34 
35     /**
36 * 使用@ManyToMany映射双向关联关系。作为非映射主体一方,只需要简单的
37 * 配置该注解的mappedBy="xxx"即可。xxx是对方实体(映射主体)中集合
38 * 属性的名称表示由对方主体的哪个属性来完成映射关系39      */
40     @ManyToMany(mappedBy="itemsSet")
41     public Set<Category>getCategoriesSet() {
42         returncategoriesSet;
43 }
44 
45     public voidsetId(Integer id) {
46         this.id =id;
47 }
48 
49     public voidsetName(String name) {
50         this.name =name;
51 }
52 
53     public void setCategoriesSet(Set<Category>categoriesSet) {
54         this.categoriesSet =categoriesSet;
55 }
56     
57 }
List_8. 测试方法
1 packagecom.magicode.jpa.doubl.many2many;
2 
3 importjavax.persistence.EntityManager;
4 importjavax.persistence.EntityManagerFactory;
5 importjavax.persistence.EntityTransaction;
6 importjavax.persistence.Persistence;
7 
8 importorg.junit.After;
9 importorg.junit.Before;
10 importorg.junit.Test;
11 
12 public classDoubleMany2ManyTest {
13 
14     private EntityManagerFactory emf = null;
15     private EntityManager em = null;
16     private EntityTransaction transaction = null;
17     
18 @Before
19     public voidbefore(){
20         emf = Persistence.createEntityManagerFactory("jpa-1");
21         em =emf.createEntityManager();
22         transaction =em.getTransaction();
23 transaction.begin();
24 }
25     
26 @After
27     public voidafter(){
28 transaction.commit();
29 em.close();
30 emf.close();
31 }
32     
33 @Test
34     public voidtestPersist(){
35         Category ctg1 = newCategory();
36         ctg1.setName("ctg-1");
37         
38         Category ctg2 = newCategory();
39         ctg2.setName("ctg-2");
40         
41         Item item1 = newItem();
42         item1.setName("item-1");
43         
44         Item item2 = newItem();
45         item2.setName("item-2");
46         
47         //建立关联关系
48 ctg1.getItemsSet().add(item1);
49 ctg1.getItemsSet().add(item2);
50 ctg2.getItemsSet().add(item1);
51 ctg2.getItemsSet().add(item2);
52         
53 item1.getCategoriesSet().add(ctg1);
54 item1.getCategoriesSet().add(ctg2);
55 item2.getCategoriesSet().add(ctg1);
56 item2.getCategoriesSet().add(ctg2);
57         
58         //持久化操作
59 em.persist(item1);
60 em.persist(item2);
61 em.persist(ctg1);
62 em.persist(ctg2);
63 }
64     
65 @Test
66     public voidtestFind(){
67         Category ctg = em.find(Category.class, 3);
68 System.out.println(ctg.getItemsSet().size());
69         
70 //Item item = em.find(Item.class, 1);
71 //System.out.println(item.getCategoriesSet().size());
72 }
73     
74 @Test
75     public voidtestRemove(){
76         Category ctg = em.find(Category.class, 3);
77         
78         Item item =ctg.getItemsSet().iterator().next();
79         /**
80 * 集合的remove方法会删除的是关联关系,也就是删除中间表的某条记录
81          */
82 ctg.getItemsSet().remove(item);
83         
84         /**
85 * 要删除Category或者是Item实体对应数据表的某条记录要用em.remove方法
86 * 当然,要删除的记录不能被中间表引用,否则会删除失败
87          */
88         
89 }
90 }

免责声明:文章转载自《10、JPA_映射双向多对多的关联关系》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇mysql增删改和学生管理sql编译安装squid3.1亲测下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

powerdesigner设置主键为自增字段,设置非主键为唯一键并作为表的外键

转自:https://www.cnblogs.com/CoffeeHome/archive/2014/06/04/3767501.html 这里powerdesigner连接的数据库是以mysql为例子,连接其他数据库时操作也基本类似 1、设置主键为自增字段 双击要设置的表,选择“Columns”标签,双击主键字段,在弹出的新窗口的General标签最下方...

定时导出Oracle数据表到文本文件的方法

该实例实现了通过windows定时任务来实现了将数据库中指定数据表数据导出为txt文本格式。其思路是通过可执行的bat文件去调用导出数据脚本,然后再在windows定时任务中调用该bat文件来实现。该示例需要能够运行的sqlplus环境,因此需要安装Oracle客户端等可运行环境。     实现了将数据库中日志表数据导出到指定文件夹下的.log文件,且该文...

Mybatis

JDBCJDBC相关概念 JAVA程序都是通过JDBC连接数据库的,通过SQL对数据库编程,JDBC是由SUN公司提出的一些列规范,只定义了接口规范,具体实现由各个数据库厂商去实现,它是一种典型的桥接模式。 桥接模式是一种结构型设计模式,它的主要特点是把抽象与行为实现分离开来,分别定义接口,可以保持各部分的独立性以及应对他们的功能扩展。 JDBC规范...

springJPA 之 QueryDSL(一)

引言不可否认的是 JPA 使用是非常方便的,极简化的配置,只需要使用注解,无需任何 xml 的配置文件,语义简单易懂,但是,以上的一切都建立在单表查询的前提下的,我们可以使用 JPA 默认提供的方法,简单加轻松的完成 CRUD 操作。但是如果涉及到多表动态查询, JPA 的功能就显得有些捉襟见肘了,虽然我们可以使用注解 @Query ,在这个注解中写 SQ...

在Entity Framework中使用存储过程(一):实现存储过程的自动映射

之前给自己放了一个比较长的假期,在这期间基本上没怎么来园子逛。很多朋友的留言也没有一一回复,在这里先向大家道个歉。最近一段时间的工作任务是如何将ADO.NET Entity Framework 4.0(以下简称EF)引入到我们的开发框架,进行相应的封装、扩展,使之成为一个符合在特定场景下进行企业级快速开发的ORM。在此过程中遇到了一些挑战,也有一些心得。为...

MySQL(一) 数据表数据库的基本操作

      序言         这类文章,记录我看《MySQL5.6从零开始学》这本书的过程,将自己觉得重要的东西记录一下,并有可能帮助到你们,在写的博文前几篇度会非常基础,只要动手敲,跟着我写的例子全部实现一遍,基本上就搞定了,前期很难理解的东西基本没有,所以写博文的内容,就是以练题的形式来呈现的。             需要用的资料以链接的形式给需...