Hibernate学习笔记 --- 创建基于中间关联表的多对多映射关系

Posted smart_妖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hibernate学习笔记 --- 创建基于中间关联表的多对多映射关系相关的知识,希望对你有一定的参考价值。

多对多映射是在实际数据库表关系之间比较常见的一种,仍然以电影为例,一部电影可以有多个演员,一个演员也可以参演多部电影,电影表和演员表之间就是“多对多”的关系

针对多对多的映射关系,Hibernate提供了三种映射实现方式:

  1.使用@ManyToMany的单向映射方式;

  2.使用@ManyToMany的双向映射方式;

  3.将多对多转化为两个基于中间关系表的一对多的映射方式;

其中,前两种方式在数据更新操作上存在效率问题,对数据的更新均是采用先删除再新增的方式,效率比较低下,但优点是在代码上隐去了中间表,开发人员完全不感知这个表的存在。而第三种方式可以做到在有数据更新的时候完全只更新有变更的数据,不需要删除重新添加,效率比较高。但使用该方式时,必须对中间表显式的声明一个数据类,多对多的两方不再直接产生关系,而是通过中间表来的,代码层面略微复杂了一些。

看代码,首先定义电影表Movie.java,注意这里使用的是@OneToMany注解,且集合里面的元素类型是MovieActor而不是Actor:

  1 package study.hibernate.model;
  2 
  3 import java.io.Serializable;
  4 import java.util.ArrayList;
  5 import java.util.List;
  6 import java.util.Objects;
  7 
  8 import javax.persistence.CascadeType;
  9 import javax.persistence.Column;
 10 import javax.persistence.Convert;
 11 import javax.persistence.Entity;
 12 import javax.persistence.Id;
 13 import javax.persistence.OneToMany;
 14 //import javax.persistence.ManyToMany;
 15 import javax.persistence.Table;
 16 
 17 import org.hibernate.annotations.Type;
 18 
 19 /**
 20  * 电影数据类
 21  *  23  *
 24  */
 25 @Entity
 26 @Table(name = "MOVIE")
 27 public class Movie implements Serializable {
 28     @Id
 29     @Column(name = "MOVIE_ID")
 30     private int id;
 31 
 32     @Column(name = "NAME")
 33     @Type(type = "string")
 34     private String name;
 35 
 36     @Column(name = "DESCRIPTION")
 37     @Type(type = "text")
 38     private String description;
 39 
 40     @Column(name = "TYPE")
 41     @Convert(converter = MovieTypeConvertor.class)
 42     private MovieType type;
 43 
 44     @OneToMany(mappedBy = "movie", cascade = CascadeType.ALL, orphanRemoval = true)
 45     private List<MovieActor> movieActors = new ArrayList<MovieActor>();
 46 
 47     public int getId() {
 48         return id;
 49     }
 50 
 51     public void setId(int id) {
 52         this.id = id;
 53     }
 54 
 55     public String getName() {
 56         return name;
 57     }
 58 
 59     public void setName(String name) {
 60         this.name = name;
 61     }
 62 
 63     public String getDescription() {
 64         return description;
 65     }
 66 
 67     public void setDescription(String description) {
 68         this.description = description;
 69     }
 70 
 71     public MovieType getType() {
 72         return type;
 73     }
 74 
 75     public void setType(MovieType type) {
 76         this.type = type;
 77     }
 78 
 79     public List<MovieActor> getMovieActors() {
 80         return movieActors;
 81     }
 82 
 83     public void addActor(Actor actor) {
 84         MovieActor movieActor = new MovieActor();
 85         movieActor.setActor(actor);
 86         movieActor.setMovie(this);
 87         
 88         if (!this.movieActors.contains(movieActor)) {
 89             this.movieActors.add(movieActor);
 90             actor.getMovieActors().add(movieActor);
 91         }
 92     }
 93     
 94     public void removeActor(Actor actor) {
 95         MovieActor movieActor = new MovieActor();
 96         movieActor.setActor(actor);
 97         movieActor.setMovie(this);
 98         
 99         if (this.movieActors.contains(movieActor)) {
100             this.movieActors.remove(movieActor);
101             actor.getMovieActors().remove(movieActor);
102         }
103         
104     }
105 
106     @Override
107     public int hashCode() {
108         return Objects.hash(id);
109     }
110 
111     @Override
112     public boolean equals(Object obj) {
113         if (obj == null) {
114             return false;
115         }
116         
117         if (obj instanceof Movie) {
118             Movie that = (Movie) obj;
119             return that.id == id;
120         }
121         
122         return false;
123     }
124 
125 }

接着,定义演员表Actor.java:

  1 package study.hibernate.model;
  2 
  3 import java.io.Serializable;
  4 import java.util.ArrayList;
  5 import java.util.List;
  6 import java.util.Objects;
  7 
  8 import javax.persistence.CascadeType;
  9 import javax.persistence.Column;
 10 import javax.persistence.Entity;
 11 import javax.persistence.Id;
 12 import javax.persistence.OneToMany;
 13 import javax.persistence.Table;
 14 
 15 import org.hibernate.annotations.Type;
 16 
 17 @Entity
 18 @Table(name="ACTOR")
 19 public class Actor implements Serializable {
 20     @Id
 21     private int id;
 22     
 23     @Column(name="NAME")
 24     @Type(type="string")
 25     private String name;
 26     
 27     @Column(name="BIRTHDAY")
 28     @Type(type="string")
 29     private String birthday;
 30     
 31     @OneToMany(mappedBy = "actor", cascade = CascadeType.ALL, orphanRemoval = true)
 32     private List<MovieActor> movieActors = new ArrayList<MovieActor>();
 33     
 34     public Actor() {
 35         
 36     }
 37     
 38     public Actor(int id, String name, String birthday) {
 39         this.id = id;
 40         this.name = name;
 41         this.birthday = birthday;
 42     }
 43 
 44     public int getId() {
 45         return id;
 46     }
 47 
 48     public void setId(int id) {
 49         this.id = id;
 50     }
 51 
 52     public String getName() {
 53         return name;
 54     }
 55 
 56     public void setName(String name) {
 57         this.name = name;
 58     }
 59 
 60     public String getBirthday() {
 61         return birthday;
 62     }
 63 
 64     public void setBirthday(String birthday) {
 65         this.birthday = birthday;
 66     }
 67 
 68     public List<MovieActor> getMovieActors() {
 69         return this.movieActors;
 70     }
 71 
 72     public void addMovie(Movie movie) {
 73         MovieActor movieActor = new MovieActor();
 74         movieActor.setMovie(movie);
 75         movieActor.setActor(this);
 76         
 77         if (!this.movieActors.contains(movieActor)) {
 78             this.movieActors.add(movieActor);
 79             movie.getMovieActors().add(movieActor);
 80         }
 81     }
 82     
 83     public void removeMovie(Movie movie) {
 84         MovieActor movieActor = new MovieActor();
 85         movieActor.setActor(this);
 86         movieActor.setMovie(movie);
 87         
 88         if (this.movieActors.contains(movieActor)) {
 89             this.movieActors.remove(movieActor);
 90             movie.getMovieActors().remove(movieActor);
 91         }
 92         
 93     }
 94 
 95     @Override
 96     public int hashCode() {
 97         return Objects.hash(id);
 98     }
 99 
100     @Override
101     public boolean equals(Object obj) {
102         if (obj == null) {
103             return false;
104         }
105         
106         if (obj instanceof Actor) {
107             Actor that = (Actor) obj;
108             return that.id == id;
109         }
110         
111         return false;
112     }
113 }

接着,定义电影和演员的关联表MovieActor.java,在这里movie变理及actor变量都添加了@Id的注解,说明这张表的主键是这两列的联合主键:

 1 package study.hibernate.model;
 2 
 3 import java.io.Serializable;
 4 import java.util.Objects;
 5 
 6 import javax.persistence.Entity;
 7 import javax.persistence.Id;
 8 import javax.persistence.ManyToOne;
 9 import javax.persistence.Table;
10 
11 @Entity
12 @Table(name="MOVIEACTOR")
13 public class MovieActor implements Serializable {
14     private static final long serialVersionUID = 1946386806442594700L;
15 
16     @Id
17     @ManyToOne
18     private Movie movie;
19     
20     @Id
21     @ManyToOne
22     private Actor actor;
23 
24     public Movie getMovie() {
25         return movie;
26     }
27 
28     public void setMovie(Movie movie) {
29         this.movie = movie;
30     }
31 
32     public Actor getActor() {
33         return actor;
34     }
35 
36     public void setActor(Actor actor) {
37         this.actor = actor;
38     }
39 
40     @Override
41     public int hashCode() {
42         return Objects.hash(actor, movie);
43     }
44 
45     @Override
46     public boolean equals(Object obj) {
47         if (obj instanceof MovieActor) {
48             MovieActor movieActor = (MovieActor) obj;
49             return Objects.equals(movie, movieActor.getMovie()) && Objects.equals(actor, movieActor.getActor());
50         }
51         
52         return false;
53     }
54     
55 }

最后,构建启动程序,对电影表和演员表数据进行操作

 1 package study.hibernate;
 2 
 3 import org.hibernate.Session;
 4 import org.hibernate.SessionFactory;
 5 import org.hibernate.boot.MetadataSources;
 6 import org.hibernate.boot.registry.StandardServiceRegistry;
 7 import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
 8 
 9 import study.hibernate.model.Actor;
10 import study.hibernate.model.Movie;
11 import study.hibernate.model.MovieType;
12 
13 public class Launcher {
14     public static void main(String[] args) {
15         StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
16                 .configure()
17                 .build();
18         SessionFactory sessionFactory = null;
19         Session session = null;
20         try {
21             sessionFactory = new MetadataSources( registry ).buildMetadata().buildSessionFactory();
22             session = sessionFactory.openSession();
23             
24             Actor actor1 = new Actor(1, "范·迪塞尔", "1967年7月18日");
25             Actor actor2 = new Actor(2, "卢卡斯·布莱克", "1982年11月29日");
26             Actor actor3 = new Actor(3, "杰森·斯坦森", "1967年7月26日");
27             
28             Movie movie = new Movie();
29             movie.setId(4);
30             movie.setName("速度与激情8");
31             movie.setDescription("多米尼克(范·迪塞尔 Vin Diesel 饰)与莱蒂(米歇尔·罗德里格兹 Michelle Rodriguez 饰)共度蜜月,布莱恩与米娅退出了赛车界,这支曾环游世界的顶级飞车家族队伍的生活正渐趋平淡。然而,一位神秘女子Cipher(查理兹·塞隆 Charlize T heron 饰)的出现,令整个队伍卷入信任与背叛的危机,面临前所未有的考验。");
32             movie.setType(MovieType.CARTOON);
33             movie.addActor(actor1);
34             movie.addActor(actor2);
35             movie.addActor(actor3);
36             
37             //保存数据
38             session.beginTransaction();
39             session.save(actor1);
40             session.save(actor2);
41             session.save(actor3);
42             session.save(movie);
43             session.getTransaction().commit();
44 
45             //更新数据
46             session.beginTransaction();
47             actor1.removeMovie(movie);
48             session.update(actor1);
49             session.getTransaction().commit();
50         } catch (Exception e) {
51             e.printStackTrace();
52         } finally {
53             if (session != null) {
54                 session.close();
55             }
56             
57             if(sessionFactory != null) {
58                 sessionFactory.close();
59             }
60         }
61     }
62 }

查看数据库,中间表被创建成功且其内有两条数据:

 1 mysql> show tables;
 2 +--------------------+
 3 | Tables_in_movie_db |
 4 +--------------------+
 5 | actor              |
 6 | movie              |
 7 | movieactor         |
 8 +--------------------+
 9 3 rows in set (0.00 sec)
10 
11 mysql> select * from movieactor;
12 +----------------+----------+
13 | movie_MOVIE_ID | actor_id |
14 +----------------+----------+
15 |              4 |        2 |
16 |              4 |        3 |
17 +----------------+----------+
18 2 rows in set (0.00 sec)

同时,根据Hibernate的日志,确认最后在删除数据时,仅执行了一条语句,而不是将所有数据删除再依次添加

1 Hibernate: insert into ACTOR (BIRTHDAY, NAME, id) values (?, ?, ?)
2 Hibernate: insert into MOVIEACTOR (movie_MOVIE_ID, actor_id) values (?, ?)
3 Hibernate: insert into ACTOR (BIRTHDAY, NAME, id) values (?, ?, ?)
4 Hibernate: insert into MOVIEACTOR (movie_MOVIE_ID, actor_id) values (?, ?)
5 Hibernate: insert into ACTOR (BIRTHDAY, NAME, id) values (?, ?, ?)
6 Hibernate: insert into MOVIEACTOR (movie_MOVIE_ID, actor_id) values (?, ?)
7 Hibernate: insert into MOVIE (DESCRIPTION, NAME, TYPE, MOVIE_ID) values (?, ?, ?, ?)
8 Hibernate: delete from MOVIEACTOR where movie_MOVIE_ID=? and actor_id=?

 

学习过程中遇到的一些问题:

1.在Movie.addActor及removeActor方法中,在级联删除Actor中的数据时,调用的是 actor.getMovieActors().add(movieActor); ,如果改成 actor.addMovie(this); 则在提交数据时会报主键冲突错误:

 1 org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [study.hibernate.model.MovieActor#[email protected]]
 2     at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:169)
 3     at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
 4     at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
 5     at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
 6     at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:97)
 7     at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
 8     at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:660)
 9     at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:652)
10     at org.hibernate.engine.spi.CascadingActions$5.cascade(CascadingActions.java:219)
11     at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
12     at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
13     at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
14     at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:491)
15     at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:423)
16     at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:386)
17     at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
18     at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
19     at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:445)
20     at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:281)
21     at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
22     at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
23     at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
24     at org.hibernate.event.internal.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:38)
25     at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
26     at org.hibernate.event.internal.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:32)
27     at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
28     at org.hibernate.internal.SessionImpl.fireSave(SessionImpl.java:691)
29     at org.hibernate.internal.SessionImpl.save(SessionImpl.java:683)
30     at org.hibernate.internal.SessionImpl.save(SessionImpl.java:678)
31     at study.hibernate.Launcher.main(Launcher.java:42)

原因个人分析,Hibernate监听了Movie的addActor方法,也监听了Acotr的addMovie方法,更新一条数据时,如果两个方法都调用了,会触发Hibernate往中间表中插入两条数据,由于这两条数据的电影ID及演员ID均相同,导致联合主键冲突。

2.对于Movie及Actor的movieActors属性上的@OneToMany注解一定要加上cascade标签,否则Hibernate不会更新中间表;

3.添加了新的数据表映射类MovieActor.java,记得更新Hibernate.cfg.xml,不然运行会报错

 1 org.hibernate.AnnotationException: Use of @OneToMany or @ManyToMany targeting an unmapped class: study.hibernate.model.Movie.movieActors[study.hibernate.model.MovieActor]
 2     at org.hibernate.cfg.annotations.CollectionBinder.bindManyToManySecondPass(CollectionBinder.java:1243)
 3     at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:800)
 4     at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:725)
 5     at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:54)
 6     at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1621)
 7     at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1589)
 8     at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:278)
 9     at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:83)
10     at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:418)
11     at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:87)
12     at org.hibernate.boot.MetadataSources.buildMetadata(MetadataSources.java:179)
13     at study.hibernate.Launcher.main(Launcher.java:21)

 

以上是关于Hibernate学习笔记 --- 创建基于中间关联表的多对多映射关系的主要内容,如果未能解决你的问题,请参考以下文章

[原创]java WEB学习笔记85:Hibernate学习之路-- -映射 一对一关系 ,基于主键方式实现

Hibernate学习笔记

Hibernate学习笔记

JavaEE学习笔记之SSH—Hibernate

框架学习笔记之Hibernate

springboot+kotlin+gradle+hibernate学习笔记