Spring Boot + JPA 多对多关系最佳实践

Spring Boot + JPA 多对多关系最佳实践

  1. 基础模型设计

推荐方式:使用中间实体(最灵活可控)

@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;
    
    // 其他关联属性...
}

优点:
• 完全控制关联表

• 可以添加额外属性

• 查询更灵活

• 适合复杂的业务场景

  1. 简单场景:使用 @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 避免重复元素

  1. 性能优化建议

延迟加载配置

@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);
}
  1. 关联操作最佳实践

维护关联关系

@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

• 删除操作通常不应该级联

  1. 数据库设计建议

索引配置

@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;
  1. 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();
    }
}

总结选择

  1. 简单关联:使用 @ManyToMany + @JoinTable
  2. 需要额外属性:使用显式中间实体
  3. 高性能需求:考虑单独Repository操作中间表
  4. 复杂业务逻辑:推荐中间实体+自定义方法

最重要原则:根据业务需求选择最简单的可行方案,避免过早优化。

posted @ 2025-04-23 20:27  许仙儿  阅读(64)  评论(0)    收藏  举报