Spring Data JPA 之 Hibernate 加载过程与配置项

20 JPA 中的 Hibernate 加载过程与配置项

这⼀讲,我们来分析⼀下在 Spring Data JPA 的项⽬下⾯ Hibernate 的配置参数有哪些,先从 Hibernate 的整体架构进⾏分析。

20.1 Hibernate 架构分析

我们可以看一下 Hibernate5.6 官网提供的架构图

官网地址:https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html

在这里插入图片描述

从架构图上,我们可以知道 Hiberante 实现的 ORM 的接⼝有两种,⼀种是 Hiberante ⾃⼰的 API 接⼝;⼀种是 Java Persistence API 的接⼝实现。

因为 Hibernate 其实是⽐ Java Persistence API 早⼏年发展的,后来才有了 Java 的持久化协议。以我个⼈的观点来看,随着时间的推移,Hiberante 的实现逻辑可能会逐渐被弱化,由 Java Persistence API 统⼀对外提供服务。

那么有了这个基础,我们研究 Hibernate 在 Spring Data JPA ⾥⾯的作⽤,得出的结论就是:Hibernate 5.6 是 Spring Data JPA 持久化操作的核⼼。我们再从类上⾯具体看⼀下,关键类的图如下所示:

在这里插入图片描述

结合类的关系图来看,Session 接⼝和 SessionFactory 接⼝都是 Hibernate 的概念,⽽ EntityManger 和 EntityManagerFactory 都是 Java Persistence API 协议规定的接⼝。

那么我们看看 Hibernate 在 Spring BOOT ⾥⾯是如何被加载进去的。

20.2 Hibernate 5 在 Spring Boot 2 中的加载过程

不同的 Spring Boot 版本,可能加载类的实现逻辑是不⼀样的,但是分析过程都是相同的。我们先打开 spring.factories ⽂件,如下图所示,其中可以⾃动加载 Hibernate 的只有⼀个类,那就是 HibernateJpaAutoConfiguration。

在这里插入图片描述

HibernateJpaAutoConfiguration 就是 Spring Boot 加载 Hibernate 的主要⼊⼝,所以我们可以直接打开这个类看⼀下

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class, SessionImplementor.class })
@EnableConfigurationProperties(JpaProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class })
@Import(HibernateJpaConfiguration.class)
public class HibernateJpaAutoConfiguration {

}

其中,我们第⼀个需要关注的就是 JpaProperties 类,因为通过这个类我们可以间接知道 application.properties ⾥⾯可以配置的 spring.jpa 的属性有哪些。

20.2.1 JpaProperties 属性

通过这个类,我们可以在 application.properties ⾥⾯得到如下配置项

# 可以配置 JPA 的实现者的原始属性的配置,如:这⾥我们⽤的 JPA 的实现者是 hibernate
# 那么 hibernate ⾥⾯的⼀些属性设置就可以通过如下⽅式实现,具体 properties ⾥⾯有哪些,本讲会详细介绍,我们先知道这⾥可以设置即可
spring.jpa.properties.hibernate.hbm2ddl.auto=none
# hibernate 的 persistence.xml ⽂件有哪些,⽬前已经不推荐使⽤
# spring.jpa.mapping-resources=
# 指定数据源的类型,如果不指定,Spring Boot 加载 Datasource 的时候会根据URL的协议⾃⼰判断
# 如:spring.datasource.url=jdbc:mysql://localhost:3306/test 上⾯可以明确知道是 mysql 数据源,所以这个可以不需要指定;
# 应⽤场景,当我们通过代理的⽅式,可能通过 datasource.url 没办法判断数据源类型的时候,可以通过如下⽅式指定,可选的值有:DB2,H2,HSQL,INFORMIX,MYSQL,ORACLE,POSTGRESQL,SQL_SERVER,SYBASE
spring.jpa.database=mysql
# 是否在启动阶段根据实体初始化数据库的 schema,默认 false,当我们⽤内存数据库做测试的时候可以打开,很有⽤
spring.jpa.generate-ddl=false
# 和 spring.jpa.database ⽤法差不多,指定数据库的平台,默认会⾃⼰发现;⼀般不需要指定,database-platform 指定的必须是 org.hibernate.dialect.Dialect 的⼦类,如 mysql 默认是⽤下⾯的 platform
spring.jpa.database-platform=org.hibernate.dialect.MySQLInnoDBDialect
# 是否在 view 层打开 session,默认是 true,其实⼤部分场景不需要打开,我们可以设置成 false,
spring.jpa.open-in-view=false
# 是否显示 sql,当执⾏ JPA 的数据库操作的时候,默认是 false,在本地开发的时候我们可以把这个打开,有助于分析 sql 是不是我们预期的
# 在⽣产环境的时候建议给这个设置成 false,改由 logging.level.org.hibernate.SQL=DEBUG 代替,这样的话⽇志默认是基于 logback 输出的
# ⽽不是直接打印到控制台的,有利于增加 traceid 和线程 ID 等信息,便于分析
spring.jpa.show-sql=true

其中,spring.jpa.show-sql=true 输出的 sql 效果如下所示。

Hibernate: insert into user (deleted, version, email, name, id) values (?, ?, ?, ?, ?)

上⾯是孤⽴⽆援的 System.out.println 的效果,如果是在线上环境,多线程的情况下就不知道是哪个线程输出来的,⽽ logging.level.org.hibernate.SQL=DEBUG 输出的 sql 效果如下所示。

2022-08-20 07:24:01.307 DEBUG 44564 --- [           main] org.hibernate.SQL                        : insert into user (deleted, version, email, name, id) values (?, ?, ?, ?, ?)

这样我们可以轻易知道线程 ID 和执⾏时间,甚⾄可以有 tranceID 和 spanID 进⾏⽇志跟踪,⽅便分析是哪个线程打印的。

我们了解完了 JpaProperties,下⾯再看另外⼀个关键类 HibernateJpaConfiguration,它也是 HibernateJpaAutoConfiguration 导⼊进来加载的。

20.2.2 HibernateJpaConfiguration 分析

我们通过上述 HibernateJpaAutoConfiguration ⾥⾯的 @Import(HibernateJpaConfiguration.class),打开 HibernateJpaConfiguration.class 看看是什么情况

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HibernateProperties.class)
@ConditionalOnSingleCandidate(DataSource.class)
class HibernateJpaConfiguration extends JpaBaseConfiguration {
    // ... 其他我们暂不关⼼的代码我们可以先省略 
}

通过源码我们可以得到 Hibernate 在 JPA 中配置的三个重要线索,下⾯详细说明。

第⼀个线索:HibernatePropertes 这个配置类对应的是 spring.jpa.hibernate 的配置。

我们通过源码可以看得出来,@EnableConfigurationProperties(HibernateProperties.class) 启⽤了 HibernatePropertes 的配置类,其中可以看到 application.properties 的配置项,如下所示。

# 正如我们之前课时讲到的nameing的物理策略值有: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy(默认) 和org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.hibernate.naming.physical-strategy=
# ddl 的⽣成策略,默认 none;如果我们没有指定任何数据源的 url,采⽤的是 spring 的集成数据源,也就是内存数据源 H2 的时候,默认值是 create-drop;
# 所以你会发现当我们每次⽤ H2 的时候什么都没做,它就会⾃动帮我们创建表等,内存数据库和写测试⽤的时候,create-drop 就⾮常⽅便了;不过,当我们⽣产数据库的时候⼀定要设置成 none;
spring.jpa.hibernate.ddl-auto=none
# 当我们的 @Id 配置成 @GeneratedValue(strategy= GenerationType.AUTO) 的时候是否采⽤ hibernate 的 Id-generator-mappings (即会默认帮我们创建⼀张表 hibernate_sequence 来存储和⽣成 ID),默认是 true
spring.jpa.hibernate.use-new-id-generator-mappings=true

第⼆个线索:通过源码我们还可以看得出来,HibernateJpaConfiguration 的⽗类 JpaBaseConfiguration 也会优先加载,此类就是 Spring Boot 加载 JPA 的核⼼逻辑

那么我们打开 JpaBaseConfiguration 类看⼀下源码。

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(JpaProperties.class)
public abstract class JpaBaseConfiguration implements BeanFactoryAware {
    
    // ... 其他我们暂不关⼼的代码我们可以先省略 
    
    protected JpaBaseConfiguration(DataSource dataSource, JpaProperties properties,
			ObjectProvider<JtaTransactionManager> jtaTransactionManager) {
		this.dataSource = dataSource;
		this.properties = properties;
        // jtaTransactionManager 赋值,正常情况下我们⽤不到,⼀般⽤来解决分布式事务的场景才会⽤到。
		this.jtaTransactionManager = jtaTransactionManager.getIfAvailable();
	}
    
    /**
     * 加载 JPA 的实现⽅式
     */
    @Bean
	@ConditionalOnMissingBean
	public JpaVendorAdapter jpaVendorAdapter() {
		AbstractJpaVendorAdapter adapter = createJpaVendorAdapter();
		adapter.setShowSql(this.properties.isShowSql());
		if (this.properties.getDatabase() != null) {
			adapter.setDatabase(this.properties.getDatabase());
		}
		if (this.properties.getDatabasePlatform() != null) {
			adapter.setDatabasePlatform(this.properties.getDatabasePlatform());
		}
		adapter.setGenerateDdl(this.properties.isGenerateDdl());
		return adapter;
	}
    // ... 其他我们暂不关⼼的代码我们可以先省略 
}

从构造函数中我们也可以看到其是否有⽤到 jtaTransactionManager(这个是分布式事务才会⽤到);⽽ createJpaVendorAdapter() 是在 HibernateJpaConfiguration ⾥⾯实现的,这个要重点说⼀下,关键代码如下。

class HibernateJpaConfiguration extends JpaBaseConfiguration {
    // 这⾥是 hibernate 和 Jpa 的结合,可以看到使⽤的 HibernateJpaVendorAdapter 作为 JPA 的实现者,感兴趣的话你可以打开 HibernateJpaVendorAdapter ⾥⾯设置⼀些断点,就会知道 Spring boot 是如何⼀步⼀步加载 Hibernate 的了;
    @Override
	protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
		return new HibernateJpaVendorAdapter();
	}
}   

现在我们知道了 HibernateJpaVendorAdapter 的加载逻辑,⽽ HibernateJpaVendorAdapter ⾥⾯实现了 Hibernate 的初始化逻辑,我在这⾥不多说了,你过后可以仔细 debug 看⼀下,基本上就是 Hibernate 5.6 官⽅的加载逻辑。那么 Hibernate Jpa 对应的原始配置有哪些呢?

第三个线索:spring.jpa.properties 配置项有哪些?

我们打开 org.hibernate.cfg.AvailableSettings 可以看到 Hibernate ⽀持的配置项⼤概有 100 多个配置信息,那么接下来我们看看该怎么使⽤ AvailableSettings ⾥⾯的配置呢?

我们只需要将 AvailableSettings 变量的值放到 spring.jpa.properties ⾥⾯即可,如下这些是我们常⽤的。

# 开启hibernate statistics的信息,如session、连接等⽇志:
spring.jpa.properties.hibernate.generate_statistics=true
# 格式化 SQL
spring.jpa.properties.hibernate.format_sql: true
# 显示 SQL
spring.jpa.properties.hibernate.show_sql: true
# 添加 HQL 相关的注释信息
spring.jpa.properties.hibernate.use_sql_comments: true
# hbm2ddl 的策略 validate, update, create, create-drop, none,建议配置成 validate,
# 这样在我们启动项⽬的时候就知道⽣产数据库的表结构是否正确的了,⽽不⽤等到运⾏期间才发现问题。
spring.jpa.properties.hibernate.hbm2ddl.auto=validate
# 关联关系的时候取数据的深度,默认是 3 层,我们可以设置成 2 级,防⽌其他开发乱⽤,提⾼ sql 性能
spring.jpa.properties.hibernate.max_fetch_depth=2
# 批量 fetch ⼤⼩默认 -1
spring.jpa.properties.hibernate.default_batch_fetch_size=100
# 事务完成之前是否进⾏ flush 操作,即同步到 db ⾥⾯去,默认是 true
spring.jpa.properties.hibernate.transaction.flush_before_completion=true
# 事务结束之后是否关闭 session,默认 false
spring.jpa.properties.hibernate.transaction.auto_close_session=false
# 有的时候不只要批量查询,也会批量更新,默认 batch size 是 15,我们可以根据实际情况⾃由调整,可以提⾼批量更新的效率;
spring.jpa.properties.hibernate.jdbc.batch_size=100
# 开启按需 insert sql 重新组合
 spring.jpa.properties.hibernate.order_inserts=true

其他的配置不经常⽤,我们就不需要关⼼了,你只知道在哪⾥看就好,实际⽤到时,发现哪些是我没举例的,你直接看源码会⾮常好理解的。

这⾥我为什么要特别强调这个 Hibernate 的配置类呢?因为有的时候我们遇到问题会去⽹上搜索解决⽅案,发现别⼈给的配置可能不对,那么你就可以想到从这个源码中进⾏查看,并找到解决办法。

本讲我们只关⼼了 JpaVendorAdapter 和 properties 的创建逻辑,我们前⾯在讲数据源的时候也说过这个类,⾥⾯有我们关⼼的 PlatformTransactionManager transactionManager 和 LocalContainerEntityManagerFactoryBean entityManagerFactory 的创建逻辑

那么说了这么多加载的类,它们之间是什么关系呢?我们通过⼀个图来知晓⼀下。

20.2.3 自动加载过程类之间的关系

通过刚刚的分析,我们可以得出一些结论

  1. JpaBaseConfiguration 是 Jpa 和 Hibernate 被加载的基⽯,⾥⾯通过 BeanFactoryAware 的接⼝的 bean 加载⽣命周期也实现了⼀些逻辑。
  2. HibernateJpaConfiguration 是 JpaBaseConfiguration 的⼦类,覆盖了⼀些⽗类⾥⾯的配置相关的特殊逻辑,并且⾥⾯引⽤了 JpaPropeties 和 HibernateProperties 的配置项。
  3. HibernateJpaAutoConfiguration 是 Spring Boot ⾃动加载 HibernateJpaConfiguration 的桥梁,起到了 importHibernateJpaConfiguration 和加载 HibernateJpaConfiguration 的作⽤。
  4. JpaRepositoriesAutoConfiguration 和 HibernateJpaAutoConfiguration、DataSourceAutoConfiguration 分别加载 JpaRepositories 的逻辑和 HibernateJPA、数据源,都是被 spring.factories ⾃动装配进⼊到 Spring Boot ⾥⾯的,⽽三者之间有加载的先后顺序。
  5. DataSourceAutoConfiguration 最先加载、HibernateJpaAutoConfiguration 第⼆顺序加载、JpaRepositoriesAutoConfiguration 最后加载。

我们了解完了 Hibernate 5 在 Spring Boot ⾥⾯的加载过程,那么来看下 JpaRepositoriesAutoConfiguration 的主要作⽤有哪些。

20.3 Repositories 的加载模式

我们通过上⾯分享的整个加载过程可以发现,DataSourceAutoConfiguration 完成了数据源的加载,HibernateJpaAutoConfiguration 完成了 Hibernate 的加载过程,⽽ JpaRepositoriesAutoConfiguration 要做的就是解决我们之前定义的 Repositories 相关的实体和接⼝的加载初始化过程,这是 Spring Data JPA 的主要实现逻辑,和 Hiberante、数据源没什么关系了。

我们可以通过 JpaRepositoriesAutoConfiguration 的源码发现其主要职责和实现⽅式,利⽤异步线程池初始化 repositories,关键源码如下:

在这里插入图片描述

⽽其中加载 repositories 有三种⽅式,即 spring.data.jpa.repositories.bootstrap-mode 的三个值,分别为 deferred、 lazy、 default,下⾯详细说明。

  • deferred:是默认值,表示在启动的时候会进⾏数据库字段的检查,⽽ repositories 相关的实例的初始化是 lazy 模式,也就是在第⼀次⽤到 repositories 实例的时候再进⾏初始化。这个⽐较适合⽤在测试环境和⽣产环境中,因为测试不可能覆盖所有场景,万⼀谁多加个字段或者少⼀个字段,这样在启动的阶段就可以及时发现问题,不能等进⾏到⽣产环境才暴露。
  • lazy:表示启动阶段不会进⾏数据库字段的检查,也不会初始化 repositories 相关的实例,⽽是在第⼀次⽤到 repositories 实例的时候再进⾏初始化。这个⽐较适合⽤在开发的阶段,可以加快应⽤的启动速度。如果⽣产环境中,我们为了提⾼业务⾼峰期间⽔平来扩展应⽤的启动速度,也可以采⽤这种模式。
  • default:默认加载⽅式,但从 Spring Boot 2.0 之后就不是默认值了,表示⽴即验证、⽴即初始化 repositories 实例,这种⽅式启动的速度最慢,但是最保险,运⾏期间的请求最快,因为避免了第⼀次请求初始化 repositories 实例的过程。

我们通过在 application.properties ⾥⾯修改这⼀⾏代码,来测试⼀下 lazy 的加载⽅式。

spring.data.jpa.repositories.bootstrap-mode=lazy

然后启动我们的项⽬,就会发现在 tomcat 容器加载完之后,没有⽤到 UserRepository 之前,这个 UserRepository 是不会进⾏初始化的。⽽当我们发⼀个请求⽤到了 UserRepository,就进⾏了初始化。

我们通过⽇志也可以看到,启动的线程和初始化的线程是不⼀样的,⽽初始化的线程是 NIO 线程的名字,表示 request 的 http 线程池⾥⾯的线程。

2022-08-20 09:10:13.410 DEBUG 54062 --- [nio-8080-exec-1] o.s.d.r.c.s.RepositoryFactorySupport     : Initializing repository instance for com.example.jpa.example1.repository.UserRepository…

我们在分析 Hibernate 的加载⽅式的时候,会发现⽇志的重要性,那么都有哪些⽇志供我们观察呢?如何开启?

20.4 在调试时需要的日志配置

### ⽇志级别的灵活运⽤
## hibernate相关
# 显示 sql 的执⾏⽇志,如果开了这个,show_sql 就可以不⽤了
logging.level.org.hibernate.SQL=debug
# hibernate id的⽣成⽇志
logging.level.org.hibernate.id=debug
# hibernate 所有的操作都是 PreparedStatement,把 sql 的执⾏参数显示出来
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
# sql 执⾏完提取的返回值
logging.level.org.hibernate.type.descriptor.sql=trace
# 请求参数
logging.level.org.hibernate.type=debug
# 缓存相关
logging.level.org.hibernate.cache=debug
# 统计 hibernate 的执⾏状态
logging.level.org.hibernate.stat=debug
# 查看所有的缓存操作
logging.level.org.hibernate.event.internal=trace
logging.level.org.springframework.cache=trace
# hibernate 的监控指标⽇志
logging.level.org.hibernate.engine.internal.StatisticalLoggingSessionEventLis
tener=DEBUG
### 连接池的相关⽇志
## hikari 连接池的状态⽇志,以及连接池是否完好 #连接池的⽇志效果:HikariCPPool - Pool stats (total=20, active=0, idle=20, waiting=0)
logging.level.com.zaxxer.hikari=TRACE
#开启 debug 可以看到 AvailableSettings ⾥⾯的默认配置的值都有哪些,会输出类似下⾯的⽇志格式
# org.hibernate.cfg.Settings : Statistics: enabled
# org.hibernate.cfg.Settings : Default batch fetch size: -1
logging.level.org.hibernate.cfg=debug
# hikari 数据的配置项⽇志
logging.level.com.zaxxer.hikari.HikariConfig=TRACE
### 查看事务相关的⽇志,事务获取,释放⽇志
logging.level.org.springframework.orm.jpa=DEBUG
logging.level.org.springframework.transaction=TRACE
logging.level.org.hibernate.engine.transaction.internal.TransactionImpl=DEBUG
### 分析 connect 以及 orm 和 data 的处理过程更全的⽇志
logging.level.org.springframework.data=trace
logging.level.org.springframework.orm=trace

上⾯是我在分析复杂问题和原理的时候常⽤的⽇志配置项⽬,这⾥给你提供⼀个技巧,当我们分析⼀个问题的时候,如果不知道⽇志具体在哪个类⾥⾯,通过设置 logging.level.root=trace 的话,⽇志⼜⾮常多⼏乎没有办法看,那么我们可以缩⼩范围,不如说我们分析的是 hikari 包⾥⾯相关的问题。

我们可以把整个⽇志级别 logging.level.root=info 设置成 info,把其他所有的⽇志都关闭,并把 logging.level.com.zaxxer=trace 设置成最⼤的,保持⽇志不受⼲扰,然后观察⽇志再逐渐减少查看范围。

20.5 本章小结

这⼀讲我通过源码分析,帮助你了解了 JpaRepositoriesAutoConfiguration、HibernateJpaAutoConfiguration、DataSourceAutoConfiguration 的主要作⽤和加载顺序的依赖,还介绍了 Spring Hibernate 的配置项有哪些。

你在⼯作中可以举⼀反三,通过 debug 断点⼀步⼀步分析出来这⼀讲没涉及的东⻄。⽐如可以⾃⼰做⼀个项⽬,跟着我的步骤操作,你会对这部分的内容有更深刻的体会。这样当遇到⼀些问题,并且⽹上没有合适的资料时,你可以试着采⽤本讲中我分享给你的思路来解决。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值