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

Spring Boot中Author与Book双向关联避免循环引用方案咨询

解决双向关联序列化循环引用+双向展示关联信息的三种方案

方案一:用DTO分层(推荐,最灵活可控)

实体类只负责映射数据库,不处理JSON序列化逻辑,完全用DTO来控制返回的字段:

  • 定义精简的子DTO:
    • AuthorSimpleDTO:只包含id、name字段
    • BookSimpleDTO:只包含id、title字段
  • 定义主DTO:
    • BookDTO:包含书籍本身的所有字段,加上AuthorSimpleDTO author
    • AuthorDTO:包含作者本身的所有字段,加上List<BookSimpleDTO> books
  • 服务层查询后,把实体转换为对应的DTO(可以用MapStruct简化转换)

代码示例:

// Author实体(无JSON注解)
@Entity
public class Author {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    @OneToMany(mappedBy = "author")
    private List<Book> books;
    // getter/setter
}

// Book实体(无JSON注解)
@Entity
public class Book {
    @Id
    @GeneratedValue
    private Long id;
    private String title;
    @ManyToOne
    private Author author;
    // getter/setter
}

// AuthorSimpleDTO
public class AuthorSimpleDTO {
    private Long id;
    private String name;
    // getter/setter
}

// BookSimpleDTO
public class BookSimpleDTO {
    private Long id;
    private String title;
    // getter/setter
}

// BookDTO
public class BookDTO {
    private Long id;
    private String title;
    private AuthorSimpleDTO author;
    // getter/setter
}

// AuthorDTO
public class AuthorDTO {
    private Long id;
    private String name;
    private List<BookSimpleDTO> books;
    // getter/setter
}

// 服务层转换示例
@Service
public class BookService {
    public List<BookDTO> getAllBooks() {
        List<Book> books = bookRepository.findAll();
        return books.stream().map(book -> {
            BookDTO dto = new BookDTO();
            dto.setId(book.getId());
            dto.setTitle(book.getTitle());
            AuthorSimpleDTO authorDto = new AuthorSimpleDTO();
            authorDto.setId(book.getAuthor().getId());
            authorDto.setName(book.getAuthor().getName());
            dto.setAuthor(authorDto);
            return dto;
        }).collect(Collectors.toList());
    }
}

优点:完全解耦实体和返回格式,想返回什么字段就定义什么,彻底避免循环引用,适合复杂业务场景。

方案二:用@JsonIdentityInfo注解(最简单快捷)

给两个实体类加上@JsonIdentityInfo,让Jackson识别对象的唯一ID,遇到重复引用时只输出ID,不会递归序列化:

@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Author {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    @OneToMany(mappedBy = "author")
    private List<Book> books;
    // getter/setter
}

@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Book {
    @Id
    @GeneratedValue
    private Long id;
    private String title;
    @ManyToOne
    private Author author;
    // getter/setter
}

效果:查询书籍时,书籍里的作者会输出完整信息,作者里的书籍列表只会输出每本书的id;查询作者时,作者里的书籍会输出完整信息,书籍里的作者只会输出id。既满足双向展示,又不会循环溢出。
优点:代码改动最小,不用额外写DTO,适合简单场景。

方案三:用@JsonView实现不同场景的序列化

定义两个视图接口,分别对应查询书籍和查询作者的场景,然后在实体的字段上标注对应的视图:

  1. 定义视图接口:
public class Views {
    public interface BookWithAuthor {}
    public interface AuthorWithBooks {}
}
  1. 实体类上标注视图:
@Entity
public class Author {
    @Id
    @GeneratedValue
    private Long id;
    
    @JsonView({Views.BookWithAuthor.class, Views.AuthorWithBooks.class})
    private String name;
    
    @JsonView(Views.AuthorWithBooks.class)
    @OneToMany(mappedBy = "author")
    private List<Book> books;
    // getter/setter
}

@Entity
public class Book {
    @Id
    @GeneratedValue
    private Long id;
    
    @JsonView({Views.BookWithAuthor.class, Views.AuthorWithBooks.class})
    private String title;
    
    @JsonView(Views.BookWithAuthor.class)
    @ManyToOne
    private Author author;
    // getter/setter
}
  1. 控制器上指定视图:
@RestController
public class BookController {
    @GetMapping("/books")
    @JsonView(Views.BookWithAuthor.class)
    public List<Book> getAllBooks() {
        return bookRepository.findAll();
    }
}

@RestController
public class AuthorController {
    @GetMapping("/authors")
    @JsonView(Views.AuthorWithBooks.class)
    public List<Author> getAllAuthors() {
        return authorRepository.findAll();
    }
}

效果:查询/books时,返回的书籍会包含完整作者信息,但作者里不会带出书籍列表;查询/authors时,返回的作者会包含书籍列表,但书籍里不会带出作者信息。刚好满足需求,同时避免循环。
优点:不用写DTO,通过视图控制序列化字段,适合需要多场景返回不同格式的情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 14:02:43