spring data:spring-data-jdbc spring-data-relational 源码解析 (2)

news2024/12/23 14:08:18

文章目录

  • 简介
      • 项目特点
      • 解决的主要问题
      • 关联的项目
      • 如何引入到项目工程中
        • 源码分析框架

最近这几年在做数据中台相关的项目,有个技术点就是要支持多款数据库,尤其是一些国产数据库, sql 语法多样,如何做统一就是一个我们面临的一个难题,在扒 JPA 相关的代码时,发现了一个可以参考的点,就是 spring-data-relational.

简介

  • spring-data-jdbc 一般通过 starter 引入:
    并随 hikariCP 连接池,一起引入到项目中。
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

在这里插入图片描述

Spring Data JDBC 的目的有别于 JPA 的就在于概念简单化:

If you load an entity, SQL statements get run. Once this is done, you have a completely loaded entity. No lazy loading or caching is done.
If you save an entity, it gets saved. If you do not, it does not. There is no dirty tracking and no session.
There is a simple model of how to map entities to tables. It probably only works for rather simple cases. If you do not like that, you should code your own strategy. Spring Data JDBC offers only very limited support for customizing the strategy with annotations.
如果你加载一个实体,SQL语句就会运行。完成此操作后,您将拥有一个完全加载的实体。不进行延迟加载或缓存。
如果你保存一个实体,它就会被保存。如果你不这样做,它就不会。没有脏跟踪,也没有会话。
有一个如何将实体映射到表的简单模型。它可能只适用于相当简单的情况。如果你不喜欢这样,你应该编写自己的策略。Spring Data JDBC对使用注释自定义策略的支持非常有限。

简单来说就是抛弃了 JPA 后面 hibernate 所隐藏的复杂部分,让我们所用即所得。没有复杂的会话、跟踪等等。

  • Spring-data-relational 是 spring-data-jdbc 的依赖部分,主要面向关系型数据库的 DML.
    在这里插入图片描述
    对于 spring-data 的路线,多是以 DML 为主,配合 liquid、flyway 等数据库迁移工具来保证,对于 JPA 以 hibernate 为根基的 auto-ddl 并不在这里适用。

项目特点

  1. 高度抽象:Spring Data Relational(通过Spring Data JPA等模块)提供了对关系数据库操作的高度抽象,使得开发者可以通过简单的接口和方法名约定来执行复杂的数据库操作。
  2. 一致性API:无论底层使用哪种关系数据库,Spring Data都提供了一致的编程模型,降低了数据库迁移的复杂性。
  3. 强大的查询能力:支持基于方法名的查询推导、Criteria API、JPQL(Java Persistence Query Language)等多种查询方式,满足复杂的查询需求。
  4. 集成Spring框架:无缝集成Spring框架,利用Spring的依赖注入(DI)和面向切面编程(AOP)等特性,简化开发。
  5. 丰富的生态系统:Spring Data是Spring生态系统的一部分,与Spring Boot、Spring Cloud等项目紧密集成,支持快速开发和部署。

解决的主要问题

  • 简化数据库访问层开发:通过提供一套标准的接口和抽象,减少了数据库访问层(DAO层)的模板代码,使开发者能够更专注于业务逻辑的实现。
  • 提高开发效率:通过自动化的查询生成和强大的查询能力,减少了手动编写SQL语句的需求,提高了开发效率。
  • 增强系统的可维护性和可扩展性:通过一致的编程模型和灵活的扩展机制,使得系统在面对数据库变更或扩展时能够更容易地适应。

关联的项目

  • Spring Data JPA:Spring Data的一个子项目,专门用于简化JPA(Java Persistence API)的使用。
  • Spring Boot:提供了Spring Data的自动配置功能,使得在Spring Boot项目中集成Spring Data变得更加简单。
  • Hibernate:作为JPA的一个实现,经常与Spring Data JPA一起使用,提供ORM(对象关系映射)功能。
  • Spring Framework:Spring Data是Spring框架的一部分,依赖于Spring的核心功能,如依赖注入和AOP。

如何引入到项目工程中

  1. 添加依赖:在项目的pom.xml( 中添加Spring Data JDBC starter的依赖。
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
  1. 配置数据源:在application.propertiesapplication.yml中配置数据库连接信息。

  2. 定义实体类:根据数据库表结构定义相应的实体类,并不需要像 JPA 注解进行标注,定义简单 pojo 即可。
    在这里插入图片描述

  3. 创建Repository接口:继承Spring Data的JpaRepositoryCrudRepository接口,定义数据访问方法。

  4. 使用Repository:在业务层或服务层中注入Repository接口,通过调用其方法来执行数据库操作。

源码分析框架

下面我们以一个简单的自定义接口实现来看源码层是如何对一个查询处理并生成为 SQL 的。

  1. 接口层
    对于 Repository 接口来说,本身不含任何实现,Spring 体系通过动态代理进行方法调用,采用反射的形式。
interface CategoryRepository extends CrudRepository<Category, Long>, WithInsert<Category> {
    List<Category> findByName(String name);
}

CategoryRepository 在自定义查询方法时,代理给了 SimpleJdbcRepository ,该 Repository 提供了一般 JDBC 查询所需要的方法。如果不需要自定义查询方法,使用 Repository 已定义好的就采用该类代理实现。
在这里插入图片描述
在这里要另外需要注意的就是

List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

这个 chain 提供了非常灵活的扩展能力:
在这里插入图片描述

  1. 实现层:深入JpaRepository的实现类(通常是Spring Data 内部实现,非直接暴露给开发者),了解查询方法的解析和执行过程。
    调用方法有很深的调用栈:
    在这里插入图片描述
    在这里插入图片描述
    PartTree 就是进行方法名解析的类,并将方法名转成 Criteria 的元数据。
    在这里插入图片描述
    最终,就到了这里,
   protected ParametrizedQuery complete(@Nullable Criteria criteria, Sort sort) {
        RelationalPersistentEntity<?> entity = this.entityMetadata.getTableEntity();
        Table table = Table.create(this.entityMetadata.getTableName());
        MapSqlParameterSource parameterSource = new MapSqlParameterSource();
        SelectBuilder.SelectLimitOffset limitOffsetBuilder = this.createSelectClause(entity, table);
        SelectBuilder.SelectWhere whereBuilder = this.applyLimitAndOffset(limitOffsetBuilder);
        SelectBuilder.SelectOrdered selectOrderBuilder = this.applyCriteria(criteria, entity, table, parameterSource, whereBuilder);
        selectOrderBuilder = this.applyOrderBy(sort, entity, table, selectOrderBuilder);
        SelectBuilder.BuildSelect completedBuildSelect = selectOrderBuilder;
        if (this.lockMode.isPresent()) {
            completedBuildSelect = selectOrderBuilder.lock(((Lock)this.lockMode.get()).value());
        }

        Select select = ((SelectBuilder.BuildSelect)completedBuildSelect).build();
        // 生成 sql
        String sql = SqlRenderer.create(this.renderContextFactory.createRenderContext()).render(select);
        return new ParametrizedQuery(sql, parameterSource);
    }

render 方法在渲染 sql 的时候调用 spring-data-relational 的 sql 部分,使用visitor 模式(前面几个系列 spotbugs,p3c-pmd 都有涉猎此设计模式,在进行操作和数据分离方面有非常广泛的应用)。

  1. 扩展点:研究如何通过自定义查询、使用@Query注解等方式来扩展Spring Data 的功能。
    @Query("SELECT * FROM Category WHERE description = :description")
    List<Category> findByDD(String description);

对于这种 @Query 注解进行处理的方法与上面的 PartTree 转成 criteria 的元数据不同,由用户自己定义 sql ,就避免的进行 SQL 创建的过程。所以很简单地从注解中取出:

	@Nullable
    private <T> T getMergedAnnotationAttribute(String attribute) {
        Query queryAnnotation = (Query)AnnotatedElementUtils.findMergedAnnotation(this.method, Query.class);
        return AnnotationUtils.getValue(queryAnnotation, attribute);
    }
------------------
  public Object execute(Object[] objects) {
        RelationalParameterAccessor accessor = new RelationalParametersParameterAccessor(this.getQueryMethod(), objects);
        ResultProcessor processor = this.getQueryMethod().getResultProcessor().withDynamicProjection(accessor);
        JdbcQueryExecution.ResultProcessingConverter converter = new JdbcQueryExecution.ResultProcessingConverter(processor, this.converter.getMappingContext(), this.converter.getEntityInstantiators());
        RowMapper<Object> rowMapper = this.determineRowMapper(this.rowMapperFactory.create(this.resolveTypeToRead(processor)), converter, accessor.findDynamicProjection() != null);
        JdbcQueryExecution<?> queryExecution = this.getQueryExecution(this.queryMethod, this.determineResultSetExtractor(rowMapper), rowMapper);
        MapSqlParameterSource parameterMap = this.bindParameters(accessor);
        String query = this.determineQuery(); // 这里
        if (ObjectUtils.isEmpty(query)) {
            throw new IllegalStateException(String.format("No query specified on %s", this.queryMethod.getName()));
        } else {
            return queryExecution.execute(this.processSpelExpressions(objects, parameterMap, query), parameterMap);
        }
    }

反射的方法执行也从 PartTreeJdbcQuery 切换到 StringBasedJdbcQuery
在这里插入图片描述
在这里插入图片描述
而这个 queries 的初始化要在更早的 IOC 过程:
org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#afterPropertiesSet
org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepository(java.lang.Class, org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments)

public QueryExecutorMethodInterceptor(RepositoryInformation repositoryInformation, ProjectionFactory projectionFactory, Optional<QueryLookupStrategy> queryLookupStrategy, NamedQueries namedQueries, List<QueryCreationListener<?>> queryPostProcessors, List<RepositoryMethodInvocationListener> methodInvocationListeners) {
        this.repositoryInformation = repositoryInformation;
        this.namedQueries = namedQueries;
        this.queryPostProcessors = queryPostProcessors;
        this.invocationMulticaster = (RepositoryInvocationMulticaster)(methodInvocationListeners.isEmpty() ? NoOpRepositoryInvocationMulticaster.INSTANCE : new RepositoryInvocationMulticaster.DefaultRepositoryInvocationMulticaster(methodInvocationListeners));
        this.resultHandler = new QueryExecutionResultHandler(RepositoryFactorySupport.CONVERSION_SERVICE);
        if (!queryLookupStrategy.isPresent() && repositoryInformation.hasQueryMethods()) {
            throw new IllegalStateException("You have defined query methods in the repository but do not have any query lookup strategy defined. The infrastructure apparently does not support query methods");
        } else {
            this.queries = (Map)queryLookupStrategy.map((it) -> {
                return this.mapMethodsToQuery(repositoryInformation, it, projectionFactory);
            }).orElse(Collections.emptyMap());
        }
    }
  1. relational 渲染 sql
    扒这段代码地原因在文章最开始也讲了,如何规避多数据库适配地场景。手上的项目要支持9款数据库: oracle,ms,mysql,mariadib,pg,highgo,oscar,kingbase,dameng 。各个数据库地语法也不尽相同。在系统库支持上,JPA是一个很好地解决方式,目前项目也是采用此技术栈。但是还有一点问题就是数据中台项目相关的功能,如面向数仓的数据查询连接器,connector 不仅仅是连接池连接的提供,还得有语法屏障的破除的能力,不然就是 sql 满天飞。基于此目的,我们查询了该包的源码实现:

relational 的代码结构如下:
在这里插入图片描述
有下面的几个包:

  • conversion
  • dialect :包括几个常见数据库的 dialect,如 oracle,mysql,mssql,pg 等,有别于 hibernate 的 dialect。
  • mapping:
  • query:
  • sql:渲染成sql语句

以最简单的分页查询为例:

 @Test
    public void test_sqlrender_field2() {
        List<Dialect> list = Arrays.asList(MySqlDialect.INSTANCE, PostgresDialect.INSTANCE, OracleDialect.INSTANCE,
                H2Dialect.INSTANCE, Db2Dialect.INSTANCE, SqlServerDialect.INSTANCE);
        for (Dialect dialect : list) {
            RenderContextFactory factory = new RenderContextFactory(dialect);
            RenderContext renderContext = factory.createRenderContext();
            BaseSelectBuilder baseSelectBuilder = new BaseSelectBuilder();
            baseSelectBuilder.from("tableTest");// 表名
            baseSelectBuilder.select(Column.create("id", tableTest.getTable()),
            Column.create("name", tableTest.getTable()),
            Column.create("age", tableTest.getTable()),
            Column.create("address", tableTest.getTable())); // 字段名
            baseSelectBuilder.limitOffset(10, 0); // 分页
            String s = SqlRenderer.create(renderContext).render(baseSelectBuilder.build());
            System.out.println(dialect.getClass().getName()+" "+s);
        }

    }

在这里插入图片描述
思路就打开了,我们可以利用 relational 提供的 api 开发数据库无关的应用功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2044714.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

采购oled全透明显示屏需要注意什么

采购OLED全透明显示屏时&#xff0c;需要注意以下几个方面以确保选择到符合需求的高品质产品&#xff1a; 一、明确需求与预算 应用场景&#xff1a;首先明确OLED全透明显示屏将用于何种场景&#xff0c;如零售展示、展览展示、智能家居、车载显示等&#xff0c;以便选择合适的…

前端新手必看:掌握CSS样式优先级和!important,让你的样式不再冲突!(CSS优先级和!important的用法)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 CSS样式优先级 📒📝 CSS样式优先级基础📝 选择器的特异性(Specificity)📝 源(Source)顺序🚀 !important的使用📝 使用!important的场景📝 如何使用!important🚧 注意事项与最佳实践📝 替代方法⚓️ 相关链…

【最新CUDA安装教程2024】手把手教你安装/更新cuda(2024版)

【最新CUDA安装教程2024】手把手教你安装/更新cuda(2024版) 需要更新的看这里(如果单纯安装的话请跳过)查看电脑的cuda版本与其支持的cuda版本删除旧版本需要安装的看这里进入官网cudnn安装测试一下配置一下设备:Legion Y9000P IAH7H系统:Win11CPU:12th Gen Intel Core™…

Android 实现动态换行显示的 TextView 列表

在开发 Android 应用程序时&#xff0c;我们经常需要在标题栏中显示多个 TextView&#xff0c;而这些 TextView 的内容长度可能不一致。如果一行内容过长&#xff0c;我们希望它们能自动换行&#xff1b;如果一行占不满屏幕宽度&#xff0c;则保持在一行内。本文将带我们一步步…

springBoot+ druid配置多数据源

springBoot druid配置多数据源 1.在yml加&#xff1a; spring:#1.JDBC数据源datasource:druid:first:username: PYpassword: ral2024url: jdbc:mysql://localhost:3306/mysql?serverTimezoneUTC&characterEncodingutf8&useUnicodetrue&useSSLfalsedriver-class-n…

vue通过iframe预览 pdf、word、xls、ppt、txt文件

vue通过iframe预览 pdf、word、xls、ppt、txt文件 iframe中预览只能直接打开pdf文件&#xff0c;其他文件需要通过office365预览。 效果&#xff1a; 组件代码&#xff1a; <!--* fileName: 文件预览-FileView.vue* date: yanghaoxing-2024-08-16 09:32:24 !--> <…

【飞桨AI实战】PaddleNLP大模型指令微调,从0打造你的专属家常菜谱管家

1.项目背景 家庭烹饪作为日常生活的重要组成部分&#xff0c;不仅关乎健康&#xff0c;也是家庭情感交流的重要方式。 相信很多小伙伴在烹饪时也会困惑&#xff1a;不知道如何选择合适的食材和菜谱&#xff0c;或者缺乏灵感来创造新的菜品。 最近看到一本《家庭实用菜谱大全…

win10配置pytorch环境+CUDA安装

步骤 1&#xff1a;更新显卡驱动 参考&#xff1a;如何在windows上 安装&更新 显卡的驱动_显卡驱动series和notebook-CSDN博客 进入英伟达官网&#xff1a;下载 NVIDIA 官方驱动 | NVIDIA 根据GPU类型选择对应的NVIDIA驱动&#xff0c;选好后点击“查找” 选择下载 GeFo…

记录|C#中panel与panel重叠显示问题

目录 前言一、问题在现二、方案解决三、效果展示更新时间 前言 参考文章&#xff1a; C#中winform中panel重叠无法显示问题的解决 一、问题在现 问题是我实现上图中效果&#xff0c;但是panel和panel的交界处放入其他组件后&#xff0c;会被部分覆盖【如下图示】 二、方案解决…

在线互动学习网站设计

TOC springboot249在线互动学习网站设计 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范…

音频矩阵主要功能及常规路数配置有哪些

音频矩阵&#xff0c;又称AUDIO矩阵或音频矩阵切换器&#xff0c;是一种用于管理和控制多个音频信号的设备。它具备多种功能&#xff0c;主要可以概括为以下几个方面&#xff1a; 一、主要功能 信号切换&#xff1a; AUDIO128128音频矩阵能够将多个音频源的信号输入到设备中&…

汽车EDI:法雷奥Valeo EDI项目案例

Valeo是一家总部位于法国的汽车零部件供应商。它专注于设计、生产、和销售各种创新产品和系统&#xff0c;以提高汽车的能效和减少排放。其业务主要分为舒适与驾驶辅助系统、动力总成系统、热系统以及可视系统。 本文将从业务的角度出发&#xff0c;带领大家了解供应商H公司在对…

投资中国硬科技,沙特钱多人不傻

"不止是商业的游戏" 作者 | 田 甜 编辑 | 卢旭成 “中东热”有可能热过了头。 在中东淘金者口中&#xff0c;流传着这样一句话&#xff1a;世界看中东&#xff0c;中东看沙特。过去一年里&#xff0c;中国的GP与创业者们组团赴沙特&#xff0c;目的无非两个——…

C语言 【自定义类型——结构体】(详细)

目录 1、结构体的定义 2、创建与初始化结构体变量 2.0 举例 2.1 结构体的特殊声明 2.1.0 匿名结构体 2.1.1 结构体的自引用 3、结构体内存对齐 3.0 为什么要内存对齐 3.1 对齐规则 3.2 如何修改默认对齐数 4、结构体传参 5、结构体中的位段使用 5.0 什么是位段&…

printf、fprintf、sprintf的使用和区别

printf、fprintf、sprintf的使用和区别 1、sprintf 函数 sprintf函数用于将格式化的数据写入字符串&#xff0c;其原型为&#xff1a; #include <stdio.h>/* *描述&#xff1a;将格式化的数据写入字符串 * *参数&#xff1a; * [out] str&#xff1a; 输出缓冲区…

Python聊天机器人-NoneBot2入门(2024新版)

1. NoneBot2 安装与使用 NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架&#xff08;下称 NoneBot&#xff09;&#xff0c;它基于 Python 的类型注解和异步优先特性&#xff08;兼容同步&#xff09;&#xff0c;能够为你的需求实现提供便捷灵活的支持。同时&…

煤炭检测实验室信息管理系统LIMS

在煤矿行业&#xff0c;实验室作为质量控制与技术创新的核心部门&#xff0c;其管理效率与数据准确性直接关系到企业的生产安全与经济效益。随着信息技术的飞速发展&#xff0c;实验室信息管理系统(LIMS)在煤矿行业的应用日益广泛&#xff0c;成为提升实验室管理水平、优化检测…

【动态规划,dp】P1044[NOIP2003 普及组] 栈 题解

题意 给定一个 n ( 1 ≤ n ≤ 18 ) n(1 \leq n \leq 18) n(1≤n≤18)&#xff0c;表示一个操作数序列&#xff0c; 1 , 2 , … , n 1,2,…,n 1,2,…,n&#xff08;图示为 1 到 3 的情况&#xff09;&#xff0c;栈 A 的深度大于 n n n。 现在可以进行两种操作&#xff0c; …

如何选出高品质 SD 存储卡 —— 具备高耐用度且防水防震抗冲击

SD卡&#xff08;Secure Digital Memory Card&#xff09;是一种广泛使用的存储器件&#xff0c;因其快速的数据传输速度、可热插拔的特性以及较大的存储容量&#xff0c;广泛应用于各种场景&#xff0c;例如在便携式设备如智能手机、平板电脑、运动相机等&#xff0c;用于存储…

录屏为什么录制不进去,没有声音?屏幕录制中的声音问题及解决方案

在数字时代&#xff0c;屏幕录制已成为我们日常工作和生活中不可或缺的一部分。无论是制作教学视频、记录在线课程&#xff0c;还是捕捉游戏精彩瞬间&#xff0c;一个好的屏幕录制软件都能让我们的工作更加高效&#xff0c;生活更加丰富。然而&#xff0c;许多用户在使用屏幕录…