Spring Boot中Hibernate复合主键@OneToMany关联实现咨询
用户评分系统的Hibernate实现方案
一、需求与初始疑问
我要构建如下数据库结构:
User表
| id | username |
|---|---|
| 1 | Mary |
| 2 | George |
| 3 | Candy |
Movie表
| id | name |
|---|---|
| 1 | Slumdog Millionaire |
| 2 | Avatar |
| 3 | Star Trek |
Rating表
| user_id | movie_id | rate |
|---|---|---|
| 1 | 1 | 6 |
| 2 | 3 | 8 |
| 3 | 2 | 9 |
需求是用户只能为同一部电影评分一次,我打算把Rating表的user_id和movie_id设为复合主键,同时在User-Rating、Movie-Rating之间建立一对多关联,这样设计是否正确?
我缺乏Hibernate经验,常见示例都是单一主键的一对多关联,需要一个复合主键关联的正确实现示例。
二、已尝试的代码(更新内容)
我用@Embeddable实现了部分实体,代码如下:
User实体
@Entity @Table(name = "user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private Set<Rating> ratings = new HashSet<>(); public void addRating(Rating rating) { ratings.add(rating); } public void removeRating(Rating rating) { ratings.remove(rating); } // Getters, setters, constructors and equals methods }
Movie实体
@Entity @Table(name = "movie") public class Movie { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; @OneToMany(mappedBy = "movie", cascade = CascadeType.ALL, orphanRemoval = true) private Set<Rating> ratings = new HashSet<>(); public void addRating(Rating rating) { ratings.add(rating); } public void removeRating(Rating rating) { ratings.remove(rating); } // ... Getters, setters, constructors and equals methods }
Rating实体
@Entity @Table(name = "rating") public class Rating { @EmbeddedId private RatingId ratingId; private int rate; @ManyToOne(fetch = FetchType.LAZY) private User user; @ManyToOne(fetch = FetchType.LAZY) private Movie movie; }
RatingId(嵌入式主键)
@Embeddable public class RatingId implements Serializable { private Long userId; private Long movieId; // ... }
三、修正后的正确实现方案
你的初始设计思路是正确的:复合主键保证“用户-电影”唯一配对,一对多关联符合业务逻辑。但现有代码存在两个关键问题:嵌入式主键与关联字段未绑定,双向关联未维护。以下是修正后的完整代码:
1. 完善RatingId嵌入式主键
必须实现equals()和hashCode(),同时提供无参构造函数(Hibernate要求):
@Embeddable public class RatingId implements Serializable { private Long userId; private Long movieId; // Hibernate必需的无参构造函数 public RatingId() {} // 带参构造函数,方便创建主键实例 public RatingId(Long userId, Long movieId) { this.userId = userId; this.movieId = movieId; } // 主键比较必须实现equals和hashCode @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; RatingId ratingId = (RatingId) o; return Objects.equals(userId, ratingId.userId) && Objects.equals(movieId, ratingId.movieId); } @Override public int hashCode() { return Objects.hash(userId, movieId); } // Getters and Setters }
2. 修正Rating实体的关联映射
使用@MapsId将@ManyToOne关联的ID同步到嵌入式主键的对应字段,避免冗余存储:
@Entity @Table(name = "rating") public class Rating { @EmbeddedId private RatingId ratingId; private int rate; @ManyToOne(fetch = FetchType.LAZY) @MapsId("userId") // 将user的ID映射到RatingId的userId字段 private User user; @ManyToOne(fetch = FetchType.LAZY) @MapsId("movieId") // 将movie的ID映射到RatingId的movieId字段 private Movie movie; // 无参构造函数 public Rating() {} // 带参构造函数,快速创建评分实例 public Rating(User user, Movie movie, int rate) { this.user = user; this.movie = movie; this.rate = rate; this.ratingId = new RatingId(user.getId(), movie.getId()); } // Getters and Setters }
3. 维护双向关联的一致性
在User和Movie的addRating/removeRating方法中设置反向关联,确保对象模型与数据库同步:
User类修改:
public void addRating(Rating rating) { ratings.add(rating); rating.setUser(this); // 绑定评分到当前用户 } public void removeRating(Rating rating) { ratings.remove(rating); rating.setUser(null); // 解除绑定 }
Movie类修改:
public void addRating(Rating rating) { ratings.add(rating); rating.setMovie(this); // 绑定评分到当前电影 } public void removeRating(Rating rating) { ratings.remove(rating); rating.setMovie(null); // 解除绑定 }
关键说明
@MapsId的作用:让Hibernate自动将关联实体的ID赋值到嵌入式主键的对应字段,无需手动维护,避免数据不一致。- 双向关联维护:必须在添加/删除评分时同步设置反向关联,否则会出现Hibernate无法识别的孤立记录,导致持久化失败。
- 复合主键的
equals()/hashCode():Hibernate依赖这两个方法判断主键唯一性,必须正确实现。
内容的提问来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

