探索在 Java + Spring 中使用 Redis 是多么容易,并发现关系数据库的五个区别。
让我们从这个问题开始,“你如何使用 Redis?我相信大多数人将其用作服务的缓存。我希望你知道它可以做的不仅仅是这些。最近,我在一次会议上发表了一份报告,介绍了我们如何将部分数据移动到 Redis 并首先将请求发送到它。现在我想告诉你的不是我们如何应用它,而是关于在使用 Spring 及其抽象时,你可能不会立即注意到替换的事实。
让我们尝试编写一个小型的Spring应用程序,它将使用两个PostgreSQL和Redis数据库。我想指出的是,我们将在数据库中存储的不是某种平面对象,而是来自具有嵌套字段(内连接)的关系数据库的完整对象。为此,我们需要在 Redis 中安装一些插件,例如 RedisJSON 和 RediSearch。第一个允许我们以 JSON 格式存储我们的对象,第二个允许我们按对象的任何字段进行搜索,甚至是嵌套字段。
要使用关系数据库,我们将选择 Spring Data JPA。为了使用 Redis,我们将使用优秀的 Redis OM Spring 库,它允许您在抽象级别使用数据库。这是数据JPA的模拟。在后台,Redis OM Spring 拥有 Spring 和 Jedis 使用数据库所需的所有依赖项。我们不会详述细节,因为本文与此无关。
让我们编写代码
因此,让我们编写代码。假设我们需要编写一个调用到数据库的某个实体。在此实体中,我添加了其他对象,例如 、 和其他对象。"downtime""place""reason"
关系数据库的实体:
1 @Entity
2@Table(schema = "test", name = "downtime")
3 public class Downtime {
4
5 @Id
6 private String id;
7 private LocalDateTime beginDate;
8 private LocalDateTime endDate;
9 @ManyToOne(fetch = FetchType.EAGER)
10 @JoinColumn(name = "area")
11 private Place area;
12 @ManyToOne(fetch = FetchType.EAGER)
13 @JoinColumn(name = "cause")
14 private Cause cause;
15 ...
这段代码不需要注释。我们需要为Redis做同样的事情。
Redis的对象:
1
@Document2
public class DowntimeDoc {3
4
@Id5
@Indexed6
private String id;7
@Indexed8
private LocalDateTime beginDate;9
private LocalDateTime endDate;10
@Indexed11
private PlaceDoc area;12
@Indexed13
private CauseDoc cause;14
....
在这种情况下,我们使用 代替 。此注释指示我们的对象是一个实体。它将存储在数据库中的键“包路径 + 类名 + Idx”下。@Entity@Document
注释意味着它将被索引以供搜索。如果未指定此注释,则此字段将保存在数据库中,但搜索它将返回空结果。您可以根据需要添加此注释。数据库中已有的数据将以异步方式编制索引;新数据将同步编制索引。@Indexed
接下来,我们将创建一个存储库,它基本上可以从数据库中获取数据。
关系数据库的示例:
1
public interface DowntimeRepository extends JpaRepository<Downtime, String> {2
}
瑞迪斯的例子:
1
public interface DowntimeRedisRepository extends RedisDocumentRepository<DowntimeDoc, String> {2
}
不同之处在于,我们扩展了当前接口,这扩展了 Spring 的标准 CRUD 接口。RedisDocumentRepository
让我们添加一个方法来查找我们指定原因的第一次停机时间。
1
public interface DowntimeRepository extends JpaRepository<Downtime, String> {2
3
Downtime findFirstByCauseIdOrderByBeginDate(String causeId);4
}
雷迪斯也是如此:
1
public interface DowntimeRedisRepository extends RedisDocumentRepository<DowntimeDoc, String> {2
3
DowntimeDoc findTopByCause_IdOrderByBeginDateAsc(String causeId);4
}
正如您所注意到的,如果您通过抽象编写使用数据库的代码,那么差异几乎不明显。此外,Redis OM Spring 允许您使用注释自己编写查询,就像在 Spring Data JPA 中一样。@Query
下面是 HQL 查询的示例:
1
@Query("SELECT d FROM Downtime d" +2
" JOIN FETCH d.area " +3
" JOIN FETCH d.cause" +4
" JOIN FETCH d.fixer" +5
" JOIN FETCH d.area.level " +6
" WHERE d.area IN ?1 AND (d.beginDate BETWEEN ?2 AND ?3 OR d.cause IN ?4) ")7
List<Downtime> findAllByParams(List<Place> workPlace, LocalDateTime start, LocalDateTime end, List<Cause> causes);
与 Redis 相同:
1
@Query("(@area_id:{$areas} ) & (@beginDate:[$start $end] | @cause_id:{$causes})")2
Page<DowntimeDoc> findByParams(@Param("areas") List<String> areas,3
@Param("start") long start,4
@Param("end") long end,5
@Param("causes") List<String> causes, Pageable pageable);
对于 Redis 的情况,我们只需指定该部分的条件。没有必要指示需要附加哪些字段,因为它们始终是从数据库中提取的。但是,我们不能拉出所有字段,而是使用附加参数指定我们到底需要什么。您还可以指定排序、限制和偏移 - 顺便说一下,后者在 HQL 中是不可能的。在此示例中,我传递给该方法,它将在数据库级别工作,而不是将所有数据拉入服务,并在其中对其进行修剪(就像 Hibernate 一样)。“WHERE”“returnFields”Pageable
此外,Redis OM Spring 允许您使用 编写查询,这类似于 Stream API。EntityStream
下面是使用 .EntityStream
1
…2
entityStream3
.of(DowntimeDoc.class)4
.filter(DowntimeDoc$.AREA_ID.in(filter.getWorkPlace().toArray(String[]::new)))5
.filter(between + " | " + causes)6
.map(mapper::toEntity)7
.collect(Collectors.toList());
在此示例中,我使用一个使用元模型的筛选器,将参数作为字符串传递给第二个筛选器,以显示这两个选项都有效。你猜对了:接受一组中间操作,并在调用终端操作时执行这组。EntityStream
雷迪斯OM春天的细微差别
让我告诉你使用Redis OM Spring的一些细微差别:
- 您将无法使用 UUID 作为主键。您可以将其指定为字符串,它将被索引。但是在搜索时,您将需要 转义空格 :
@id
{2e5af82m\-02af\-553b\-7961\-168878aa521е}还有一件事:如果您搜索存储库,则没有任何效果,因为代码中有这样一个表达式可以删除所有屏幕:RedisDocumentRepository
String regex = "(\\$" + key + ")(\\W+|\\*|\\+)(.*)";因此,为了按此类字段进行搜索,您必须直接在 RediSearch 中编写查询。我有一个如何在演示项目中执行此操作的示例。
- 在搜索方法时,如果需要集合,则必须传递指示预期行的大小或在 ;否则,您将收到最多 10 条记录。
RedisDocumentRepositoryPageable@Query - 该方法仅支持一个参数进行排序。这是通过 编写查询来解决的。
FT.SEARCH (@Query)FT.AGGREGATE (@Aggregation)
以上列表并非详尽无遗。在使用这些库时,我发现了许多不同的东西,但这只是数据库实现的一个特殊性。最后,我没有在这篇文章中放关于 Redis 插件的信息,也没有谈论 Redis OM Spring 的所有功能;否则,这篇文章将是巨大的,不可读。
结论
我展示了目前,Redis 允许您存储具有大型嵌套的对象,并允许您搜索该对象的字段。如果你通过存储库中的抽象来处理数据,那么有些人可能看不出与Spring Data JPA有任何区别,特别是如果你使用一些简单的查询,如、、等,以及通过方法名称进行的查询。SavedeletefindAllBy
示例可以在 GitHub 上找到。
一切成功。




