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多对多映射中间表有多余字段问题该如何映射的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate多对多关系映射(建表)

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

Hibernate多对多中间表数据更新问题

在 Hibernate 中使用连接表初始化多对多关联

hibernate表映射xml方式多对多怎么配置

hibernate 表关系映射详解之多对多