美文网首页
【Spring JPA总结】JPA One-To-One实现的4

【Spring JPA总结】JPA One-To-One实现的4

作者: 伊丽莎白2015 | 来源:发表于2023-01-01 18:49 被阅读0次

    【参考】
    https://hellokoding.com/one-to-one-mapping-in-jpa-and-hibernate/


    本文介绍Spring Jpa的One-To-One关联的4种方式,包含:
    外键关联下的双向和单向关联
    外键关联的意思是两张表都有自己的主键,一张表的主键作为外键存在于另一张表中:

    外键关联下的双向和单向关联

    共同主键下的双向和单向关联
    共同主键的意思是两张表的主键一样,一张表的主键同时又是另一张表的主键+外键:

    共同主键下的双向和单向关联

    1. 外键关联

    【数据模型】学生,和学生证:一个学生只有一张学生证,学生证也只属于某一个学生。


    image.png
    1.1 使用@OneToOne@JoinColumn来实现双向的外键关联

    双向的One-To-One关联,在两个entity中都需要用到注解@OneToOne

    • Student实体中,@OneToOne是主动的一方,@JoinColumn表示外键的列,name属性是用来标识表中所对应的字段的名称。
    • StudentCard中,则是被关联的一方,@OneToOne需要写mappedBy的值,这个的含义是被映射(即这方不用管关联关系),指向Student实体类。
    @Entity
    @Table(name = "student")
    public class Student {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;
    
        private String name;
    
        @OneToOne(cascade = CascadeType.ALL, optional = false)
        @JoinColumn(name = "student_card_id")
        private StudentCard studentCard;
    }
    
    @Entity
    @Table(name = "student_card")
    public class StudentCard {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;
    
        @Column(unique = true, nullable = false)
        private String code = UUID.randomUUID().toString();
    
        @OneToOne(mappedBy = "studentCard")
        private Student student;
    }
    

    【测试a】主entity(student)中findById,能查到两边的数据,sql:

    select
    student0_.id as id1_3_0_,
    student0_.name as name2_3_0_,
    student0_.student_card_id as student_3_3_0_,
    studentcar1_.id as id1_4_1_,
    studentcar1_.code as code2_4_1_
    from
    student student0_
    inner join
    student_card studentcar1_
    on student0_.student_card_id=studentcar1_.id
    where
    student0_.id=?

    【测试b】子entity(student_card)中findById,能查到两边的数据,sql:

    select
    studentcar0_.id as id1_4_0_,
    studentcar0_.code as code2_4_0_,
    student1_.id as id1_3_1_,
    student1_.name as name2_3_1_,
    student1_.student_card_id as student_3_3_1_
    from
    student_card studentcar0_
    left outer join
    student student1_
    on studentcar0_.id=student1_.student_card_id
    where
    studentcar0_.id=?

    【测试c】在主entity中测试插入,将会插入数据到两张表中:

        @Test
        public void saveTest() {
            Student student = Student.builder().name("test1").studentCard(new StudentCard()).build();
            studentRepository.save(student);
        }
    

    【测试d】在子entity中无法插入主entity的数据,但能单独创建子entity自己的数据,不过因为它的id并没有关联到主entity的表中,也就没有什么意义(像是一个没有学生的学生证)。

    【测试e】在主entity中删除数据,同时会删除关联的子entity数据。

    【测试f】在子entity中尝试删除数据,但因为有外键的关联,无法删除数据。(除非它本身的数据并没有被主entity关联到,是个orphan data):

        @Test
        public void delete() {
            studentCardRepository.deleteById(1);
        }
    
    1.2 使用@OneToOne@JoinColumn来实现单向的外键关联

    数据模型和#1.1一样。

    单向的One-To-One关联,只需要在Student实体中用到注解@OneToOne

    • Student实体中,@OneToOne是主动的一方,@JoinColumn表示外键的列,name属性是用来标识表中所对应的字段的名称。(同双向的关联时配置一样)。
    • StudentCard中,则不需要配置。
    @Entity
    @Table(name = "student")
    public class Student {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;
    
        private String name;
    
        @OneToOne(cascade = CascadeType.ALL, optional = false)
        @JoinColumn(name = "student_card_id")
        private StudentCard studentCard;
    }
    
    @Entity
    @Table(name = "student_card")
    public class StudentCard {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;
    
        @Column(unique = true, nullable = false)
        private String code = UUID.randomUUID().toString();
    }
    

    【测试a】主entity中的findById,查询同上述#1.1一样,会inner join子entity,返回两张表的数据。

    【测试b】而由于是单向关联,子entity中findById 并不会同上述#1.1一样有left outer join,只会返回自己的数据,sql:

    select
    studentcar0_.id as id1_4_0_,
    studentcar0_.code as code2_4_0_
    from
    student_card studentcar0_
    where
    studentcar0_.id=?

    【测试c】主entity插入同上述#1.1一样,执行后可以同时插入两张表的数据。

    【测试d】子entity插入同上述#1.1一样,可单独插入自己的数据(不过并没有意义)。

    【测试e】主entity删除同上述#1.1一样,执行后可以同时删除两张表的数据。

    【测试f】子entity能单独删除自己的数据。


    2. 共同主键

    【数据模型】员工和员工的薪水:一个员工有自己的薪水,每个员工的薪水都不一样,薪水表中的employee_id即为主表employee中的id。
    image.png
    2.1 使用@OneToOne@MapsId来实现双向的共同主键关联

    共同主键表示两个实体共享主键,两个实体类都需要写@OneToOne。在子entity中,主键同时又是外键。

    • 双向的关系,所以需要在主entity也加上@OneToOne。主entity上有mappedBy,因为共同主键关联时,子entity占据了主导地位。
    • 在子entity中,需要再加上@MapsId,表示该列也是主键。而该列同时需要加上@JoinColumn,表示它是外键(即关联到主entity的id)。
    @Entity
    @Table(name = "employee")
    public class Employee {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;
    
        private String name;
    
        @OneToOne(cascade = CascadeType.ALL, mappedBy = "employee")
        private EmployeeSalary employeeSalary;
    }
    
    @Entity
    @Table(name = "employee_salary")
    public class EmployeeSalary {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;
    
        private double salary;
    
        @OneToOne(cascade = CascadeType.ALL, optional = false)
        @JoinColumn(name = "employee_id")
        @MapsId
        private Employee employee;
    }
    

    【测试a】主entity中findById,能查到两边的数据,sql:

    select
    employee0_.id as id1_1_0_,
    employee0_.name as name2_1_0_,
    employeesa1_.employee_id as employee1_2_1_,
    employeesa1_.salary as salary2_2_1_
    from
    employee employee0_
    left outer join
    employee_salary employeesa1_
    on employee0_.id=employeesa1_.employee_id
    where
    employee0_.id=?

    【测试b】子entity中findById,能查到两边的数据,这里的sql会查两遍,第一遍是子entity自己的表,即employee_salary,第二遍是自己的表+left outer join关联到主entity表。这样看来效率比较低下。具体sql:

    select
    employeesa0_.employee_id as employee1_2_0_,
    employeesa0_.salary as salary2_2_0_
    from
    employee_salary employeesa0_
    where
    employeesa0_.employee_id=?

    select
    employee0_.id as id1_1_0_,
    employee0_.name as name2_1_0_,
    employeesa1_.employee_id as employee1_2_1_,
    employeesa1_.salary as salary2_2_1_
    from
    relation_study_employee employee0_
    left outer join
    employee_salary employeesa1_
    on employee0_.id=employeesa1_.employee_id
    where
    employee0_.id=?

    【测试c】在#2.1一开始解释了,虽然employee是主entity,但其实共同主键子entity(即employee_salary)占据了主导地位,所以在主entity中想要插入两张表的数据,需要将employee entity set回salary中才可以,如:

        @Test
        public void save() {
            Employee employee = Employee.builder().name("mark").build();
    
            EmployeeSalary employeeSalary = new EmployeeSalary();
            employeeSalary.setSalary(9000d);
            employee.setEmployeeSalary(employeeSalary);
            employeeSalary.setEmployee(employee);
    
            employeeRepository.save(employee);
        }
    

    【测试d】尝试在employee_salary中插入两张表的数据,成功:

        @Test
        public void saveTest() {
            EmployeeSalary employeeSalary = new EmployeeSalary();
            employeeSalary.setSalary(9000d);
            employeeSalary.setEmployee(Employee.builder().name("mark").build());
    
            employeeSalaryRepository.save(employeeSalary);
        }
    

    【测试e】在主entity中删除数据,同时会删除关联的子entity数据。

    【测试e】在子entity中删除数据,同时会删除关联的主entity数据。

    2.2 使用@OneToOne@PrimaryKeyJoinColumn来实现双向的共同主键

    数据模型和#2.1一样。

    共同主键表示两个实体共享主键,在子entity中,主键同时又是外键:

    • 因为是单向的关联,主entity不需要配置。
    • 子entity需要配置@OneToOne@PrimaryKeyJoinColumn,在@PrimaryKeyJoinColumn中需要指定本表的列(employee_id),以及要关联到另一张表的列名(id)。
    @Entity
    @Table(name = "employee")
    public class Employee {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;
    
        private String name;
    }
    
    @Entity
    @Table(name = "employee_salary")
    public class EmployeeSalary {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int employeeId;
    
        private double salary;
    
        @OneToOne(cascade = CascadeType.ALL, optional = false)
        @PrimaryKeyJoinColumn(name = "employee_id", referencedColumnName = "id")
        private Employee employee;
    }
    

    【测试a】由于是单向关联,主entity(employee)中findById只会返回自己的数据,sql:

    select
    employee0_.id as id1_1_0_,
    employee0_.name as name2_1_0_
    from
    relation_study_employee employee0_
    where
    employee0_.id=?

    【测试b】子entity(employee_salary)中findById,和#2.1一样,会返回两张表数据。唯一不同的是,这次只会查询一遍,即使用left outer join返回所有数据,并不会查两遍,具体sql:

    select
    employeesa0_.employee_id as employee1_2_0_,
    employeesa0_.salary as salary2_2_0_
    from
    relation_study_employee_salary employeesa0_
    where
    employeesa0_.employee_id=?
    Hibernate:
    select
    employee0_.id as id1_1_0_,
    employee0_.name as name2_1_0_

    from
        relation_study_employee employee0_ 
    where
        employee0_.id=?
    

    【测试c】在主entity中尝试插入数据,由于是单向关联,只会插入本表数据。

    【测试d】在子entity中尝试插入数据,需要分别调用各自的repository才能插入两张表的数据:

        @Test
        public void saveTest() {
            Employee employee = Employee.builder().name("mark").build();
            employeeRepository.save(employee);
    
            EmployeeSalary employeeSalary = new EmployeeSalary();
            employeeSalary.setSalary(9000d);
            employeeSalary.setEmployee(employee);
            employeeSalary.setEmployeeId(employee.getId());
    
            employeeSalaryRepository.save(employeeSalary);
        }
    

    【测试e】在主entity中尝试删除数据,只能删除自己表的数据。

    【测试f】在子entity中尝试删除数据,能同时删除两张表数据。

    相关文章

      网友评论

          本文标题:【Spring JPA总结】JPA One-To-One实现的4

          本文链接:https://www.haomeiwen.com/subject/laeqcdtx.html