You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot多对多关联实体加载异常:查询时作者信息丢失

多对多关联查询时作者信息随机不显示问题

我定义了BookEntity与AuthorEntity两个实体,二者为多对多关联关系。通过Postman创建记录时结果符合预期,但调用getBooks或getBookById查询时,会随机出现作者信息不显示的异常。

实体类代码

@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "books")
public class BookEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(
            name = "books_authors",
            joinColumns = {@JoinColumn(name = "book_id")},
            inverseJoinColumns = {@JoinColumn(name = "author_id")}
    )
    private Set<AuthorEntity> authors;
}
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "authors")
public class AuthorEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private Integer age;

    private Boolean dead;

    @ManyToMany(mappedBy = "authors", fetch = FetchType.EAGER)
    @JsonBackReference
    private Set<BookEntity> books;
}

控制器与服务层代码

Book控制器代码

@PostMapping
public ResponseEntity<BookDTO> createBook(@RequestBody BookDTO bookDTO){
    BookEntity bookEntity;
    Set<AuthorEntity> authors = utilities.authorInsideBook(bookDTO);
    if(authors == null){
        return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
    }
    bookEntity = bookMapper.mapFrom(bookDTO);
    bookEntity.setAuthors(authors);
    bookEntity = bookServiceImpl.save(bookEntity);
    return new ResponseEntity<>(bookMapper.mapTo(bookEntity), HttpStatus.OK);
}

@GetMapping
public List<BookDTO> getBooks(){
    List<BookEntity> books = bookServiceImpl.getBooks();
    return books.stream().map(bookMapper::mapTo).toList();
}

@GetMapping(path = "/{id}")
public ResponseEntity<BookDTO> getBookById(@PathVariable(value = "id") Long id){
    Optional<BookEntity> bookEntity = bookServiceImpl.getBookById(id);
    return bookEntity.map(book -> {
        BookDTO bookDTO = bookMapper.mapTo(book);
        return new ResponseEntity<>(bookDTO, HttpStatus.FOUND);
    }).orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}

@DeleteMapping(path = "/{id}")
public ResponseEntity<HttpStatus> deleteBooksById(@PathVariable(value = "id") Long id){
    if(!bookServiceImpl.isExists(id)){
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
    bookServiceImpl.delete(id);
    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}

Book服务层代码

@Override
public BookEntity save(BookEntity bookEntity) {
    return bookRepository.save(bookEntity);
}

@Override
public List<BookEntity> getBooks() {
    return StreamSupport.stream(
                    bookRepository.findAll()
                            .spliterator(),
                    false)
            .toList();
}

@Override
public Optional<BookEntity> getBookById(Long id) {
    return bookRepository.findById(id);
}

@Override
public boolean isExists(Long id) {
        return bookRepository.existsById(id);
}

@Override
public void delete(Long id) {
    bookRepository.deleteById(id);
}

问题原因与解决方案

核心原因

  1. 双向多对多均设置FetchType.EAGER:Hibernate在双向EAGER加载多对多关系时,易出现加载顺序冲突或循环加载问题,导致关联集合随机无法初始化。
  2. 集合序列化与加载上下文问题:Set集合的无序性加上EAGER加载的序列化上下文波动,可能导致JSON序列化时随机忽略关联数据。

解决方案

1. 调整FetchType并主动关联查询

将实体类中的FetchType.EAGER改为FetchType.LAZY,同时在Repository中通过JOIN FETCH主动加载关联数据,避免延迟加载导致的未初始化问题:

  • 修改BookEntity的多对多注解:
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(
        name = "books_authors",
        joinColumns = {@JoinColumn(name = "book_id")},
        inverseJoinColumns = {@JoinColumn(name = "author_id")}
)
private Set<AuthorEntity> authors;
  • 修改AuthorEntity的多对多注解:
@ManyToMany(mappedBy = "authors", fetch = FetchType.LAZY)
@JsonBackReference
private Set<BookEntity> books;
  • 在BookRepository中添加带关联查询的方法:
@Query("SELECT b FROM BookEntity b JOIN FETCH b.authors")
List<BookEntity> findAllWithAuthors();

@Query("SELECT b FROM BookEntity b JOIN FETCH b.authors WHERE b.id = :id")
Optional<BookEntity> findByIdWithAuthors(Long id);
  • 服务层调用新的Repository方法:
@Override
public List<BookEntity> getBooks() {
    return bookRepository.findAllWithAuthors();
}

@Override
public Optional<BookEntity> getBookById(Long id) {
    return bookRepository.findByIdWithAuthors(id);
}
2. 优化JSON序列化配置

在BookEntity的authors字段上添加@JsonManagedReference,与AuthorEntity的@JsonBackReference配合,明确序列化方向,避免循环序列化问题:

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(
        name = "books_authors",
        joinColumns = {@JoinColumn(name = "book_id")},
        inverseJoinColumns = {@JoinColumn(name = "author_id")}
)
@JsonManagedReference
private Set<AuthorEntity> authors;
3. 检查Mapper映射逻辑

确认bookMapper.mapTo方法正确处理authors集合的映射,确保即使集合为空也能正常序列化该字段,而非直接忽略。

内容的提问来源于stack exchange,提问作者maziogra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 14:15:54