Stackoverflow有一道题JoinColumn vs mappedBy很有意思:
@Entity
public class Company {
@OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
@JoinColumn(name = "companyIdRef", referencedColumnName = "companyId")
private List
...
}
@Entity
public class Company {
@OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, mappedBy = "companyIdRef")
private List
...
}
上面两种级联有什么区别?
原题下面有两个高分答案。可惜对于我这种学渣,看完还是一头雾水。
看不懂别人写的,那就自己调调看。
准备环境
磨刀不误砍柴工,先准备调试环境。
我用的是Spring Boot 2.0 + Spring Data JPA 2.0, Hibernate版本是5.2.14。
首先在Spring Boot的application.yml里开启Hibernate的调试选项。
spring:
jpa:
properties:
hibernate:
show_sql: true # 打印SQL语句
format_sql: true # 格式化SQL语句
use_sql_comments: true # 增加注释信息,就知道语句对应的Entity类型了
generate_statistics: true # 统计信息,给出了每一步的耗时信息
还要在log配置开启org.hibernate.type
的debug
级别的日志信息。我用的是log4j2,就需要在log4j2.yml文件中添加:
Configuration:
Loggers
Logger:
- name: org.hibernate.type
additivity: false
level: trace # 这个最关键
AppenderRef:
- ref: CONSOLE
- ref: ROLLING_FILE
第一次测试
原题里使用的是“公司-部门”模型,我这里本地已经有现成的“部门-雇员”模型,就直接复用了。道理是一样的。
@Entity
@Table(name="department_demo")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "departmentId")
private Set<Employee> employeeSet = new HashSet<>();
// setters & getters
}
@Entity
@Table(name="employee_demo")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private Integer departmentId;
// setters & getters
}
在这里,Department使用主键id
一对多的关联于Employee的departmentId
属性。
然后就是保存一个对象看看。
@Service
public class DepartmentService {
@Autowired
DepartmentRepository departmentRepository;
@PostConstruct
public void insertNewRecord() {
Department department = new Department();
department.setName("Leader");
departmentRepository.save(department);
Employee emily = new Employee();
emily.setName("David");
Employee alice = new Employee();
alice.setName("Wang Dali");
department.addEmployee(emily);
department.addEmployee(alice);
departmentRepository.save(department);
}
}
启动之后发现log里是这样打印的。
2018-04-22 09:11:22,155:INFO restartedMain (StatisticalLoggingSessionEventListener.java:258) - Session Metrics {
0 nanoseconds spent acquiring 0 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
0 nanoseconds spent preparing 0 JDBC statements;
0 nanoseconds spent executing 0 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
Hibernate:
/* insert com.example.demo.model.PO.Department
*/ insert
into
department_demo
(name)
values
(?)
2018-04-22 09:11:22,314:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [VARCHAR] - [Leader]
Hibernate:
select
currval('department_demo_id_seq')
2018-04-22 09:11:22,438:INFO restartedMain (StatisticalLoggingSessionEventListener.java:258) - Session Metrics {
733681 nanoseconds spent acquiring 1 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
6711770 nanoseconds spent preparing 2 JDBC statements;
75097150 nanoseconds spent executing 2 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
14646304 nanoseconds spent executing 1 flushes (flushing a total of 1 entities and 1 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
Hibernate:
/* load com.example.demo.model.PO.Department */ select
department0_.id as id1_1_1_,
department0_.name as name2_1_1_,
employeese1_.department_id as departme2_2_3_,
employeese1_.id as id1_2_3_,
employeese1_.id as id1_2_0_,
employeese1_.department_id as departme2_2_0_,
employeese1_.name as name3_2_0_
from
department_demo department0_
left outer join
employee_demo employeese1_
on department0_.id=employeese1_.department_id
where
department0_.id=?
2018-04-22 09:11:22,460:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [33]
2018-04-22 09:11:22,499:TRACE restartedMain (BasicExtractor.java:51) - extracted value ([id1_2_0_] : [INTEGER]) - [null]
2018-04-22 09:11:22,502:TRACE restartedMain (BasicExtractor.java:61) - extracted value ([name2_1_1_] : [VARCHAR]) - [Leader]
2018-04-22 09:11:22,503:TRACE restartedMain (BasicExtractor.java:51) - extracted value ([departme2_2_3_] : [INTEGER]) - [null]
Hibernate:
/* insert com.example.demo.model.PO.Employee
*/ insert
into
employee_demo
(department_id, name)
values
(?, ?)
2018-04-22 09:11:22,516:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [33]
2018-04-22 09:11:22,516:TRACE restartedMain (BasicBinder.java:65) - binding parameter [2] as [VARCHAR] - [David]
Hibernate:
select
currval('employee_demo_id_seq')
Hibernate:
/* insert com.example.demo.model.PO.Employee
*/ insert
into
employee_demo
(department_id, name)
values
(?, ?)
2018-04-22 09:11:22,526:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [33]
2018-04-22 09:11:22,527:TRACE restartedMain (BasicBinder.java:65) - binding parameter [2] as [VARCHAR] - [Wang Dali]
Hibernate:
select
currval('employee_demo_id_seq')
第二次测试
第二次将Department的级联注解做了修改。
@Entity
@Table(name="department_demo")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@JoinColumn(name="departmentId", referencedColumnName = "id")
private Set<Employee> employeeSet = new HashSet<>();
打印的log是这样子。
2018-04-22 09:16:29,366:INFO restartedMain (StatisticalLoggingSessionEventListener.java:258) - Session Metrics {
0 nanoseconds spent acquiring 0 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
0 nanoseconds spent preparing 0 JDBC statements;
0 nanoseconds spent executing 0 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
Hibernate:
/* insert com.example.demo.model.PO.Department
*/ insert
into
department_demo
(name)
values
(?)
2018-04-22 09:16:29,478:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [VARCHAR] - [Leader]
Hibernate:
select
currval('department_demo_id_seq')
2018-04-22 09:16:29,513:INFO restartedMain (StatisticalLoggingSessionEventListener.java:258) - Session Metrics {
743154 nanoseconds spent acquiring 1 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
4202462 nanoseconds spent preparing 2 JDBC statements;
9628594 nanoseconds spent executing 2 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
11708469 nanoseconds spent executing 1 flushes (flushing a total of 1 entities and 1 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
Hibernate:
/* load com.example.demo.model.PO.Department */ select
department0_.id as id1_1_1_,
department0_.name as name2_1_1_,
employeese1_.department_id as departme2_2_3_,
employeese1_.id as id1_2_3_,
employeese1_.id as id1_2_0_,
employeese1_.department_id as departme2_2_0_,
employeese1_.name as name3_2_0_
from
department_demo department0_
left outer join
employee_demo employeese1_
on department0_.id=employeese1_.department_id
where
department0_.id=?
2018-04-22 09:16:29,529:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [34]
2018-04-22 09:16:29,538:TRACE restartedMain (BasicExtractor.java:51) - extracted value ([id1_2_0_] : [INTEGER]) - [null]
2018-04-22 09:16:29,541:TRACE restartedMain (BasicExtractor.java:61) - extracted value ([name2_1_1_] : [VARCHAR]) - [Leader]
2018-04-22 09:16:29,542:TRACE restartedMain (BasicExtractor.java:51) - extracted value ([departme2_2_3_] : [INTEGER]) - [null]
Hibernate:
/* insert com.example.demo.model.PO.Employee
*/ insert
into
employee_demo
(department_id, name)
values
(?, ?)
2018-04-22 09:16:29,552:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [34]
2018-04-22 09:16:29,552:TRACE restartedMain (BasicBinder.java:65) - binding parameter [2] as [VARCHAR] - [Wang Dali]
Hibernate:
select
currval('employee_demo_id_seq')
Hibernate:
/* insert com.example.demo.model.PO.Employee
*/ insert
into
employee_demo
(department_id, name)
values
(?, ?)
2018-04-22 09:16:29,555:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [34]
2018-04-22 09:16:29,556:TRACE restartedMain (BasicBinder.java:65) - binding parameter [2] as [VARCHAR] - [David]
Hibernate:
select
currval('employee_demo_id_seq')
Hibernate:
/* create one-to-many row com.example.demo.model.PO.Department.employeeSet */ update
employee_demo
set
department_id=?
where
id=?
2018-04-22 09:16:29,576:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [34]
2018-04-22 09:16:29,577:TRACE restartedMain (BasicBinder.java:65) - binding parameter [2] as [INTEGER] - [30]
Hibernate:
/* create one-to-many row com.example.demo.model.PO.Department.employeeSet */ update
employee_demo
set
department_id=?
where
id=?
2018-04-22 09:16:29,595:TRACE restartedMain (BasicBinder.java:65) - binding parameter [1] as [INTEGER] - [34]
2018-04-22 09:16:29,596:TRACE restartedMain (BasicBinder.java:65) - binding parameter [2] as [INTEGER] - [29]
2018-04-22 09:16:29,600:INFO restartedMain (StatisticalLoggingSessionEventListener.java:258) - Session Metrics {
225339 nanoseconds spent acquiring 1 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
731494 nanoseconds spent preparing 7 JDBC statements;
24985288 nanoseconds spent executing 7 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
35806114 nanoseconds spent executing 1 flushes (flushing a total of 3 entities and 1 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}
简单分析
对比log,我们可以看出,两次持久化新的employee对象时,都会:
- 执行一次left join查询语句,查询department关联的所有employee对象
- 每一个新的employee对象,都会执行一次插入操作
不同之处在于,第二次使用JoinColumn
注解的时候,log显示:每一个新增employee对象都执行了一次update语句,更新了外键。
原因就在于,mappedBy
将外键的赋值操作委托给了Employee对象。而JoinColumn
则选择由Department对象自己来约束外键的关联。
两个注解只有少许区别,但是最终的执行结果差异却很大。多出来的写操作,在生产环境下很容易对数据库构成很大的压力。在代码中完成对Employee对象departmentId
属性的赋值,显然是一个更为合适的方案。
实际上,上面两个方案是JPA API文档里OneToMany
注解的示例2、示例3.
示例1的双向注解,也可以避免多余的写操作。