Spring Data JPA 实现原理

Spring Data JPA 实现原理

在使用Spring Data JPA的时候,只需使用简单的接口定义,通过JPA约定好的命名格式书写对于的方法,就能够完成日常开发中的大部分数据库交互的场景,看下官方给出的例子:

@Repository
public interface SimpleUserRepository extends CrudRepository<User, Long> {

    /**
     * Find the user with the given username. This method will be translated into a query using the
     * {@link javax.persistence.NamedQuery} annotation at the {@link User} class.
     *
     * @param lastname
     * @return
     */
    User findByTheUsersName(String username);

    /**
     * Uses {@link Optional} as return and parameter type.
     *
     * @param username
     * @return
     */
    Optional<User> findByUsername(Optional<String> username);
    
    // ...
}

可以知道,这里使用的是接口,而Java中的接口要使用必须要有实现类,那么JPA时怎么做到的呢,想到这里基本就可以猜出来Spring Data JPA是通过动态代理来实现,但是具体是怎么操作的呢?

@EnableJpaRepositories说起

配置Spring Data JPA的时候通常就是通过@EnableJpaRepositories开启的,而通过注解就可以让整个JPA run起来,其中最重要的就是在@EnableJpaRepositories中import了JpaRepositoriesRegistrar,而这个配置就是入口所在。

先说明一下,Spring Data可不仅仅只有JPA的实现,还有其他各种各样的实现(如,JDBC,Redis,LDAP等),所以基本都是基于SPI(Service Provider Interface)解耦分层,所以大部分实现操作都是在spring-data-commons包中完成的。

JpaRepositoriesRegistrar

首先来看JpaRepositoriesRegistrar提供的功能,在spring-data-jpa包中,主要是用于告诉spring-data-commons抽象层的一些具体配置与解析:

  • getAnnotation(),提供JAP配置注解类,即@EnableJpaRepositories
  • getExtension(),提供JpaRepositoryConfigExtension,用于解析@EnableJpaRepositories
RepositoryBeanDefinitionRegistrarSupport

JpaRepositoriesRegistrar继承于RepositoryBeanDefinitionRegistrarSupport,它就是加载Repositories的关键:

  • registerBeanDefinitions,向Spring容器注册JpaRepositoryFactoryBean

大致步骤

所以,大致可以分为三大块,JPA加载的入口,注册JpaRepositoryFactoryBean和通过JpaRepositoryFactoryBean创建Repository

  • @EnableJpaRepositories import JpaRepositoriesRegistrar
  • JpaRepositoriesRegistrar.registerBeanDefinitions,注册JpaRepositoryFactoryBean
  • JpaRepositoryFactoryBean.afterPropertiesSet,创建Repository
JpaRepositoryFactoryBean如何创建Repository

其实,最关键的还是Repository是如何被创建出来的,首先看afterPropertiesSet

public void afterPropertiesSet() {

    this.factory = createRepositoryFactory();
    this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);
    this.factory.setNamedQueries(namedQueries);
    this.factory.setEvaluationContextProvider(
            evaluationContextProvider.orElseGet(() -> QueryMethodEvaluationContextProvider.DEFAULT));
    this.factory.setBeanClassLoader(classLoader);
    this.factory.setBeanFactory(beanFactory);

    if (publisher != null) {
        this.factory.addRepositoryProxyPostProcessor(new EventPublishingRepositoryProxyPostProcessor(publisher));
    }

    repositoryBaseClass.ifPresent(this.factory::setRepositoryBaseClass);

    RepositoryFragments customImplementationFragment = customImplementation //
            .map(RepositoryFragments::just) //
            .orElseGet(RepositoryFragments::empty);

    RepositoryFragments repositoryFragmentsToUse = this.repositoryFragments //
            .orElseGet(RepositoryFragments::empty) //
            .append(customImplementationFragment);

    this.repositoryMetadata = this.factory.getRepositoryMetadata(repositoryInterface);

    // Make sure the aggregate root type is present in the MappingContext (e.g. for auditing)
    this.mappingContext.ifPresent(it -> it.getPersistentEntity(repositoryMetadata.getDomainType()));
    
    //这里创建Repository    
    this.repository = Lazy.of(() -> this.factory.getRepository(repositoryInterface, repositoryFragmentsToUse));

    if (!lazyInit) {
        this.repository.get();
    }
}

具体来看RepositoryFactorySupport.getRepository()方法

public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {

    if (LOG.isDebugEnabled()) {
        LOG.debug("Initializing repository instance for {}…", repositoryInterface.getName());
    }

    Assert.notNull(repositoryInterface, "Repository interface must not be null!");
    Assert.notNull(fragments, "RepositoryFragments must not be null!");

    RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
    RepositoryComposition composition = getRepositoryComposition(metadata, fragments);
    //指定RepositoryBaseClass为SimpleJpaRepository
    RepositoryInformation information = getRepositoryInformation(metadata, composition);

    validate(information, composition);

    Object target = getTargetRepository(information);

    // Create proxy
    ProxyFactory result = new ProxyFactory();
    result.setTarget(target);
    result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);
    
    //Bean Validation Advice
    if (MethodInvocationValidator.supports(repositoryInterface)) {
        result.addAdvice(new MethodInvocationValidator());
    }
    //事务 Advice
    result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
    result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
    
    //RepositoryProxyPostProcessor处理
    postProcessors.forEach(processor -> processor.postProcess(result, information));
    
    //默认方法 Advice,背后实现为SimpleJpaRepository
    result.addAdvice(new DefaultMethodInvokingMethodInterceptor());

    ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory);
    //自定义方法 Advice
    result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory));

    composition = composition.append(RepositoryFragment.implemented(target));
    //自定义实现方法的 Advice
    result.addAdvice(new ImplementationMethodExecutionInterceptor(composition));
    
    //通过动态代理创建Repository
    T repository = (T) result.getProxy(classLoader);

    if (LOG.isDebugEnabled()) {
        LOG.debug("Finished creation of repository instance for {}.", repositoryInterface.getName());
    }

    return repository;
}

通过源码我们可以知道,Spring Data JPA是基于SimpleJpaRepository类的动态代理实现,通过AOP实现对自定义方法进行处理的

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,306评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,657评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,928评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,688评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,105评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,024评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,159评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,937评论 0 212
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,689评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,851评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,325评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,651评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,364评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,192评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,985评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,154评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,955评论 2 279

推荐阅读更多精彩内容