红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

JPA 学习笔记 3:映射关联关系

2025年10月5日 12点热度 0人点赞 0条评论

单向多对一的关联关系

创建两个存在一对多关系的实体:

@Entity
@Table(name = "tb_customer")
@Data
@ToString
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "last_name", length = 10, nullable = false)
    private String lastName;
    private Integer age;
    private LocalDate birth;
    private LocalDateTime createTime;
}
@Entity
@Table(name = "tb_order")
@Data
@NoArgsConstructor
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "order_name", length = 20, nullable = false)
    private String orderName;
    @JoinColumn(name = "customer_id")
    @ManyToOne
    private Customer customer;
​
    public Order(String orderName, Customer customer) {
        this.orderName = orderName;
        this.customer = customer;
    }
}

这里的关键是在从表tb_order对应的实体Order中的customer字段上完成一对多的关系映射。使用两个注解:

  • @ManyToOne:表明这个属性体现了多对一关系(tb_order表是从表,是多的一方)

  • @JoinColumn:表明多对一关系具体由表字段customer_id关联(外键)

程序执行后 JPA 自动创建的tb_order 表结构:

-- auto-generated definition
create table tb_order
(
    id          bigint auto_increment
    primary key,
    order_name  varchar(20) not null,
    customer_id bigint      null,
    constraint FKqcp43jdylvf2riad5s1x1i2dn
    foreign key (customer_id) references tb_customer (id)
);

新增

示例:

Customer customer = new Customer();
customer.setLastName("张三");
customer.setAge(18);
customer.setBirth(LocalDate.of(1990, 1, 1));
customer.setCreateTime(LocalDateTime.now());
Order order1 = new Order("订单1", customer);
Order order2 = new Order("订单2", customer);
entityManager.persist(customer);
entityManager.persist(order1);
entityManager.persist(order2);

示例创建了一个顾客和与之关联的两个订单,并使用persist方法保存到数据库。可以观察到 JPA 使用三条 INSERT 语句依次添加数据。

需要注意的是,这里先保存主表对应的实体Customer,再保存从表对应的实体Order。如果顺序相反:

entityManager.persist(order1);
entityManager.persist(order2);
entityManager.persist(customer);

就会发现日志中 JPA 执行的 SQL 是先执行 3 条 INSERT,其中关联字段customer_id使用 null 占位,然后再执行两条 UPDATE 对关联字段更新为正确的值。

因此,为了优化执行效率,处理一对多关系的添加操作时,应当先添加主表对应的实体,再添加从表对应的实体。

查询

Order order = entityManager.find(Order.class, 10L);
System.out.println(order.getCustomer());

默认情况下 JPA 通过左连接进行查询:

   select
        o1_0.id,
        c1_0.id,
        c1_0.age,
        c1_0.birth,
        c1_0.createTime,
        c1_0.last_name,
        o1_0.order_name 
    from
        tb_order o1_0 
    left join
        tb_customer c1_0 
            on c1_0.id=o1_0.customer_id 
    where
        o1_0.id=?

可以通过@ManyToOne注解改为懒加载:

public class Order {
    // ...
    @JoinColumn(name = "customer_id")
    @ManyToOne(fetch = FetchType.LAZY)
    private Customer customer;
    // ...
}

此时 JPA 会先用一条 SELECT 查询订单信息,只有订单关联的顾客信息被读取的时候才会执行 SELECT 查询顾客信息。

修改

Order order = entityManager.find(Order.class, 10L);
order.getCustomer().setLastName("李四");
entityManager.merge(order);

删除

可以随意删除从表:

Order order = entityManager.find(Order.class, 10L);
entityManager.remove(order);

如果要删除主表,就需要主表没有关联的数据,如果有就会报错:

Order order = entityManager.find(Order.class, 9L);
// 会因为外键约束报错
entityManager.remove(order.getCustomer());

单向一对多的关联关系

一对多的关系也可以在表示主表的实体上添加:

// ...
public class Child {
    // ...
    @JoinColumn(name = "child_id")
    @OneToMany
    private List<Toy> toys = new ArrayList<>();
	// ...
}

新增

Child child = new Child("小王");
Toy toy1 = new Toy("玩具1");
Toy toy2 = new Toy("玩具2");
child.getToys().add(toy1);
child.getToys().add(toy2);
entityManager.persist(child);
entityManager.persist(toy1);
entityManager.persist(toy2);

需要主要的是,此时 JPA 会执行5条SQL,3条 INSERT 语句和 2条 UPDATE 语句,无论这里是否先添加主表实体。

查询

Child child = entityManager.find(Child.class, 1L);
System.out.println(child.getToys());

与之前不同,这里默认使用懒加载的方式,先查询主表内容,如果从表数据(getToys)被读取,才会用额外的 SELECT 语句获取从表数据并返回。

同样可以在注解中修改这一行为:

public class Child {
    // ...
    @JoinColumn(name = "child_id")
    @OneToMany(fetch = FetchType.EAGER)
    private List<Toy> toys = new ArrayList<>();
	// ...
}

此时会使用一条 SELECT 通过左外连接的方式一次查询主表和从表数据返回。

修改

Child child = entityManager.find(Child.class, 1L);
child.getToys().getFirst().setName("奥特曼");

删除

Child child = entityManager.find(Child.class, 1L);
entityManager.remove(child);

默认情况下对主表实体的删除,会将主表行数据删除的同时将主表相应行数据的关联字段设置为null。

可以修改级联删除策略:

public class Child {
	// ...
    @JoinColumn(name = "child_id")
    @OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.REMOVE})
    private List<Toy> toys = new ArrayList<>();
	// ...
}

此时删除主表实体会将主表数据行和关联的从表数据行一起删除。

双向一对多关联关系

创建两个实体类:

@Table(name = "tb_person")
@Entity
@Data
@NoArgsConstructor
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(mappedBy = "owner")
    private List<Car> cars = new ArrayList<>();

    public Person(String name) {
        this.name = name;
    }
}
@Table(name = "tb_car")
@Entity
@Data
@NoArgsConstructor
public class Car {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String branch;
    @JoinColumn(name = "person_id")
    @ManyToOne
    private Person owner;

    public Car(String branch) {
        this.branch = branch;
    }
}

关联关系由表示从表的Car的owner属性持有:

@JoinColumn(name = "person_id")
@ManyToOne
private Person owner;

主表的cars属性使用@OneToMany(mappedBy = "owner")标识,表示映射到从表实体Car的owner属性定义的关联关系上。

测试:

Person person = new Person("张三");
Car car1 = new Car("ford");
Car car2 = new Car("bmw");
person.getCars().add(car1);
person.getCars().add(car2);
car1.setOwner(person);
car2.setOwner(person);
entityManager.persist(person);
entityManager.persist(car1);
entityManager.persist(car2);

除了这种方式外,也可以不使用@OneToMany(mappedBy = "owner")映射关系,转而在两侧分别定义一对多和多对一的关联关系,但那样会导致保存时产生额外的 UPDATE 语句,效率较差,不推荐。

双向一对一关联关系

实体类:

@Entity
@Data
@Table(name = "tb_department")
@NoArgsConstructor
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToOne(mappedBy = "department")
    private Manager manager;

    public Department(String name) {
        this.name = name;
    }
}
@Table(name = "tb_manager")
@Entity
@Data
@NoArgsConstructor
public class Manager {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @JoinColumn(name = "department_id", unique = true)
    @OneToOne
    private Department department;

    public Manager(String name) {
        this.name = name;
    }
}

这里经理和部门是一对一的关系,且关联关系在Manager实体中体现,Department实体中使用关系映射。

新增

Manager manager = new Manager("张三");
Department department = new Department("开发部");
manager.setDepartment(department);
department.setManager(manager);
entityManager.persist(department);
entityManager.persist(manager);

这里先保存不持有关系的一方(Department),这样做的好处是不会产生额外的 UPDATE 语句。

查询

Manager manager = entityManager.find(Manager.class, 1L);
System.out.println(manager.getDepartment());

查询持有关系的一方时,默认会使用左外链接进行查询:

  select
        m1_0.id,
        d1_0.id,
        d1_0.name,
        m1_0.name 
    from
        tb_manager m1_0 
    left join
        tb_department d1_0 
            on d1_0.id=m1_0.department_id 
    where
        m1_0.id=?

可以修改为懒加载:

public class Manager {
    // ...
    @JoinColumn(name = "department_id", unique = true)
    @OneToOne(fetch = FetchType.LAZY)
    @ToString.Exclude
    private Department department;
	// ...
}

此时会先使用一条 SELECT 查询出经理信息,如果需要查询部门信息,才会再使用一条 SELECT 查询。

如果查询的是不持有关系的一方(这个例子中就是部门),这样做是行不通的,因为没有关联关系字段就意味着无法知道是否存在关联的数据,因此无法实现懒加载。

双向多对多关系

这里创建两个实体,课程和学生,它们是多对多关系:

@Table(name = "tb_student")
@Entity
@Data
@NoArgsConstructor
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ManyToMany(mappedBy = "students")
    private List<Course> courses = new ArrayList<>();

    public Student(String name) {
        this.name = name;
    }
}
@Table(name = "tb_course")
@Entity
@Data
@NoArgsConstructor
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @JoinTable(name = "tb_course_student",
            joinColumns = @JoinColumn(name = "course_id"),
            inverseJoinColumns = @JoinColumn(name = "student_id"))
    @ManyToMany
    private List<Student> students = new ArrayList<>();

    public Course(String name) {
        this.name = name;
    }
}

这里与双向一对多关系类似,也是由一方维护关联关系,另一方使用关系了映射。不同的是这里使用的是@JoinTable维护关联关系,因为多对多关系要使用中间表,通过@JoinTable注解可以指定中间表名称以及关联的外键字段。

新增

Course course1 = new Course("JPA");
Course course2 = new Course("Hibernate");
Student student1 = new Student("张三");
Student student2 = new Student("李四");
student1.getCourses().add(course1);
student1.getCourses().add(course2);
course1.getStudents().add(student1);
course2.getStudents().add(student1);
student2.getCourses().add(course1);
course1.getStudents().add(student2);
entityManager.persist(student1);
entityManager.persist(student2);
entityManager.persist(course1);
entityManager.persist(course2);

查询

Student student = entityManager.find(Student.class, 1L);
System.out.println("----------------");
System.out.println(student.getCourses());

多对多关系查询时默认使用懒加载策略。

本文的完整示例可以从这里获取。

参考资料

  • 尚硅谷jpa开发教程全套完整版

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: jpa
最后更新:2025年10月6日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号