hibernate多对多映射中间表有多余字段问题该如何映射
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了hibernate多对多映射中间表有多余字段问题该如何映射相关的知识,希望对你有一定的参考价值。
我有role(角色)表,power(权限)表,role_power表,role和power是多对多关系,role_power为中间表,我现在映射时出现问题了,role-power表中的字段有role_id(FK),power_id(FK)和ishasread普通字段,role-id和power_id是role_power表的联合主键,因为中间表有多余字段,那要怎么映射啊?用不了正常的多对多映射,我考虑映射成两个1对多的关系,但是要是这样的话role_power作为一的一方,它在hbm.xml文件中映射时要指定<id>,主键标签啊,我的表中并没有设计有唯一主键这项,那么这个问题该如何解决啊?类定义如下。
class Role_power calss Role class Power
private Role role; private int role_id; private int power_id;
private Power power; private Set<Role_power> set; private Set<Role-power> set;
private int ishasread; //get,set省略 //get,set省略
//get,set省略
你的中间表对象没有主键,第一种方式就是你为中间表增加一个主键,这个也是如果要把中间表映射成对象的推荐方式。
第二种方式就比较复杂,你需要在中间表对象里面再增加两个属性,int role_id,int power_id,用来映射你中间表中作为复合组件的两个外键,然后在Role_power对象中把role_id和power_id映射成compositeid,然后仍然把Role role和Power power两个属性映射成many2one,然后设置这两个属性对应的many-to-one的column为role_id和power_id,然后把这两个属性的insert="false" update="false",就可以了。这样就比较麻烦
还有一种就是你中间表就不要Role role和Power power,而直接把这两个对象改成int roleId,int powerId来映射。 参考技术A
省省是吧。已myeclipse为例
--》
,直接自动生成吧
参考技术B 将多对多转换成一个多对一和一个一对多。中间表自己控制。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多对多映射中间表有多余字段问题该如何映射的主要内容,如果未能解决你的问题,请参考以下文章