1. 程式人生 > >Hibernate學習筆記(五) --- 創建基於中間關聯表的多對多映射關系

Hibernate學習筆記(五) --- 創建基於中間關聯表的多對多映射關系

mys 兩個 override pac tid 一對多 main ber different

多對多映射是在實際數據庫表關系之間比較常見的一種,仍然以電影為例,一部電影可以有多個演員,一個演員也可以參演多部電影,電影表和演員表之間就是“多對多”的關系

針對多對多的映射關系,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學習筆記(五) --- 創建基於中間關聯表的多對多映射關系