Spring Boot + JPA 多对多关系最佳实践
Spring Boot + JPA 多对多关系最佳实践
- 基础模型设计
推荐方式:使用中间实体(最灵活可控)
@Entity
public class Student {
@Id @GeneratedValue
private Long id;
// 其他字段...
@OneToMany(mappedBy = "student")
private Set<CourseRegistration> registrations = new HashSet<>();
}
@Entity
public class Course {
@Id @GeneratedValue
private Long id;
// 其他字段...
@OneToMany(mappedBy = "course")
private Set<CourseRegistration> registrations = new HashSet<>();
}
@Entity
public class CourseRegistration {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "student_id")
private Student student;
@ManyToOne
@JoinColumn(name = "course_id")
private Course course;
@Column
private LocalDateTime registeredAt;
@Column
private Integer grade;
// 其他关联属性...
}
优点:
• 完全控制关联表
• 可以添加额外属性
• 查询更灵活
• 适合复杂的业务场景
- 简单场景:使用
@ManyToMany
@Entity
public class Book {
@Id @GeneratedValue
private Long id;
@ManyToMany
@JoinTable(
name = "book_author",
joinColumns = @JoinColumn(name = "book_id"),
inverseJoinColumns = @JoinColumn(name = "author_id")
)
private Set<Author> authors = new HashSet<>();
}
@Entity
public class Author {
@Id @GeneratedValue
private Long id;
@ManyToMany(mappedBy = "authors")
private Set<Book> books = new HashSet<>();
}
最佳实践:
• 总是明确指定 @JoinTable 名称和列名
• 在一侧使用 mappedBy 避免重复映射
• 使用 Set 而非 List 避免重复元素
- 性能优化建议
延迟加载配置
@ManyToMany(fetch = FetchType.LAZY) // 默认就是LAZY,但建议显式声明
private Set<Author> authors = new HashSet<>();
批量获取优化
@Entity
public class Author {
// ...
@BatchSize(size = 50)
@ManyToMany(mappedBy = "authors")
private Set<Book> books = new HashSet<>();
}
查询优化
public interface BookRepository extends JpaRepository<Book, Long> {
@EntityGraph(attributePaths = {"authors"})
Optional<Book> findWithAuthorsById(Long id);
}
- 关联操作最佳实践
维护关联关系
@Entity
public class Book {
// ...
public void addAuthor(Author author) {
this.authors.add(author);
author.getBooks().add(this);
}
public void removeAuthor(Author author) {
this.authors.remove(author);
author.getBooks().remove(this);
}
}
级联操作(谨慎使用)
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<Author> authors = new HashSet<>();
注意:
• 避免使用 CascadeType.ALL
• 删除操作通常不应该级联
- 数据库设计建议
索引配置
@Entity
@Table(indexes = {
@Index(name = "idx_book_author_book", columnList = "book_id"),
@Index(name = "idx_book_author_author", columnList = "author_id")
})
public class BookAuthor {
// ...
}
外键约束
@ManyToOne
@JoinColumn(
name = "student_id",
foreignKey = @ForeignKey(name = "fk_registration_student")
)
private Student student;
- DTO 和序列化处理
避免循环引用
public class BookDTO {
private Long id;
private String title;
private Set<AuthorDTO> authors;
@JsonIgnoreProperties("books")
public static class AuthorDTO {
private Long id;
private String name;
}
}
使用投影查询
public interface BookProjection {
String getTitle();
Set<AuthorSummary> getAuthors();
interface AuthorSummary {
String getName();
}
}
总结选择
- 简单关联:使用
@ManyToMany+@JoinTable - 需要额外属性:使用显式中间实体
- 高性能需求:考虑单独Repository操作中间表
- 复杂业务逻辑:推荐中间实体+自定义方法
最重要原则:根据业务需求选择最简单的可行方案,避免过早优化。
浙公网安备 33010602011771号