Spring Data JPA Quick Guide
  • Spring Data JPA 快速指南
  • 配置依赖
  • 配置数据源
  • 数据库编码与词语定序
  • Core Concepts
  • 基本用法
  • Transactions
    • 什么是事务
    • 配置事务(@EnableTransactionManagement)
    • 配置事务(XML)
    • 配置事务(@Transactional)
    • @Transactional 实现细节
    • Transaction and Spring Data JPA
    • 事务隔离
    • 事务传播
  • Query Creation from Method Names
  • Using @Query
  • Sorting and Pagination
  • Projection
  • Specification
  • Query by Example
  • javax.persistence Annotations
    • @OneToOne (bidirectional)
      • Usage
      • PO Serialization
      • Save and Update
      • Nested Projection
      • @MapsId
    • @OneToMany (bidirectional)
    • @OneToMany (unidirectional)
    • @ManyToMany (bidirectional)
      • Many-to-Many Using a Composite Key
由 GitBook 提供支持
在本页
  1. javax.persistence Annotations
  2. @OneToOne (bidirectional)

PO Serialization

看起来非常顺利,那我们试着通过 @RestController 将得到的实体传给前端吧:

序列化:就是将对象转化成字节序列的过程。

反序列化:就是讲字节序列转化成对象的过程。

@RestController
@RequiredArgsConstructor
public class studentController {
    private final StudentRepository studentRepository;
    private final TuitionRepository tuitionRepository;

    @GetMapping("/all/student")
    public List<Student> getAllStudent() {
        return studentRepository.findAllBy(Student.class);
    }

    @GetMapping("/all/tuition")
    public List<Tuition> getAllStudent() {
        return tuitionRepository.findAllBy(Tuition.class);
    }
}

不好了,前端请求 API 时后端抛出了 java.lang.StackOverflowError!

这个异常其实是 Jackson 抛出的。由于 Student 里有 Tuition,Tuition 里有 Student,这样无限套娃,Jackson 的序列化就无限地递归处理下去直到崩溃了。

要处理这个问题也好办,有两种方法:

  • 方法一 @JsonBackReference @JsonManagedReference

    在 owning side 使用 @JsonBackReference,在 non-owning side 使用 @JsonManagedReference 注解。

    @Entity
    @Table(name = "student")
    public class Student {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        private String name;
    
        @JsonManagedReference
        @OneToOne(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true, fetch=FetchType.LAZY)
        private Tuition tuition;
    
        /* Getters and setters */   
    }
    @Entity
    @Table(name = "tuition")
    public class Tuition {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        private Double fee;
    
        @JsonBackReference
        @OneToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "student_id")
        private Student student;
    
        /* Getters and setters */    
    }

    序列化结果:

    • /all/student

      [
          {
              "id": 1,
              "name": "张三",
              "tuition": {
                  "id": 2,
                  "fee": 1145.14
              }
          }
      ]

      我们发现,方法一序列化 Student 时,成功将其内部的 tuition 序列化,并且内部 tuition 的 student 域被忽略,消除了循环引用。

    • /all/tuition

      [
          {
              "id": 2,
              "fee": 1145.14
          }
      ]

      很遗憾,tuition 的其他域虽然被成功序列化,其 student 域无法被实例化。

    总结:使用 @JsonBackReference @JsonManagedReference 确实可以解决循环引用的问题,但是序列化只有 non-owning side 会序列化内部的引用,并且随着数据库关系变得复杂,这种解决方法渐渐变得无法维护,思考谁是 owning side 容易把人绕晕。

  • 方法二 @JsonIdentityInfo

    方法二非常简单,只需要无脑地对所有实体加上 @JsonIdentityInfo 注解即可。

    @Entity
    @Table(name = "student")
    @JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class, property = "@id")
    public class Student {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        private String name;
    
        @OneToOne(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
        private Tuition tuition;
    
        /* Getters and setters */   
    }
    @Entity
    @Table(name = "tuition")
    @JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class, property = "@id")
    public class Tuition {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        private Double fee;
    
        @OneToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "student_id")
        private Student student;
    
        /* Getters and setters */    
    }

    序列化结果:

    • /all/student

      [
          {
              "@id": "13389f64-688b-4162-b030-1039133c15f6",
              "id": 1,
              "name": "张三",
              "tuition": {
                  "@id": "9b90d0bb-d267-4111-a262-bfca07e43f13",
                  "id": 2,
                  "fee": 1145.14,
                  "student": "13389f64-688b-4162-b030-1039133c15f6"
              }
          }
      ]
    • /all/tuition

      [
          {
              "@id": "5fc601fc-6eae-489b-a225-fb205f7c2d0c",
              "id": 2,
              "fee": 1145.14,
              "student": {
                  "@id": "adc4d249-de70-4070-ae33-dce1f1384574",
                  "id": 1,
                  "name": "张三",
                  "tuition": "5fc601fc-6eae-489b-a225-fb205f7c2d0c"
              }
          }
      ]

    可以看到,无论是 owning side 还是 non-owning side 序列化,都能完整地展现全部信息。并且,序列化得到的 JSON 中,不仅包含了原来的域,还新包含了 @id 域。每一次序列化,Jackson 用不同的 UUID 标记每个对象,这样就可以在遇到已经序列化的对象时停止序列化,而将那个域的值设置为引用的 UUID。

  • 方法三

    为什么要直接向前端返回 PO 呢?

    我们应该创建合适的 VO,将 PO 在 service 层转化为 VO 再返回给前端,这样不就好了。

上一页Usage下一页Save and Update

最后更新于3年前