创建两个存在一对多关系的实体:
name = "tb_customer")
(
public class Customer {
strategy = GenerationType.IDENTITY)
( private Long id;
name = "last_name", length = 10, nullable = false)
( private String lastName;
private Integer age;
private LocalDate birth;
private LocalDateTime createTime;
}
name = "tb_order")
(
public class Order {
strategy = GenerationType.IDENTITY)
( private Long id;
name = "order_name", length = 20, nullable = false)
( private String orderName;
name = "customer_id")
(
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 {
// ...
name = "customer_id")
( 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());
多对多关系查询时默认使用懒加载策略。
本文的完整示例可以从获取。
文章评论