2

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

 2 years ago
source link: https://www.jdon.com/59674
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Spring Boot数据存储最佳实践

在这篇文章中,我们回顾了对优化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.。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK