多對多映射是在實際數據庫表關係之間比較常見的一種,仍然以電影爲例,一部電影能夠有多個演員,一個演員也能夠參演多部電影,電影表和演員表之間就是「多對多」的關係java
針對多對多的映射關係,Hibernate提供了三種映射實現方式:mysql
1.使用@ManyToMany的單向映射方式;sql
2.使用@ManyToMany的雙向映射方式;數據庫
3.將多對多轉化爲兩個基於中間關係表的一對多的映射方式;session
其中,前兩種方式在數據更新操做上存在效率問題,對數據的更新均是採用先刪除再新增的方式,效率比較低下,但優勢是在代碼上隱去了中間表,開發人員徹底不感知這個表的存在。而第三種方式能夠作到在有數據更新的時候徹底只更新有變動的數據,不須要刪除從新添加,效率比較高。但使用該方式時,必須對中間表顯式的聲明一個數據類,多對多的兩方再也不直接產生關係,而是經過中間表來的,代碼層面略微複雜了一些。app
看代碼,首先定義電影表Movie.java,注意這裏使用的是@OneToMany註解,且集合裏面的元素類型是MovieActor而不是Actor:ide
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的註解,說明這張表的主鍵是這兩列的聯合主鍵:ui
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 }
最後,構建啓動程序,對電影表和演員表數據進行操做this
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#study.hibernate.model.MovieActor@7c4] 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)