標籤:jdbc 簡單 record log4j2 rom 避免 repo exe rtm
Stackoverflow有一道題JoinColumn vs mappedBy很有意思:
@Entitypublic class Company { @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY) @JoinColumn(name = "companyIdRef", referencedColumnName = "companyId") private List<Branch> branches;...}
@Entitypublic class Company { @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, mappedBy = "companyIdRef") private List<Branch> branches; ...}
上面兩種級聯有什麼區別?
原題下面有兩個高分答案。可惜對於我這種學渣,看完還是一頭霧水。
看不懂別人寫的,那就自己調調看。
準備環境
磨刀不誤砍柴工,先準備調試環境。
我用的是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
屬性。
然後就是儲存一個對象看看。
@Servicepublic 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的雙向註解,也可以避免多餘的寫操作。
參考資料
- [JavaEE - JPA] 效能最佳化: 如何定位效能問題
- The best way to map a @OneToMany relationship with JPA and Hibernate
JoinColumn vs mappedBy