bigywater.tech
Spring · 87

Repository 接口为什么不用写实现:代理与 SimpleJpaRepository

UserRepo 只是个接口,运行时是谁在干活?一次 findAll 经过了哪些拦截器?事务是从哪来的?为什么派生查询没有事务?这篇在 Spring Boot 4.0.8、Hibernate 7、MySQL 上把代理拆开看。

$Proxy107
容器里的 UserRepo 是 JDK 动态代理,目标是 SimpleJpaRepository
7 个
代理上挂着的拦截器个数,事务拦截器排第 3
只读 / 可写 / 没有
findAll 在只读事务,save 在可写事务,派生查询没有事务

Bean 的真身

实测 · 本机执行输出
repo.getClass() = jdk.proxy2.$Proxy107;AopUtils.isJdkDynamicProxy = true
实现的接口:[UserRepo, Repository, TransactionalProxy, Advised, DecoratingProxy]
代理的目标对象(Advised.getTargetSource().getTarget()):org.springframework.data.jpa.repository.support.SimpleJpaRepository
BeanDefinition 的类:org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean
容器里 UserRepo 类型的 Bean 个数:1;FactoryBean 的 Bean:[&userRepo]

JDK 动态代理,工厂 Bean 是 JpaRepositoryFactoryBean,目标是 SimpleJpaRepository。

拦截器链

实测 · 本机执行输出
  …data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor
  …dao.support.PersistenceExceptionTranslationInterceptor
  …transaction.interceptor.TransactionInterceptor
  …data.projection.DefaultMethodInvokingMethodInterceptor
  …data.repository.core.support.QueryExecutorMethodInterceptor
  …data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor
  …dao.support.PersistenceExceptionTranslationInterceptor

7 个拦截器,事务拦截器第 3 个,后面分派到查询方法或基础实现。

事务从哪来

实测 · 本机执行输出
  findAll()(继承自 CrudRepository):
    SQL 发出时:[select] 事务激活=true,只读=true
    调用栈里的关键帧(从里到外):[SimpleJpaRepository.findAll, RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0, RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke, QueryExecutorMethodInterceptor.invoke, DefaultMethodInvokingMethodInterceptor.invoke, TransactionInterceptor$1.proceedWithInvocation, TransactionInterceptor.invoke, JdkDynamicAopProxy.invoke, $Proxy107.findAll]
  findById(1L):
    SQL 发出时:[select] 事务激活=true,只读=true
    调用栈里的关键帧(从里到外):[$Proxy98.find, SimpleJpaRepository.findById, RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0, RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke, QueryExecutorMethodInterceptor.invoke, DefaultMethodInvokingMethodInterceptor.invoke, TransactionInterceptor$1.proceedWithInvocation, TransactionInterceptor.invoke, JdkDynamicAopProxy.invoke, $Proxy107.findById]
  save(new User):
    SQL 发出时:[insert] 事务激活=true,只读=false
    调用栈里的关键帧(从里到外):[$Proxy98.persist, SimpleJpaRepository.save, RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0, RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke, QueryExecutorMethodInterceptor.invoke, DefaultMethodInvokingMethodInterceptor.invoke, TransactionInterceptor$1.proceedWithInvocation, TransactionInterceptor.invoke, JdkDynamicAopProxy.invoke, $Proxy107.save]
  findByName("a")(派生查询):
    SQL 发出时:[select] 事务激活=false,只读=false
    调用栈里的关键帧(从里到外):[$Proxy111.getResultList, AbstractJpaQuery.execute, QueryExecutorMethodInterceptor.invoke, DefaultMethodInvokingMethodInterceptor.invoke, TransactionInterceptor$1.proceedWithInvocation, TransactionInterceptor.invoke, JdkDynamicAopProxy.invoke, $Proxy107.findByName]
  count():
    SQL 发出时:[select] 事务激活=true,只读=true
    调用栈里的关键帧(从里到外):[SimpleJpaRepository.count, RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0, RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke, QueryExecutorMethodInterceptor.invoke, DefaultMethodInvokingMethodInterceptor.invoke, TransactionInterceptor$1.proceedWithInvocation, TransactionInterceptor.invoke, JdkDynamicAopProxy.invoke, $Proxy107.count]
  custom()(自定义片段 UserRepoCustomImpl) → 返回 来自 UserRepoCustomImpl:
    没有发出 SQL
    自定义方法体里的事务状态:custom → 事务激活=false,只读=false
  hello()(接口 default 方法,里面调 count()) → 返回 default 方法:3:
    SQL 发出时:[select] 事务激活=true,只读=true
    调用栈里的关键帧(从里到外):[SimpleJpaRepository.count, RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0, RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke, QueryExecutorMethodInterceptor.invoke, DefaultMethodInvokingMethodInterceptor.invoke, TransactionInterceptor$1.proceedWithInvocation, TransactionInterceptor.invoke, JdkDynamicAopProxy.invoke, $Proxy107.count, $Proxy107.hello]

来自 SimpleJpaRepository 上的 @Transactional;派生查询和自定义片段没有事务。

调用方的事务

实测 · 本机执行输出
  外面(调用方)当前:事务激活=false,只读=false

调用方没有事务时,各个 Repository 调用各开各的事务。

分步演示

Repository 代理
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Hibernate 7、Spring Data JPA 随 spring-boot-starter-data-jpa;MySQL 是本机自己起的 26.7.0 实例,mysql-connector-j 驱动)。一个实体 User、一个 UserRepo(extends JpaRepository,含派生查询、default 方法、自定义片段 UserRepoCustom);SQL 发出时的调用栈与事务状态用 Hibernate 的 StatementInspector 取得(只保留 Spring Data、AOP、事务相关的栈帧并去重);ddl-auto=create
    • 没有验证:@Query、Specification 等其他查询方式的拦截路径(见后面几篇);Spring Data 的 AOT 仓库(Boot 4 的 AOT 生成实现)对代理的影响;CGLIB 代理(proxyTargetClass);多数据源下的多个 Repository 基础包;方法上写 @Transactional 覆盖默认事务属性的情形;@NoRepositoryBean 与自定义基类(repositoryBaseClass)
    京ICP备2026067502号-1京公网安备11010502063487号