Spring Boot数据存储最佳实践 - Ahad


在这篇文章中,我们回顾了对优化spring boot数据访问层非常有效的最佳实践。
 
Spring boot JPA增加了一些关于JPA的接口。JPA只是一种规范,而不是一种实现。有各种实现JPA的ORM,如Hibernate和EclipseLink。
Hibernate提供了许多好处,如对象映射、缓存、多事务、独立于数据库的查询等。但是它必须管理每个实体的生命周期(暂存、管理、分离、移除),这导致了一点开销。但是,如果缺乏足够的知识来使用它,就会造成严重的性能问题,并可能导致产品失败。
对于优化基于Spring boot JPA和Hibernate的应用程序数据访问层,我推荐这些技巧:
  
监测和测试

  • 为了实现生产和测试之间100%的数据库兼容性,使用测试容器而不是内存数据库(H2,Fongo)进行测试。
  • 使用单元测试来断言和计算SQL语句,确保你的代码不会产生额外的查询。使用此链接获取更多信息。
  • 通过在配置文件中添加这一行来记录缓慢的查询(在Hibernate 5.4.5以上版本中可用),否则使用第三方库,如datasource-proxy。

  
事务
在事务中,我们应该尽可能地使用只读和小型事务。下面的说明可以帮助我们提高事务的性能。
  • 使用这些配置来推迟获取连接,直到真正需要的时候。

spring.datasource.hikari.auto-commit=false
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true

  • 使用@Transactional(readOnly=true)来加载只读状态的实体。
  • @Transactional() 在读写状态下加载实体。只读状态要比读写状态便宜得多。
  • 不要在私有或保护方法上使用@Transactional()。因为它在这种模式下不起作用。
  • 在Repository Interfaces中使用@Transactional(readOnly=true),在有更新或创建操作的服务方法中使用@Transactional()(或任何需要作为单一事务运行的方法)。
  • 不要在服务或控制器类中使用 @Transactional() 。
  • 重构长时间的事务方法以减少事务运行时间。把它们分解成更小的事务。

 
Id生成类型
  • 不要使用GenerationType.AUTO,因为在Mysql中它映射为GenerationType.TABLE,当你想插入一条新行时,会导致三次查询。
  • GenerationType.SEQUENCE是一个不错的选择。但是,如果底层的DBMS不能支持它,请使用GenerationType.IDENTITY(在Mysql中)。
  • 不要使用复合键和长键作为主键。避免使用UUID/GUID作为主键,因为它是随机的,会导致B树的重新组织,最后会降低性能。

 
关联和关系
我们应该防止创建意外的表和执行更多的SQL语句。请考虑这些。
  • 使用双向的@OneToMany关联(1⇆m)而不是单向的(1→m)。
  • 使用@JoinColumn可以为单向的@OneToMany提供好处,但是双向的@OneToMany仍然比它好。
  • 单向的@ManyToOne是完全有效的。
  • 总是使用从父方到子方的级联。
  • 在@OneToMany中,当在多个会话中加载实体或处理分离的实体时,覆盖equals()和hashCode()方法。
  • 在任何关系类型中,对关联的每个部分使用懒惰获取(例如,@ManyToOne(fetch = FetchType.LAZY))
  • 在@ManyToMany中选择关系的所有者,并使用Set而不是List。

 
读取和查询
  • 当你不想改变请求的实体时,使用DTO来映射获取的只读实体。
  • 当想要修改一个实体并需要加载关系的子端时,在JPQL查询中使用JOIN Fetch,而不是急于加载。如果我们想获取左边的所有实体,那么我们使用LEFT JOIN FETCH。
  • 在JPQL/SQL中,JOIN映射到INNER JOIN,LEFT/RIGHT/FULL JOIN等同于LEFT/RIGHT/FULL OUTER JOIN。
  • 显式 JOIN 比隐式 JOIN 好。在某些情况下,隐式JOIN映射为CROSS JOIN而不是INNER JOIN。
  • 在父子实体查询中,当使用JOIN FETCH时,结果可能包含对象引用重复。它为每一条要被获取的子行重复了父记录。DISTINCT关键字应该只在从JDBC获得ResultSet后应用,因此建议使用INT_PASS_DISTINCT_THROUGH而不是DISTINCT。不要在标量查询中使用该方法。
  • 使用Spring的内置方法findById()、find()或get()来获取实体的id,比通过特定的JPQL/SQL查询来获取要好。这些方法使用了Hibernate的缓存机制(一级缓存->二级缓存->数据库),但是JPQL/SQL查询直接从数据库中获取。
  • 对于获取实体,我们可以使用Spring内置的方法,findById()(它使用EntityManagerfind()并返回精确的声明模型)或getOne()(它使用EntityManagergetReference()并返回Hibernate特定的代理对象)。
  • 在@ManyToOne或@OneToOne关联中,当我们需要用对其父实体的引用来持久化一个子实体时,最好使用getOne()来获取父方。
  • 在冲洗的时候,Hibernate会在实体中找到修改过的属性,并根据脏检查机制来更新它们。在更新任务中不要使用save()方法,因为它是一个重复的任务,会导致额外的Hibernate特定内部操作(MergeEvent)。当然,使用save()方法也不会产生额外的查询。
  • 为了避免N+1的问题(执行1个查询来检索父实体和N个查询来检索子实体),使用JOIN FETCH或实体图。
  • 总是使用分页来使结果集尽可能的小。
  • Open Session In View(OSIV)试图通过将JPA EntityManager绑定到请求线程生命周期中来避免LazyInitializationException。这是一个糟糕的方法和反模式,业务层应该决定获取的方法。它还会导致性能下降。使用这个配置来关闭OSIV:spring.jpa.open-in-view=false
  • 缓存执行计划通过缓存准备好的执行计划来减少负载。该计划可以重复使用,没有优化的开销。如果RDBMS没有缓存执行计划(Mysql),那么通过IN子句的参数填充进行缓存只会增加语句的复杂性,并可能降低查询的速度。
  • 如果你有大量的查询,改变Hibernate查询计划缓存(QPC)以覆盖你的应用程序中的所有查询。JPQL和Criteria API的默认大小为2048,本地查询的默认大小为128。

spring.jpa.properties.hibernate.query.plan_cache_max_size = 3000
spring.jpa.properties.hibernate.query.plan_parameter_metadata_max_size=200

  • 在oneToMany关联中,如果你分离一个实体,更新它并保存它,那么这个save()方法会导致一个额外的选择连接查询。为了避免这种情况,并通过更新优化合并操作,请使用Hibernate会话update()方法而不是直接使用JPA save()方法。

val session=entityManager.unwrap(Session::class.java)
session.update(yourEntity)

 
持久性上下文
  • 要在查询执行后清除持久化上下文,请使用@Modifying(clearAutomatically = true)。它可能会删除未保存的更改。为了防止删除持久化上下文中的非刷新实体,使用@Modifying(flushAutomatically = true)。
  • 持久化上下文是一个位于应用程序和数据库之间的第一级缓存。Hibernate使用它来处理实体的生命周期。对于检查持久化上下文中实体的状态,这段小代码很有帮助(Kotlin代码)。

@PersistenceContext
private val entityManager: EntityManager? = null
Val persistenceContext= entityManager.unwrap(SharedSessionContractImplementor::class.java).persistenceContext

 
元素集合

  • 使用@ElementCollection与嵌入式或基本类型而不是实体。在这种情况下,任何对嵌入对象的粗暴操作都会被直接限制,你需要在父类那边做这些操作。
  • 当你在集合上有大量的插入/删除操作时,不要使用@ElementCollection。它会导致Hibernate执行大量不需要的插入/删除操作。

 
分页和Hibernate类型

  • 使用强大的过滤(keyset分页)而不是使用偏移分页。偏移方法在大型数据库中会降低性能,因为要获取和丢弃很多行。
  • 当你需要Hibernate ORM核心不支持的额外类型(如json)时,请使用Hibernate Types资源库repository.。