在上一篇文章 万字长文:从缓存文章聊聊Redis OM是怎么个事儿 · 上 中,我们详细介绍了 Redis OM 的基础概念,并展示了如何使用 Schema 和 Repository 进行缓存操作。今天,我们将深入探讨 Redis OM 的搜索、排序、数据结构以及原生查询等功能,并利用这些知识来封装文章相关的缓存与搜索功能。
5. 搜索与排序
Redis OM 提供了强大的搜索和排序功能,使得我们可以轻松地对数据进行全文搜索和排序操作。
5.1 使用 Redis OM 进行全文搜索
Redis OM 基于 RediSearch 提供全文搜索功能。我们可以使用 search 方法在指定字段中进行全文搜索。
示例代码
假设我们有一个定义好的文章 Schema:
import { Schema, Repository } from 'redis-om';
// 定义文章的 Schema
const articleSchema = new Schema('article', {
title: { type: 'text' },
author: { type: 'string' },
publishDate: { type: 'date' },
content: { type: 'text' },
tags: { type: 'string[]' }
});
// 创建文章的 Repository
const articleRepository = new Repository(articleSchema, redis);
await articleRepository.createIndex();
// 创建一些示例文章
const articles = [
{
title: 'Redis OM 的强大功能',
author: '张三',
publishDate: new Date('2023-01-01'),
content: 'Redis OM 提供了许多强大的功能,包括全文搜索和排序。',
tags: ['Redis', '搜索']
},
{
title: '使用 Redis 进行数据存储',
author: '李四',
publishDate: new Date('2023-02-15'),
content: 'Redis 是一个高性能的键值数据库,适用于多种数据存储场景。',
tags: ['Redis', '数据库']
},
{
title: 'Redis 在实际应用中的案例',
author: '王五',
publishDate: new Date('2023-03-10'),
content: '本文介绍了 Redis 在实际应用中的一些成功案例。',
tags: ['Redis', '案例']
}
];
// 将文章存储到 Redis
for (const article of articles) {
await articleRepository.save(article);
}
console.log('示例文章已存储到 Redis');
我们可以使用 search 方法在 title 和 content 字段中进行全文搜索:
// 搜索标题和正文
const searchArticles = async (query: string | number | boolean) => {
const articles = await articleRepository
.search()
.where("title")
.matches(query)
.or("content")
.matches(query)
.return.all();
return articles;
};
const query = "Redis";
const articleList = await searchArticles(query);
articleList.forEach((article) => {
console.log(`标题: ${article.title}, 作者: ${article.author},正文:${article.content}`);
});
在这段代码中,我们定义了一个 searchArticles 函数,使用 articleRepository.search 方法在 title 和 content 字段中进行全文搜索,并返回所有匹配的文章。
运行结果
npm run dev
> redisearch@1.0.0 dev
> ts-node src/index.ts
redis connected
示例文章已存储到 Redis
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Understanding Redis OM, 作者: John Doe,正文:Redis OM for Node.js simplifies data modeling...
标题: Redis OM 的强大功能, 作者: 张三,正文:Redis OM 提供了许多强大的功能,包括全文搜索和排序。
标题: 使用 Redis 进行数据存储, 作者: 李四,正文:Redis 是一个高性能的键值数据库,适用于多种数据存储场景。
标题: Redis 在实际应用中的案例, 作者: 王五,正文:本文介绍了 Redis 在实际应用中的一些成功案例。
从结果可以看到,最后几条是我们刚刚缓存的。前面几条都是我们昨天文章中的,接下来我做一下排序
5.2 对搜索结果进行排序
Redis OM 允许我们对搜索结果进行排序。我们可以使用 sortAscending 或 sortDescending 方法对指定字段进行升序或降序排序。这使得我们能够根据需要对查询结果进行更细致的控制和优化。
示例代码
我们可以对搜索结果按 publishDate 字段进行排序,以下是示例代码:
/**
* 根据查询条件搜索文章并按发布日期排序
* @param query - 搜索关键词
* @returns 排序后的文章列表
*/
const searchAndSortArticles = async (query: string | number | boolean) => {
// 执行搜索并按发布日期升序排序
const articles = await articleRepository.search()
.where('title').matches(query)
.or('content').matches(query)
.sortAscending('publishDate')
.return.all();
return articles;
};
// 示例查询
const query = "Redis";
const sortedArticles = await searchAndSortArticles(query);
// 输出排序后的文章列表
sortedArticles.forEach(article => {
console.log(`标题: ${article.title}, 作者: ${article.author},正文:${article.content}`);
});
在这段代码中,我们定义了一个 searchAndSortArticles 函数,该函数接受一个查询参数 query,并使用 Redis OM 的 search 方法进行搜索。搜索条件是标题或内容中包含查询关键词的文章。
接着,我们使用 sortAscending 方法对搜索结果按 publishDate 字段进行升序排序。sortAscending 方法的参数是需要排序的字段名称,这里我们选择 publishDate,表示我们希望按照文章的发布日期从早到晚进行排序。
最后,我们使用 return.all() 方法返回所有匹配的文章,并将这些文章按排序后的顺序输出。
完整示例
import { createClient } from "redis";
import { EntityId, Repository, Schema } from "redis-om";
const redis = createClient({ url: "redis://localhost:6380" });
redis.on("error", (err) => console.log("Redis Client Error", err));
/**
* 定义文章的 Schema
*/
const articleSchema = new Schema("article", {
title: { type: "text" },
author: { type: "string" },
publishDate: { type: "date", sortable: true }, // 将 publishDate 标记为可排序
content: { type: "text" },
tags: { type: "string[]" },
});
/**
* 创建文章的 Repository
*/
const articleRepository = new Repository(articleSchema, redis);
/**
* 根据查询条件搜索文章的标题和正文
* @param query - 搜索关键词
* @returns 匹配的文章列表
*/
const searchArticles = async (query: string | number | boolean) => {
const articles = await articleRepository
.search()
.where("title")
.matches(query)
.or("content")
.matches(query)
.return.all();
return articles;
};
/**
* 根据查询条件搜索文章并按发布日期排序
* @param query - 搜索关键词
* @returns 排序后的文章列表
*/
const searchAndSortArticles = async (query: string | number | boolean) => {
const articles = await articleRepository
.search()
.where("title")
.matches(query)
.or("content")
.matches(query)
.sortAscending("publishDate")
.return.all();
return articles;
};
/**
* 清空所有文章数据
*/
const clearAllArticles = async () => {
const allArticles = await articleRepository.search().return.all();
const allIds = allArticles.map(article => article[EntityId as any]);
await articleRepository.remove(allIds);
};
/**
* 初始化 Redis 连接和存储示例文章
*/
const initialize = async () => {
await redis.connect();
console.log("redis connected");
await articleRepository.dropIndex(); // 删除现有索引
await articleRepository.createIndex(); // 重新创建索引
// 清空现有的数据
await clearAllArticles();
// 创建一些示例文章
const articles = [
{
title: "Redis OM 的强大功能",
author: "张三",
publishDate: new Date("2023-01-01"),
content: "Redis OM 提供了许多强大的功能,包括全文搜索和排序。",
tags: ["Redis", "搜索"],
},
{
title: "使用 Redis 进行数据存储",
author: "李四",
publishDate: new Date("2023-02-15"),
content: "Redis 是一个高性能的键值数据库,适用于多种数据存储场景。",
tags: ["Redis", "数据库"],
},
{
title: "Redis 在实际应用中的案例",
author: "王五",
publishDate: new Date("2023-03-10"),
content: "本文介绍了 Redis 在实际应用中的一些成功案例。",
tags: ["Redis", "案例"],
},
];
// 将文章存储到 Redis
for (const article of articles) {
await articleRepository.save(article);
}
console.log("示例文章已存储到 Redis");
const query = "Redis";
const articleList = await searchArticles(query);
console.log("搜索结果:");
articleList.forEach((article) => {
console.log(`标题: ${article.title}, 作者: ${article.author},正文:${article.content}`);
});
const sortedArticles = await searchAndSortArticles(query);
console.log("排序后的搜索结果:");
sortedArticles.forEach((article) => {
console.log(`标题: ${article.title}, 作者: ${article.author},正文:${article.content}`);
});
};
// 调用初始化函数
initialize().catch((err) => console.error("Error initializing Redis:", err));
通过 Redis OM 的搜索和排序功能,我们可以轻松地对数据进行复杂的查询操作,并按需对结果进行排序。这使得 Redis OM 成为处理大规模数据和实现高效搜索的强大工具。无论是需要按时间排序的新闻文章、按价格排序的商品列表,还是按评分排序的用户评论,Redis OM 都能提供灵活且高效的解决方案。
6. 高级功能
Redis OM 提供了许多高级功能,使得数据存储和查询更加灵活和高效。本节将探讨使用 JSON 和 Hashes 存储数据的优缺点,Redis OM 支持的高级搜索功能,以及如何使用链式查询和原生查询进行复杂查询。
6.1 使用 JSON 和 Hashes 存储数据
Redis OM 支持将数据存储为 JSON 文档或 Hashes。每种存储方式都有其优缺点。
优缺点
-
JSON 存储
- 优点:
- 支持嵌套数据结构。
- 可以使用 JSONPath 进行复杂查询。
- 更适合存储结构化和半结构化数据。
- 缺点:
- 存储和查询开销较大,适合需要复杂查询的场景。
- 优点:
-
Hashes 存储
- 优点:
- 存储和查询开销较小。
- 更适合平面数据结构。
- 缺点:
- 不支持嵌套数据结构。
- 查询功能相对简单。
- 优点:
示例代码
- JSON 存储
const jsonArticleSchema = new Schema('article', {
title: { type: 'string' },
author: { type: 'string' },
publishDate: { type: 'date', sortable: true },
content: { type: 'string' },
tags: { type: 'string[]' }
}, {
dataStructure: 'JSON'
});
const jsonArticleRepository = new Repository(jsonArticleSchema, redis);
await jsonArticleRepository.createIndex();
- Hashes 存储
const hashArticleSchema = new Schema('article', {
title: { type: 'string' },
author: { type: 'string' },
publishDate: { type: 'date', sortable: true },
content: { type: 'string' },
tags: { type: 'string[]' }
}, {
dataStructure: 'HASH'
});
const hashArticleRepository = new Repository(hashArticleSchema, redis);
await hashArticleRepository.createIndex();
Schema的类型注解SchemaOptions中有如下注释:
/** The data structure used to store the {@link Entity} in Redis. Can be set
* to either `JSON` or `HASH`. Defaults to JSON.
*/
如果不显式指定 dataStructure,则 Redis OM 将默认使用 JSON 数据结构来存储实体。
6.2 高级搜索功能(日期搜索、数组搜索等)
Redis OM 支持多种高级搜索功能,包括日期搜索、数组搜索等。
示例代码
- 日期搜索
const searchByDate = async (date: Date) => {
const articles = await jsonArticleRepository.search()
.where('publishDate').on(date)
.return.all();
return articles;
};
const date = new Date('2023-01-01');
const articlesByDate = await searchByDate(date);
articlesByDate.forEach(article => {
console.log(`Title: ${article.title}, Publish Date: ${article.publishDate}`);
});
- 数组搜索
const searchByTag = async (tag: string) => {
const articles = await jsonArticleRepository.search()
.where('tags').contain(tag)
.return.all();
return articles;
};
const tag = 'redis';
const articlesByTag = await searchByTag(tag);
articlesByTag.forEach(article => {
console.log(`Title: ${article.title}, Tags: ${article.tags}`);
});
6.3 链式查询
链式查询使得我们可以组合多个查询条件。
const searchWithChaining = async () => {
const articles = await jsonArticleRepository.search()
.where('author').equals('张三')
.and('tags').contain('redis')
.or('publishDate').after(new Date('2022-01-01'))
.return.all();
return articles;
};
const chainedArticles = await searchWithChaining();
chainedArticles.forEach(article => {
console.log(`Title: ${article.title}, Author: ${article.author}`);
});
6.4 原生查询
原生查询允许我们使用 RediSearch 的原生查询语法进行复杂查询。
const searchWithRawQuery = async (query: string) => {
const articles = await jsonArticleRepository.searchRaw(query).return.all();
return articles;
};
const rawQuery = "@author:{张三} @tags:{redis} @publishDate:[2022 +inf]";
// const rawQuer1 = "@artist:{Mushroomhead} @title:beautiful @year:[1990 +inf]"
const rawQueryArticles = await searchWithRawQuery(rawQuery);
rawQueryArticles.forEach(article => {
console.log(`Title: ${article.title}, Author: ${article.author}`);
});
通过 Redis OM 的高级功能,我们可以灵活地存储和查询数据,满足各种复杂的业务需求。
完整示例
import { createClient } from "redis";
import { EntityId, Repository, Schema } from "redis-om";
const redis = createClient({ url: "redis://localhost:6380" });
redis.on("error", (err) => console.log("Redis Client Error", err));
/**
* 定义文章的 Schema
*/
const articleSchema = new Schema("article", {
title: { type: "text" },
author: { type: "string" },
publishDate: { type: "date", sortable: true }, // 将 publishDate 标记为可排序
content: { type: "text" },
tags: { type: "string[]" },
}, {
dataStructure: 'JSON'
});
/**
* 创建文章的 Repository
*/
const articleRepository = new Repository(articleSchema, redis);
/**
* 根据查询条件搜索文章的标题和正文
* @param query - 搜索关键词
* @returns 匹配的文章列表
*/
const searchArticles = async (query: string | number | boolean) => {
const articles = await articleRepository
.search()
.where("title")
.matches(query)
.or("content")
.matches(query)
.return.all();
return articles;
};
/**
* 根据查询条件搜索文章并按发布日期排序
* @param query - 搜索关键词
* @returns 排序后的文章列表
*/
const searchAndSortArticles = async (query: string | number | boolean) => {
const articles = await articleRepository
.search()
.where("title")
.matches(query)
.or("content")
.matches(query)
.sortAscending("publishDate")
.return.all();
return articles;
};
/**
* 日期搜索
* @param date - 搜索日期
* @returns 匹配的文章列表
*/
const searchByDate = async (date: Date) => {
const articles = await articleRepository.search()
.where('publishDate').on(date)
.return.all();
return articles;
};
/**
* 数组搜索
* @param tag - 搜索标签
* @returns 匹配的文章列表
*/
const searchByTag = async (tag: string) => {
const articles = await articleRepository.search()
.where('tags').contain(tag)
.return.all();
return articles;
};
/**
* 链式查询
* @returns 匹配的文章列表
*/
const searchWithChaining = async () => {
const articles = await articleRepository.search()
.where('author').equals('John Doe')
.and('tags').contain('redis')
.or('publishDate').after(new Date('2022-01-01'))
.return.all();
return articles;
};
/**
* 原生查询
* @param query - 原生查询语句
* @returns 匹配的文章列表
*/
const searchWithRawQuery = async (query: string) => {
const articles = await articleRepository.searchRaw(query).return.all();
return articles;
};
/**
* 清空所有文章数据
*/
const clearAllArticles = async () => {
const allArticles = await articleRepository.search().return.all();
const allIds = allArticles.map(article => article[EntityId as any]);
await articleRepository.remove(allIds);
};
/**
* 初始化 Redis 连接和存储示例文章
*/
const initialize = async () => {
await redis.connect();
console.log("redis connected");
await articleRepository.dropIndex(); // 删除现有索引
await articleRepository.createIndex(); // 重新创建索引
// 清空现有的数据
await clearAllArticles();
// 创建一些示例文章
const articles = [
{
title: "Redis OM 的强大功能",
author: "张三",
publishDate: new Date("2023-01-01"),
content: "Redis OM 提供了许多强大的功能,包括全文搜索和排序。",
tags: ["Redis", "搜索"],
},
{
title: "使用 Redis 进行数据存储",
author: "李四",
publishDate: new Date("2023-02-15"),
content: "Redis 是一个高性能的键值数据库,适用于多种数据存储场景。",
tags: ["Redis", "数据库"],
},
{
title: "Redis 在实际应用中的案例",
author: "王五",
publishDate: new Date("2023-03-10"),
content: "本文介绍了 Redis 在实际应用中的一些成功案例。",
tags: ["Redis", "案例"],
},
];
// 将文章存储到 Redis
for (const article of articles) {
await articleRepository.save(article);
}
console.log("示例文章已存储到 Redis");
const query = "Redis";
const articleList = await searchArticles(query);
console.log("搜索结果:");
articleList.forEach((article) => {
console.log(`标题: ${article.title}, 作者: ${article.author},正文:${article.content}`);
});
const sortedArticles = await searchAndSortArticles(query);
console.log("排序后的搜索结果:");
sortedArticles.forEach((article) => {
console.log(`标题: ${article.title}, 作者: ${article.author},正文:${article.content}`);
});
const date = new Date('2023-01-01');
const articlesByDate = await searchByDate(date);
console.log("按日期搜索结果:");
articlesByDate.forEach(article => {
console.log(`标题: ${article.title}, 发布日期: ${article.publishDate}`);
});
const tag = 'Redis';
const articlesByTag = await searchByTag(tag);
console.log("按标签搜索结果:");
articlesByTag.forEach(article => {
console.log(`标题: ${article.title}, 标签: ${article.tags}`);
});
const chainedArticles = await searchWithChaining();
console.log("链式查询结果:");
chainedArticles.forEach(article => {
console.log(`标题: ${article.title}, 作者: ${article.author}`);
});
// 原生查询语法
const rawQuery = "@author:{张三} @tags:{redis} @publishDate:[2022 +inf]";
// const rawQuer1 = "@artist:{Mushroomhead} @title:beautiful @year:[1990 +inf]"
const rawQueryArticles = await searchWithRawQuery(rawQuery);
console.log("原生查询结果:");
rawQueryArticles.forEach(article => {
console.log(`标题: ${article.title}, 作者: ${article.author}`);
});
};
// 调用初始化函数
initialize().catch((err) => console.error("Error initializing Redis:", err));
总结
Redis OM 提供了强大的搜索和排序功能,基于 RediSearch 实现全文搜索,用户可以使用 search 方法在指定字段中进行全文搜索,并通过 sortAscending 或 sortDescending 方法对搜索结果进行排序。此外,Redis OM 支持使用 JSON 和 Hashes 存储数据,JSON 存储适合复杂嵌套结构,支持复杂查询;Hashes 存储适合平面结构,查询性能较高。高级搜索功能包括日期搜索、数组搜索、链式查询和原生查询,使得数据查询更加灵活和高效。总体来说,Redis OM 提供了全面的搜索和排序功能,支持多种数据存储方式和高级查询功能,使得数据存储和查询变得更加灵活和高效,能够满足各种复杂的业务需求。
附件——完整封装
import { createClient } from "redis";
import { EntityId, Repository, Schema, Search } from "redis-om";
const redis = createClient({ url: "redis://localhost:6380" });
redis.on("error", (err) => console.log("Redis Client Error", err));
/**
* 定义文章的 Schema
*/
const articleSchema = new Schema(
"article",
{
title: { type: "text" },
author: { type: "string" },
publishDate: { type: "date", sortable: true },
content: { type: "text" },
tags: { type: "string[]" },
},
{
dataStructure: "JSON",
}
);
/**
* 创建文章的 Repository
*/
const articleRepository = new Repository(articleSchema, redis);
/**
* 搜索文章
* @param field - 搜索字段
* @param query - 搜索关键词
* @param sortField - 排序字段(可选)
* @param sortOrder - 排序顺序(asc 或 desc,可选)
* @returns 匹配的文章列表
*/
const searchArticles = async (
field: string,
query: string | number | boolean,
sortField?: string,
sortOrder: "asc" | "desc" = "asc"
) => {
if (sortField) {
if (sortOrder === "asc") {
return articleRepository
.search()
.where(field)
.matches(query)
.sortAscending(sortField)
.return.all();
} else {
return await articleRepository
.search()
.where(field)
.matches(query)
.sortDescending(sortField)
.return.all();
}
}
return await articleRepository
.search()
.where(field)
.matches(query)
.return.all();
};
/**
* 日期搜索
* @param date - 搜索日期
* @returns 匹配的文章列表
*/
const searchByDate = async (date: Date) => {
return await articleRepository
.search()
.where("publishDate")
.on(date)
.return.all();
};
/**
* 数组搜索
* @param tag - 搜索标签
* @returns 匹配的文章列表
*/
const searchByTag = async (tag: string) => {
return await articleRepository
.search()
.where("tags")
.contain(tag)
.return.all();
};
/**
* 链式查询
* @param conditions - 查询条件数组
* @returns 匹配的文章列表
*/
const searchWithChaining = async (
conditions: { field: string; operator: string; value: any }[]
) => {
let search: Search<Record<string, any>> = articleRepository.search();
conditions.forEach((condition) => {
const { field, operator, value } = condition;
switch (operator) {
case "equals":
search = search.where(field).equals(value);
break;
case "contain":
search = search.where(field).contain(value);
break;
case "after":
search = search.where(field).after(value);
break;
case "before":
search = search.where(field).before(value);
break;
case "on":
search = search.where(field).on(value);
break;
default:
throw new Error(`Unsupported operator: ${operator}`);
}
});
return await search.return.all();
};
/**
* 原生查询
* @param query - 原生查询语句
* @returns 匹配的文章列表
*/
const searchWithRawQuery = async (query: string) => {
return await articleRepository.searchRaw(query).return.all();
};
/**
* 清空所有文章数据
*/
const clearAllArticles = async () => {
const allArticles = await articleRepository.search().return.all();
const allIds = allArticles.map((article) => article[EntityId as any]);
await articleRepository.remove(allIds);
};
/**
* 初始化 Redis 连接和存储示例文章
*/
const initialize = async () => {
await redis.connect();
console.log("redis connected");
await articleRepository.dropIndex();
await articleRepository.createIndex();
await clearAllArticles();
const articles = [
{
title: "Redis OM 的强大功能",
author: "张三",
publishDate: new Date("2023-01-01"),
content: "Redis OM 提供了许多强大的功能,包括全文搜索和排序。",
tags: ["Redis", "搜索"],
},
{
title: "使用 Redis 进行数据存储",
author: "李四",
publishDate: new Date("2023-02-15"),
content: "Redis 是一个高性能的键值数据库,适用于多种数据存储场景。",
tags: ["Redis", "数据库"],
},
{
title: "Redis 在实际应用中的案例",
author: "王五",
publishDate: new Date("2023-03-10"),
content: "本文介绍了 Redis 在实际应用中的一些成功案例。",
tags: ["Redis", "案例"],
},
];
for (const article of articles) {
await articleRepository.save(article);
}