JPA 中使用 @OneToMany 、@ManyToOne 等关系映射注解

JPA 做 ORM(Object Relational Mapping,对象关系映射)时,为了开发效率,通常会在实体类上用 hibernate 的关系关联注解。 包括:@OneToOne@OneToMany@ManyToOne@ManyToMany@JoinTable、以及 @JoinColumn 以及 OrderBy

JPA 中 @JoinColumn 与 关联注解之间用法

@JoinColumn 定义多个字段之间的关联关系,配合@OneToOne@ManyToOne 以及 @OneToMany一起使用

@Repeatable(JoinColumns.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface JoinColumn {
	// 关联字段(子)
    String name() default "";
	// 关键实体字段(父)),非必填
    String referencedColumnName() default "";

    boolean unique() default false;

    boolean nullable() default true;
    // 是否更新 插入操作
    boolean insertable() default true;

    boolean updatable() default true;

    String columnDefinition() default "";

    String table() default "";

    ForeignKey foreignKey() default @ForeignKey(ConstraintMode.PROVIDER_DEFAULT);
}

@OneToMany 注解为例,源代码如下

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OneToMany {
    // 关联对象实体类
    Class targetEntity() default void.class;
	
    // 级联类型,
    // ALL, 级联所有操作
    // PERSIST,级联持久化
    // MERGE, 级联更新合并
    // REMOVE,级联删除
    // REFRESH,  刷新后更新
    //DETACH;  级联脱管操作
    CascadeType[] cascade() default {};
	
    //  获取方式
    // LAZY 懒加载,需要时加载;
    // EARLY 提前加载
    FetchType fetch() default FetchType.LAZY;
	// 被映射实体类名,当关联实体类上注有 ManyToOne 时,该参数必填;
    String mappedBy() default "";
    boolean orphanRemoval() default false;
}

@OneToMany 使用时可以结合 @JoinColumn 一起使用,既可以单向,也可以双向关联;单向指的是只配置一方,双向指两方都配置,实例如下:

假设小组(GroupClass)与学生(Student)关系,一个组可以容纳多个学生,一对多 ;多个学生组件成一个组(多对一);

外键关联,一般子实体类这边维护,因此由Student 来关联 :

@Entity
@Table(name = "student")
public class Student {
    
    // 主键自动生成策略
    @Id

    private Integer id;
    
    private String name;
    
    @Column(name = "student_no",unique = true)
    private String studentNo;
    
    @Column(name = "group_class_id")
    private Integer groupClassId;
	
 	// 关联关系多对一,级联关系,可更新,持久化, 获取方式懒加载;
    @ManyToOne(targetEntity = GroupClass.class,
            cascade = {CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH},
            fetch = FetchType.LAZY)
      // 以 group_id 为外键进行关联,referencedColumnName 默认为被关联实体类主键,可以忽略;
    @JoinColumn(name="group_class_id",referencedColumnName = "id")
    private GroupClass groupClass;
}

而双向关联的 GroupClass 实体类(父类),定义如下:

@Data
@Entity
@Table(name = "group_class")
public class GroupClass {
    @Id
    private Integer id;

    private String name;

    @Column(name="group_no",unique = true)
    private String groupNo;

	// 由于外键关联由 Student 维护,这里不需要在做处理,只需要引用 
    // @OneToMany中的 mappedBy 参数即可;
    @OneToMany(mappedBy = "groupClass")
    private List<Student> students = new ArrayList<>();
}

其它关联注解用法类同:

  • @ManyToMany :多对多;
  • @OneToOne :一对一;

@OrderBy 字段排序

@OrderBy 顾名思义目的是为根据某个字段进行排序,注解一般与 @OneToMany@ManyToMany 同时使用,

@OneToMany(mappedBy = "groupClass")
// 根据名字对 学生对象逆向排序 
@OrderBy("name DESC")
private List<Student> students = new ArrayList<>();

使用实例如下,首先插入1条 GroupClass 和 两条Student 记录,并将两个 Student 关联至 GroupClass

  // 持久化 GroupClass
  GroupClass group = new GroupClass();
  group.setGroupNo("Ano");
  group.setName("名字");
  group.setId(2L);
  //持久化 Student;
  Student student = new Student();
  student.setStudentNo("12");
  student.setId(1);
  student.setName("name1");
  student.setGroupClass(group);
  Student student2 = new Student();
  student2.setStudentNo("122");
  student2.setId(2);
  student2.setGroupClass(group);
  student2.setName("name12");
  // save 保存
  List<Student> students = new ArrayList<>();
  students.add(student);
  students.add(student2);
  group.setStudents(students);
  groupClassRepository.save(group);

根据Id 查询 GroupClass 对象,在输出打印对象的toString 方法钱,需要重新写一下 对象的 toString 函数,防止循环嵌套导致的堆栈溢出问题

   //Student.class 
    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" +name + ", studentNo=" +studentNo + ", groupClassId=" + groupClassId
                + "]";
    }

在上面例子中, GroupClass 对象中 toString 函数引用到 Student,而 StudenttoString 同时引用到 GroupClass,使用时会触发嵌套循环,造成栈溢出问题;

image-20221115090622405

输出 groupClass.toString后,就会发现 groupClass 关联的 students 属性是按照 @OrderBy 注解输出,如下:

 Optional<GroupClass> groupClassOptional = groupClassRepository.findById(2L);
 if(groupClassOptional.isPresent()) {
     GroupClass groupClass = groupClassOptional.get();
     log.info("groupClass str is {}",groupClass.toString());
 }

日志输出如下:

groupClass str is GroupClass(id=2, name=名字, groupNo=Ano, students=[Student [id=2, name=cname, studentNo=122, groupClassId=2], Student [id=1, name=bname1, studentNo=12, groupClassId=2]])

重写实体类 toString 方法,可以解决了父子实体类打印输出时,因相互依赖循环嵌套中堆栈溢出问题;但当实体类通过 SpringBoot 中 @ResponseBody以 Json 格式输出给前端时,会出现序列化堆栈溢出问题

Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError)

该错误解决方法:在父类或子类对应的关联字段上,加上 JsonIgnore 注解,即在 Json序列化 时忽略该属性

// Json 序列化时忽略该属性
@JsonIgnore
@ManyToOne(targetEntity = GroupClass.class,
           cascade = CascadeType.ALL,
           fetch = FetchType.LAZY)
@JoinColumn(name="group_class_id",referencedColumnName = "id", insertable = false, updatable = false)
private GroupClass groupClass;

另一方面,我们在项目配置中打开JPA日志输出调试设置

srping:
  jpa:
    show-sql: true

    hibernate:
      ddl-auto: none

    properties:
      # 将 hibernate 执行语句以 sql 形式打印
      hibernate.format_sql: true
      hibernate.id.new_generator_mappings: false

因此可以从控制台日志输出来查看内部执行的sql逻辑

  select
        groupclass0_.id as id1_5_0_,
        groupclass0_.group_no as group_no2_5_0_,
        groupclass0_.name as name3_5_0_,
        students1_.group_class_id as group_cl2_6_1_,
        students1_.id as id1_6_1_,
        students1_.id as id1_6_2_,
        students1_.group_class_id as group_cl2_6_2_,
        students1_.name as name3_6_2_,
        students1_.student_no as student_4_6_2_ 
    from
        group_class groupclass0_ 
    left outer join
        student students1_ 
            on groupclass0_.id=students1_.group_class_id 
    where
        groupclass0_.id=? 
    order by
        students1_.name desc
  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员大飞1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值