[上一篇][1]介绍了 Hibernate 5 和 Spring Boot 2 一些性能方面的最佳实践,这一篇会继续介绍剩下的内容。让我们开始吧!
[26. 如何通过 SqlResultSetMapping & NamedNativeQuery 提取 DTO][2]
[27. 如何通过 javax.persistence.Tuple 和原生 SQL 提取 DTO][3]
[28. 如何通过 javax.persistence.Tuple 和 JPQL 提取 DTO][4]
[29. 如何通过 Constructor 表达式和 JPQL 提取 DTO][5]
[30. 如何通过 ResultTransformer 和原生 SQL 提取 DTO][6]
[31. 如何通过 ResultTransformer 和 JPQL 提取 DTO][7]
[1]:https://dzone.com/articles/50-best-performance-practices-for-hibernate-5-amp
[2]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoSqlResultSetMappingAndNamedNativeQuery
[3]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoTupleAndSql
[4]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoTupleAndJpql
[5]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoConstructorExpression
[6]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoResultTransformer
[7]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoResultTransformerJpql
32. 通过 Blaze-Persistence 实体视图实现 DTO
"描述:" 获取不必要的数据很容易造成性能损失,使用 DTO 允许只提取必须的数据。在这个示例中,使用了 Blaze-Persistence 实体视图。
技术要点
在 Maven `pom.xml` 中,添加 Blaze-Persistence 依赖
配置 Blaze-Persistence 中 `CriteriaBuilderFactory` 和 `EntityViewManager`
按照 Blaze-Persistence 风格接口开发 *entity view*
编写 Spring repository 继承 `EntityViewRepository`
调用 repository 中的方法,比如 `findAll()`、`findOne()` 等
使用 Spring Data Projection 请参考例9
[示例代码][8]
[8]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoBlazeEntityView
33. 没有 @OrderColumn 时使用 @ElementCollection 的执行效果
没有 `@OrderColumn` 时,对 `@ElementCollection` 进行插入和删除会造成性能损失。有 `@OrderColumn` 时,情况会好一些。
"描述:"在这个示例中,展示了没有 `@OrderColumn` 时使用 `@ElementCollection` 可能带来的性能损失。接下来的示例(例34)加上 `@OrderColumn` 能减少性能损失。
技术要点
`@ElementCollection` 未设置主键
`@ElementCollection` 映射到独立的数据库表
需要频繁插入、删除操作时,避免使用 `@ElementCollection`。因为执行插入或删除前,数据库会删除所有记录
数据表记录越多,性能损失越大
示例输出
[示例代码][9]
[9]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootElementCollectionNoOrderColumn
34. 使用 OrderColumn 时 @ElementCollection 的执行效果
没有 `@OrderColumn` 时,对 `@ElementCollection` 进行插入和删除会造成性能损失。有 `@OrderColumn` 时,情况会好一些。
"描述:"在这个示例中,展示了没有 `@OrderColumn` 时使用 `@ElementCollection` 可能带来的性能损失。加上 `@OrderColumn` 后,能减少对集合尾部操作(例如,在集合尾部执行 add/remove 操作)的性能损失。主要原因是操作对象左边的元素都不会移动,性能开销主要集中在集合尾部。
技术要点
`@ElementCollection` 未设置主键
`@ElementCollection` 映射到独立的数据库表
需要频繁插入、删除操作时,使用 `@ElementCollection` 同时加上 `@OrderColumn`
在数据表开始的地方,执行插入或移除记录越多性能损失越大
示例输出
[示例代码][10]
[10]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootElementCollectionWithOrderColumn
35. 如何避免 Open Session In View 反模式带来的延迟加载实体问题(1 Session/1 HTTP Request-Response)
只要存在延迟加载实体,无论将来是否用到 Open-Session In View 都会获取,从而造成严重的性能问题。
"描述:"Spring Boot 会默认开启 Open Session In View 反模式。如果确实需要使用,最好能够减少由此带来的性能损失。一种优化方法,可以把 `Connection` 标记为只读,这样能够避免数据库服务器记录事务日志。另一种办法,对那些不希望被延迟加载的实体属性显式标记。
技术要点
获取实体并显式设置延迟加载属性
可以根据需要在 Service 或 Controller,但"一定要在事务之外设置"
为什么这种方法有效?为什么能够设置托管实体属性却不触发刷新操作?答案可以从 `OpenSessionInViewFilter` 文档中找到:
>>>
“注意:该 filter 默认不会刷新 Hibernate Session,刷新模式默认设为 `FlushMode.NEVER`。这里假定 Service 层事务会处理刷新:在事务读写期间,活动事务管理器会临时把刷新模式更改为 `FlushMode.AUTO`,事务结束时重置刷新模式为 `FlushMode.NEVER`。如果需要在不使用事务的情况下使用 filter,可以考虑修改 `flushMode` 更改默认刷新模式。”
>>>
示例输出
示例代码][11]
[11]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootSuppressLazyInitInOpenSessionInView
36. 如何使用 Spring Projection(DTO) 和 Inner Join
SQL JOIN 和 DTO 有助于解决 N+1 问题,例36至42包含了很多相关示例。
> 译注:“N+1 问题”即执行一次查询 N 条主数据后,由于关联引起的 N 次从数据查询,因此会带来了性能问题。一般来说,通过延迟加载可以部分缓解 N+1 带来的性能问题。
"描述:"这个示例使用 JPQL 和原生 SQL(MySQL)验证 Spring Projection(DTO) 和 Inner Join 方案。
技术要点
定义若干实体:例如 `Tournament` 和 `Player`,保持双向 `@OneToMany`
用测试数据填充数据库:例如,`resources/data-mysql.sql` 中的数据
开发从数据库中获取列信息 getter 接口(Projection):例如,`TournamentPlayerNameDto`、`PlayerRankNameDto`、`TournamentIdNameDto`
使用 JPQL 或原生 SQL 编写 Inner Join 查询,例如
查询所有球员的比赛:`localhost:8080/tournamentsOfPlayersNamesInnerJoinJpql`
查询所有排名小于或等于 `rank` 球员的比赛:`localhost:8080/tournamentsIdNameByRankInnerJoinSql`
[示例代码][12]
[12]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoViaInnerJoins
你可能也会对下面内容感兴趣
[37. 如何使用 Spring Projections(DTO) 和 Left Join][13]
[38. 如何使用 Spring Projections(DTO) 和 Right Join][14]
[39. 如何使用 Spring Projections(DTO) 和 Full Join][15]
[40. 如何使用 Spring Projections(DTO) 和 Left Excluding Join][16]
[41. 如何使用 Spring Projections(DTO) 和 Right Excluding Join][17]
[42. 如何使用 Spring Projections(DTO) 和 Outer Excluding Join][18]
[13]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoViaLeftJoins
[14]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoViaRightJoins
[15]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoViaFullJoins
[16]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoViaLeftExcludingJoins
[17]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoViaRightExcludingJoins
[18]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoViaOuterExcludingJoins
43. 如何使用 Spring Post 提交
本例中描述的现象通常出现在生产环境,这种环境的负载很高。Spring post-commit hook 会在执行完成前让数据库连接保持打开状态。提交结束后,在 hook 函数中执行耗时任务。高负载时会导致连接池处于饥饿状态。显然,这种情况下需要更长时间才能连接成功。
"描述:"在这个示例展示了 Spring Post Commit Hook.
技术要点
避免在 Post Commit 中执行耗时任务,数据库连接会一直保持打开状态直到代码执行完成。
[示例代码][19]
[19]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootPostCommit
44. 如何在无关实体上使用 Spring Projection(DTO) 和 Join(Hibernate 5.1+)
Hibernate 5.1 引入了 Explicit Join 可以在无关实体上使用,语法和执行效果与 SQL JOIN 类似。
"描述:" 在这个示例展示了 Spring Projection 和无关实体连接。Hibernate 5.1 引入了 Explicit Join 可以在无关实体上使用,语法和执行效果与 SQL JOIN 类似。
技术要点
定义若干实体:例如,`Patient` 和 `Clinic` 无关实体
用测试数据填充数据库:例如,`resources/data-mysql.sql` 中的数据
开发从数据库中获取列信息 getter 接口(Projection):例如,`PatientNameAndMedicalHistoryDto`
使用 JPQL 或原生 SQL 编写 Join 查询,例如
查询目前不在治疗中的所有病人的姓名和病史:`localhost:8080/allPatientsNameAndMedicalHistoryNoTreatmentInnerJoinJpql`
[示例代码][20]
[20]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoUnrelatedEntities
45. 为什么要避免在实体上使用 Lombok @EqualsAndHashCode
Lombok 时目前非常流行的一个开发库。但请注意,在实体上使用 Lombok `@EqualsAndHashCode` 可能会造成严重问题。
"描述:"实体必须像[下面][21]这样实现 `equals()` and `hashCode()`,而且必须在 *transient*、*attached*、*detached* 和 *removed* 状态转换中保持 equal。Lombok `@EqualsAndHashCode` 无法满足上述要求。
[21]:https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/
技术要点
"避免"
使用 Lombok `@EqualsAndHashCode` 默认行为:实体 `LombokDefaultProduct`,测试 `LombokDefaultEqualsAndHashCodeTest`
只基于主键使用 Lombok `@EqualsAndHashCode`:实体 `LombokIdProduct`,测试 `LombokEqualsAndHashCodeWithIdOnlyTest`
使用默认的 `equals()` 和 `hashCode()` :实体 `DefaultProduct`,测试 `DefaultEqualsAndHashCodeTest`
只基于主键实现 `equals()` 和 `hashCode()` :实体 `IdProduct`,测试 `IdEqualsAndHashCodeTest`
"推荐"
依赖 `@NaturalId`:实体 `NaturalIdProduct`,测试 `NaturalIdEqualsAndHashCodeTest`
依赖 *Primary Key*:实体 `GoodProduct`,测试 `GoodEqualsAndHashCodeTest`
`equals()` 和 `hashCode()` 的优秀实现:
[示例代码][22]
[22]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootLombokEqualsAndHashCode
46. 如何通过 JOIN FETCH 避免 LazyInitializationException
如果你从未遇到 `famousLazyInitializationException`,说明你从未用过 Hibernate :) 加入从 `LAZY` 转为 `EAGER` 解决了这个异常,这个例子就是为你准备的。
"描述:" 当遇到 `LazyInitializationException` 时,通常会把获取类型从 `LAZY` 改为 `EAGER`。这是一种很糟糕的[代码异味][23]。避免这种异常的最好办法是 `JOIN FETCH` + DTO(如果有需要)。这个示例展示了无 DTO 的 JOIN FETCH,通过单个 `SELECT` 查询获取实体。但是,基于上述 DTO 示例也可以很方便地改为使用 DTO。
[23]:https://vladmihalcea.com/eager-fetching-is-a-code-smell/
技术要点
定义两个相关实体,例如 `Category` 和 `Product` 保持*一对多*双向延迟加载关系
编写 JPQL `JOIN FETCH` 获取包括 product 在内的一个 category
编写 JPQL `JOIN FETCH` 获取所有 product 包括 category
示例输出
[示例代码][24]
[24]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootJoinFetch
47. 如何合并实体集合
如何正确地合并集合不是一件轻松的事情!
"描述:"这个 Spring Boot 示例基于[这篇文章][25]。这是 Vlad 例子的一个函数式实现。'强烈推荐阅读此文'
[25]:https://vladmihalcea.com/merge-entity-collections-jpa-hibernate/
技术要点
从数据库中移除不在目标集合中出现的行
在数据库中更新所有出现在目标集合中行
为数据库添加所有出现在目标集合中,但没有在数据库快照中找到的行
[示例代码][26]
[26]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootMergeCollections
48. 如何根据需要延迟获取数据库连接(Hibernate 5.2.10)
得到数据库连接后不立即使用可能导致性能下降。需要数据库连接的人可不会一直等待。
"描述:" 这个 Spring Boot 示例展示了如何利用 Hibernate 5.2.10 特性根据需要延迟获取连接。通常,调用 `@Transactional` 注解的方法后会立即得到连接。假如这个方法在执行第一条 SQL 语句后包含了一些耗时的任务,那么就会毫无意义地一直保持连接。但是,Hibernate 5.2.10 允许根据需要延迟获取连接。示例采用 HikariCP 作为 Spring Boot 默认连接池。
技术要点
在 `application.properties` 中设置 `spring.datasource.hikari.auto-commit=false`
在 `application.properties` 中设置 `spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true`
示例输出
[示例代码][27]
[27]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDelayConnection
49. 如何通过 Hibernate hi/lo 算法获取主键
通常,我们不太关心如何生成或获取主键。如果真正关注的话,会考虑使用 `SEQUENCE` 这种便捷、灵活且高效的方法。然而,每个 sequence 都会执行一个数据库行程导致性能问题。hi/lo 是一种标识符序列生成优化算法。
"描述:" 这个 Spring Boot 示例使用 hi/lo 算法,在10个数据库行程中,批量执行1000次插入(每次10条记录)并获取1000个主键。
技术要点
使用 `SEQUENCE` 生成器类型,例如 PostgreSQL
在 `Player.java` 实体中配置 `hi/lo` 算法
示例输出
[示例代码][28]
[28]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootHiLo
50. 如何编写高效双向 @ManyToMany 关联
你是否需要一个“多对多”关系的数据表,而且不希望使用两个 `@OneToMany` 关联进行映射?如果你更喜欢 `@ManyToMany` 双向关联,可以变得更高效一点。
"描述:" 这个示例是一个关于如何编写高效双向 `@ManyToMany` 关联的验证
技术要点
使用 `Tournament` 和 `Player` 两个实体。一个锦标赛可以有多个玩家,一个玩家可以参加多个锦标赛
示例输出
[示例代码][29]
[29]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootManyToManyBidirectional
51. 在 @ManyToMany 关系中优先使用 Set 而非 List
在 `@ManyToMany` 中使用 `List` 而不是 `Set` 可能会生成比预期更多的 SQL,例如删除操作。从而导致降低下降。
"描述:"这个 Spring Boot 示例,在双向 `@ManyToMany` 关系中分别使用了 `List` 与 `Set` 删除记录。结论是 `Set` 效果更好,该结论对于单项关系也成立。
技术要点
使用 `Set` 比 `List` 效率更高
示例输出
[示例代码][30]
[30]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootManyToManyBidirectionalListVsSet
如果喜欢本文,你可能也会对[这本书][31]感兴趣。
[31]:https://leanpub.com/java-persistence-performance-illustrated-guide