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

文章目录

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

最近这几年在做数据中台相关的项目,有个技术点就是要支持多款数据库,尤其是一些国产数据库, 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 开发数据库无关的应用功能。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[Python办公]Pandas创建透视表入门2

pivot_table 透视表在 Pandas 中是一个非常强大和灵活的工具&#xff0c;它支持许多高级功能&#xff0c;可以用于复杂的数据分析和报告生成。以下是一些更高级的用法和详细说明 1. 多级索引&#xff08;MultiIndex&#xff09; pivot_table 支持多级索引&#xff0c;这意味着…

稚晖君发布5款全能人形机器人,开源创新,全能应用

8月18日&#xff0c;智元机器人举行“智元远征 商用启航” 2024年度新品发布会&#xff0c;智元联合创始人彭志辉主持并发布了“远征”与“灵犀”两大系列共五款商用人形机器人新品——远征A2、远征A2-W、远征A2-Max、灵犀X1及灵犀X1-W&#xff0c;并展示了在机器人动力、感知、…

ArcGis在线地图插件Maponline(好用版)

ArcGis加载插件&#xff0c;可在线浏览谷歌地图、天地图、高德地图、必应地图等多种&#xff0c;包含街道、影像、标注地图等信息&#xff08;谷歌地图需自备上网手段&#xff09;&#xff0c;免费注册账号即可使用&#xff0c;可加载无水印底图。 与大地2000坐标无需配准直接使…

vue3旋转木马型轮播图,环型滚动

<template><div><div class"content"><div class"but1" click"rotateLeft">--向左</div><div class"ccc"><main id"main"><div class"haha" ref"haha"&g…

Mac 中安装内网穿透工具ngrok

ngrok 是什么&#xff1f; Ngrok 是一个网络工具&#xff0c;主要用于在网络中创建从公共互联网到私有或本地网络中运行的web服务的安全隧道。它充当了一个反向代理&#xff0c;允许外部用户通过公共可访问的URL访问位于防火墙或私有网络中的web应用程序或服务。Ngrok 特别适用…

Memcached深度解析:提升Web应用性能的内存缓存利器

一、引言 1. 介绍Web应用性能的重要性 在当今数字化时代&#xff0c;Web应用已成为企业与用户交互的主要渠道。用户对Web应用的期望越来越高&#xff0c;不仅要求功能丰富&#xff0c;还要求响应迅速、操作流畅。Web应用的性能直接影响到用户体验&#xff0c;进而关系到用户满…

Python Django功能强大的扩展库之channels使用详解

概要 随着实时 web 应用程序的兴起,传统的同步 web 框架已经无法满足高并发和实时通信的需求。Django Channels 是 Django 的一个扩展,旨在将 Django 从一个同步 HTTP 框架转变为一个支持 WebSockets、HTTP2 和其他协议的异步框架。它不仅能够处理传统的 HTTP 请求,还可以处…

mac清理软件哪个好用免费 MacBook电脑清理软件推荐 怎么清理mac

随着使用时间的增长&#xff0c;mac电脑会积累一些不必要的垃圾文件&#xff0c;这些文件会占用宝贵的存储空间&#xff0c;影响电脑的运行速度和稳定性。因此&#xff0c;定期清理mac电脑的垃圾文件是非常有必要的。市场上有许多优秀的Mac清理软件&#xff0c;包括一些出色的国…

MySQL零散拾遗(四)--- 使用聚合函数时需要注意的点点滴滴

聚合函数 聚合函数作用于一组数据&#xff0c;并对一组数据返回一个值。 常见的聚合函数&#xff1a;SUM()、MAX()、MIN()、AVG()、COUNT() 对COUNT()聚合函数的更深一层理解 COUNT函数的作用&#xff1a;计算指定字段在查询结果中出现的个数&#xff08;不包含NULL值&#…

《昇思25天学习打卡营第2天|张量》

张量其实就是矩阵&#xff0c;在python中主要是使用numpy这个库来操作&#xff0c;然后再mindspore中一般使用tensor对象作为张量的载体 张量如果维度只有二维的话可以简单理解为数据库中的表&#xff0c;但是如果是3维4维主要是在列表中增加列表项比如 【 【1&#xff0c;1】…

ROS2入门到精通—— 2-8 ROS2实战:机器人安全通过狭窄区域的方案

0 前言 室内机器人需要具备适应性和灵活性&#xff0c;以便在狭窄的空间中进行安全、高效的导航。本文提供一些让机器人在狭窄区域安全通过的思路&#xff0c;希望帮助读者根据实际开发适当调整和扩展 1 Voronoi图 Voronoi图&#xff1a;根据给定的一组“种子点”&#xff0…

嵌入式C++、ROS 、OpenCV、SLAM 算法和路径规划算法:自主导航的移动机器人流程设计(代码示例)

在当今科技迅速发展的背景下&#xff0c;嵌入式自主移动机器人以其广泛的应用前景和技术挑战吸引了越来越多的研究者和开发者。本文将详细介绍一个嵌入式自主移动机器人项目&#xff0c;涵盖其硬件与软件系统设计、代码实现及项目总结&#xff0c;并提供相关参考文献。 项目概…

手持式气象检测设备:便携科技,气象探测

一、手持式气象检测设备&#xff1a;小巧身躯&#xff0c;大能量 手持式气象检测设备&#xff0c;顾名思义&#xff0c;是一种可以手持操作的气象监测工具。它集成了温度、湿度、气压、风速风向等多种传感器&#xff0c;能够实时获取气象数据&#xff0c;并通过显示屏或手机APP…

实战解读:Llama Guard 3 Prompt Guard

前序研究&#xff1a;实战解读&#xff1a;Llama 3 安全性对抗分析 近日&#xff0c;腾讯朱雀实验室又针对 Llama 3.1 安全性做了进一步解读。 2024年7月23日晚&#xff0c;随着Llama3.1的发布&#xff0c;Meta正式提出了“Llama系统”的概念&#xff0c;通过系统级的安全组件对…

Sentinel限流规则详解

上一期教程讲解了 Sentinel 的快速入门&#xff1a;Sentinel快速入门&#xff0c;这一期主要讲述 Sentinel 的限流规则 簇点链路 簇点链路就是项目内的调用链路&#xff08;Controller -> Service -> Mapper&#xff09;&#xff0c;链路中被监控的每个接口就是一个资源…

Nginx 如何处理 WebSocket 连接?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; 文章目录 Nginx 如何处理 WebSocket 连接&#xff1f;一、WebSocket 连接简介二、Nginx 处理 WebSocket 连接的基本原理三、配置 Nginx 支持 WebSocket 连接四、Nginx 中的…

苍穹外卖(一)之环境搭建篇

Ngnix启动一闪而退 启动之前需要确保ngnix.exe的目录中没有中文字体&#xff0c;在conf目录下的nginx.conf文件查看ngnix的端口号&#xff0c;一般默认为80&#xff0c;若80端口被占用就会出现闪退现象。我们可以通过logs/error.log查看错误信息&#xff0c;错误信息如下&…

边界网关IPSEC VPN实验

拓扑&#xff1a; 实验要求&#xff1a;通过IPSEC VPN能够使PC2通过网络访问PC3 将整个路线分为三段 IPSEC配置在FW1和FW2上&#xff0c;在FW1与FW2之间建立隧道&#xff0c;能够传递IKE&#xff08;UDP500&#xff09;和ESP数据包&#xff0c;然后在FW1与PC2之间能够流通数据…

Linux网络:传输层协议TCP(二)三次挥手四次握手详解

目录 一、TCP的连接管理机制 1.1三次握手 1.2四次挥手 二、理解 TIME_WAIT 状态 2.1解决TIME_WAIT 状态引起的 bind 失败的方法 三、理解CLOSE_WAIT状态 一、TCP的连接管理机制 在正常情况下, TCP 要经过三次握手建立连接, 四次挥手断开连接 1.1三次握手 三次握手顾名思…

基于微信小程序+SpringBoot+Vue的资料分享系统(带1w+文档)

基于微信小程序SpringBootVue的资料分享系统(带1w文档) 基于微信小程序SpringBootVue的资料分享系统(带1w文档) 校园资料分享微信小程序可以实现论坛管理&#xff0c;教师管理&#xff0c;公告信息管理&#xff0c;文件信息管理&#xff0c;文件收藏管理等功能。该系统采用了Sp…