Redis可以作为关系型数据库使用吗?

news/2025/2/12 2:00:27/

        探索在 Java + Spring 中使用 Redis 是多么容易,并找到与关系数据库的五个不同之处。

        让我们从“你如何使用Redis?”这个问题开始。我确信大多数人都将其用作服务的缓存。我希望您知道它可以做的不仅仅是这些。最近,我在一次会议上发表了一份报告,介绍了我们如何将部分数据移动到 Redis 以及如何将请求首先传送到 Redis。现在我想告诉您的不是我们如何应用它,而是关于这样一个事实:在使用 Spring 及其抽象时,您可能不会立即注意到这种替换。
        让我们尝试编写一个小型 Spring 应用程序,它将使用两个PostgreSQL和 Redis 数据库。我想指出的是,我们将在数据库中存储的不是某种平面对象,而是来自具有嵌套字段(内连接)的关系数据库的成熟对象。为此,我们需要在 Redis 中安装插件,例如 RedisJSON 和RediSearch。第一个允许我们以JSON格式存储对象,第二个允许我们通过对象的任何字段进行搜索,甚至是嵌套字段。
        要使用关系数据库,我们将选择Spring Data JPA。为了使用 Redis,我们将使用优秀的Redis OM Spring库,它允许您在抽象级别使用数据库。这是 Data JPA 的模拟。在底层,Redis OM Spring 具有 Spring 和 Jedis 与数据库配合使用的所有必要依赖项。我们不会详细讨论细节,因为本文不是关于这个的。

让我们编写代码

那么我们来写代码吧。假设我们需要编写一个调用"downtime"数据库的实体。在这个实体中,我添加了其他对象,例如"place""reason"和其他对象。

关系数据库的实体:

@Entity
@Table(schema = "test", name = "downtime")
public class Downtime {@Idprivate String id;private LocalDateTime beginDate;private LocalDateTime endDate;@ManyToOne(fetch = FetchType.EAGER)@JoinColumn(name = "area")private Place area;@ManyToOne(fetch = FetchType.EAGER)@JoinColumn(name = "cause")private Cause cause;...

这段代码不需要注释。我们需要对 Redis 做同样的事情。

 Redis 对象:

@Document
public class DowntimeDoc {@Id@Indexedprivate String id;@Indexedprivate LocalDateTime beginDate;private LocalDateTime endDate;@Indexedprivate PlaceDoc area;@Indexedprivate CauseDoc cause;....

在本例中,@Entity我们使用来代替@Document。这个注释表明我们的对象是一个实体。它将存储在数据库中,键为“包路径+类名+Idx”。

@Indexed注释意味着它将被索引以供搜索。如果不指定该注解,则该字段将保存在数据库中,但搜索该字段将返回空结果。您可以根据需要添加此注释。数据库中已有的数据将被异步索引;新数据将同步索引。 

接下来,我们将创建一个存储库,它的主要作用是从数据库中获取数据。

关系数据库的示例:

public interface DowntimeRepository extends JpaRepository<Downtime, String> {
}

Redis 示例:

public interface DowntimeRedisRepository extends RedisDocumentRepository<DowntimeDoc, String> {
}

不同之处在于我们扩展了当前的接口RedisDocumentRepository,它扩展了 Spring 的标准 CRUD 接口。

让我们添加一个方法来查找由于我们指定的原因而导致的第一次停机。

public interface DowntimeRepository extends JpaRepository<Downtime, String> {Downtime findFirstByCauseIdOrderByBeginDate(String causeId);
}

Redis 也是如此:

public interface DowntimeRedisRepository extends RedisDocumentRepository<DowntimeDoc, String> {DowntimeDoc findTopByCause_IdOrderByBeginDateAsc(String causeId);
}

正如您所注意到的,如果您通过抽象编写使用数据库的代码,那么差异几乎不明显。此外,Redis OM Spring 允许您使用@Query注释自己编写查询,就像在 Spring Data JPA 中一样。

以下是 HQL 查询的示例:

@Query("SELECT d FROM Downtime d" +" JOIN FETCH d.area " +" JOIN FETCH d.cause" +" JOIN FETCH d.fixer" +" JOIN FETCH d.area.level " +" WHERE d.area IN ?1 AND (d.beginDate BETWEEN ?2 AND ?3 OR d.cause IN ?4) ")
List<Downtime> findAllByParams(List<Place> workPlace, LocalDateTime start, LocalDateTime end, List<Cause> causes);

Redis 也一样:

@Query("(@area_id:{$areas} ) & (@beginDate:[$start $end] | @cause_id:{$causes})")
Page<DowntimeDoc> findByParams(@Param("areas") List<String> areas,@Param("start") long start,@Param("end") long end,@Param("causes") List<String> causes, Pageable pageable);

对于 Redis,我们只需指定该“WHERE”部分的条件即可。没有必要指出需要附加哪些字段,因为它们总是从数据库中提取。但是,我们无法提取所有字段,而是使用附加“returnFields”参数指定我们到底需要什么。您还可以指定排序、限制和偏移量 - 顺便说一下,后者在 HQL 中是不可能的。在此示例中,我传递Pageable给该方法,它将在数据库级别工作,而不是将所有数据提取到服务中,并在其中修剪它(就像 Hibernate 的情况一样)。

此外,Redis OM Spring 允许您使用 编写查询EntityStream,这类似于 Stream API。

以下是使用上述查询的示例EntityStream

…
entityStream.of(DowntimeDoc.class).filter(DowntimeDoc$.AREA_ID.in(filter.getWorkPlace().toArray(String[]::new))).filter(between + " | " + causes).map(mapper::toEntity).collect(Collectors.toList());

在此示例中,我使用一个使用元模型的过滤器,将参数作为字符串传递给第二个过滤器,以显示这两个选项均有效。您猜对了:EntityStream接受一组中间操作并在调用终端操作时执行这组操作。

Redis OM Spring 的细微差别

让我告诉您使用 Redis OM Spring 的一些细微差别:

  • 您将无法使用 UUID 作为主键。您可以将其指定为字符串,它将被索引。但是在搜索时,您需要转义空格@id
{2e5af82m\-02af\-553b\-7961\-168878aa521е}

还有一件事:如果您在RedisDocumentRepository存储库中搜索,则没有任何效果,因为代码中有这样一个表达式将删除所有屏幕:

String regex = "(\\$" + key + ")(\\W+|\\*|\\+)(.*)";

因此,为了按这些字段进行搜索,您必须直接在 RediSearch 中编写查询。我有一个如何在演示项目中执行此操作的示例。

  • 在搜索RedisDocumentRepository方法时,如果您期望一个集合,则必须传递一个Pageable指示期望行的大小或在@Query;中指定大小的参数。否则,您最多将收到 10 条记录。
  • FT.SEARCH (@Query)方法仅支持一个参数进行排序。这是通过编写查询来解决的FT.AGGREGATE (@Aggregation)

上述列表并不详尽。在使用这些库时,我发现了许多不同的东西,但这只是数据库实现的特殊性。最后,我没有在本文中放入有关 Redis 插件的信息,也没有谈论 Redis OM Spring 的所有功能;不然这篇文章会很大,不可读。

结论

我展示了目前,Redis 允许您存储具有大嵌套的对象,并允许您搜索该对象的字段。如果您通过存储库中的抽象来处理数据,那么有些人可能看不出与 Spring Data JPA 有任何区别,特别是如果您使用一些简单的查询,例如 、 、SavedeletefindAllBy以及通过方法名称进行查询。


http://www.ppmy.cn/news/1041390.html

相关文章

JAVA编程学习笔记

常用代码、特定函数、复杂概念、特定功能……在学习编程的过程中你会记录下哪些内容&#xff1f;快来分享你的笔记&#xff0c;一起切磋进步吧&#xff01; 一、常用代码 在java编程中常用需要储备的就是工具类。包括封装的时间工具类。http工具类&#xff0c;加解密工具类&am…

k8s v1.27.4二进制部署记录

记录二进制部署过程 #!/bin/bash#升级内核 update_kernel() {rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.orgyum -y install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpmyum --disablerepo"*" --enablerepo"elrepo-kernel&q…

在linux中,使用sh文件脚本启动jar项目

使用方法 sh 执行脚本.sh [start|stop|restart|status]sh文件内容 APP_NAMEXXXX.jar#使用说明&#xff0c;用来提示输入参数 usage() { echo "Usage: sh 执行脚本.sh [start|stop|restart|status]" exit 1 }#检查程序是否在运行 is_exist(){ pidps -ef|grep $APP_N…

【数学建模】-- 数学规划模型

概述&#xff1a; 什么是数学规划&#xff1f; 数学建模中的数学规划是指利用数学方法和技巧对问题进行数学建模&#xff0c;并通过数学规划模型求解最优解的过程。数学规划是一种数学优化方法&#xff0c;旨在找到使目标函数达到最大值或最小值的变量取值&#xff0c;同时满足…

LangChain源码逐行解密之系统(二)

LangChain源码逐行解密之系统 20.2 serapi.py源码逐行剖析 我们可以看一下Google查询的例子,在LangChain中有多种实现的方式。 如图20-5所示,在utilities的serpapi.py代码文件中实现了SerpAPIWrapper。 图20- 5 utilities的serpapi.py的SerpAPIWrapper 在langchain目录的se…

PoseiSwap 更新质押系统,并将在 8 月18 日开启“Trident ”快照

自 DeFi Summer 后&#xff0c;DeFi 设施整体的形态并未发生本质的变化&#xff0c;我们看到 DeFi 应用仍旧不具向外长期捕获价值、用户的能力&#xff0c;老旧叙事导致 DeFi 赛道整体的发展停滞不前。伴随着行业进入到下行周期&#xff0c;DeFi 赛道的资金、用户不断出逃&…

8.深浅拷贝和异常处理

开发中我们经常需要复制一个对象。如果直接用赋值会有下面问题: 8.1 浅拷贝 首先浅拷贝和深拷贝只针对引用类型 浅拷贝&#xff1a;拷贝的是地址 常见方法: 1.拷贝对象&#xff1a;Object.assgin() / 展开运算符{…obj} 拷贝对象 2.拷贝数组&#xff1a;Array.prototype.con…

构建LLM应用程序时需要了解的5件事

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可二次编辑的3D应用场景 1.幻觉 使用LLM时应注意的主要方面之一是幻觉。在LLM的背景下&#xff0c;幻觉是指产生不真实的&#xff0c;不正确的&#xff0c;无意义的信息。LLM非常有创意&#xff0c;它们可以用于不同的领域&am…