对于今天的冒险,我们希望构建一个 Java 微服务,它连接到 Neo4j AuraDB Free 数据库中的图形数据并与之交互。我们的数据是 Goodreads 数据集的精简版本,其中包括书籍、作者和评论信息。虽然书籍和作者非常适合 MongoDB 等文档数据库,但一旦您将评论添加到组合中,关系的细微差别就会使该项目更适合 AuraDB。这样,我们可以利用不同实体之间的关系来改进基于连接结构的分析。虽然我们在这里所做的一切都对其他数据存储是可行的,但图形数据库极大地简化了对实体连接方式的查询。
Neo4j 提供了多种部署选项。我们可以启动一个 Docker 容器(就像我们在早期使用 MongoDB 的项目中所做的那样),或者利用名为 AuraDB 的数据库即服务选项和免费套餐。Neo4j 将负责管理数据库,它应该拥有我们需要的一切。
对于此任务,我们将创建 Neo4j 数据库,加载数据,然后构建一个与数据库交互并为客户端服务提供 API 的微服务。
数据库即服务:AuraDB
注册和创建 Neo4j AuraDB Free 实例需要几个步骤,例如验证您的电子邮件地址并等待实例启动(需要几分钟)。您可以在我的同事 Michael Hunger 的许多“发现 AuraDB Free”博客文章中找到有关该过程的详细信息以及屏幕截图,例如这篇。
图形数据加载
实例运行后,我们就可以开始数据加载了。包含书籍的起始文件在今天的代码存储库中提供,此外,该文件夹的自述文件和加载脚本中的说明也可用。自述文件中包含的几个查询允许我们验证数据。
注意此数据集的较大版本也适合 AuraDB 免费套餐实例,但为了方便和加快加载速度,我们选择保持数据集较小。
应用服务
是时候开始构建我们的应用程序来提取评论数据了。对于我们的微服务,我们将构建一个带有几个 REST 端点的 Spring Boot 应用程序,以访问连接的 Neo4j 数据库中的数据。
我们可以使用 Spring Initializr 在 start.spring.io 上整理我们的项目大纲。
在表单上,我们可以将 、 和 字段保留为 default。在该部分下,我选择更新我个人项目的组名称,但欢迎您也将其保留为默认值。我为工件命名了 ,但命名不一定重要。此部分中的所有其他字段都可以保持原样。在该部分下,我们将需要 、 和 。Spring Data Neo4j 是众多 Spring Data 项目之一,用于连接到各种数据存储,帮助我们映射和访问加载到数据库中的数据。最后,项目模板就完成了,我们可以点击底部的按钮下载项目。ProjectLanguageSpring BootProject Metadataneo4j-java-microserviceDependenciesSpring Reactive WebLombokSpring Data Neo4jGenerate
注意: Spring Initializr 通过页面右栏中的月亮或太阳图标以深色模式或浅色模式显示。
Generating 会将项目下载为 ZIP 文件,以便我们可以将其解压缩并在您最喜欢的 IDE 中打开它。
该文件包含我们在 Spring Initializr 上设置的依赖项和软件版本,因此我们可以移动到文件夹中的文件。在这里,我们想要使用 URI 和数据库凭证连接到我们的 Neo4j 实例。我们通常不希望将数据库凭证嵌入到应用程序中,尤其是因为它是基于云的实例。对这些值进行硬编码可能会意外地授予其他人登录或篡改我们的数据库的权限。pom.xmlapplication.propertiessrc/main/resources
通常,像数据库凭证这样的配置值会被外部化,并由项目在运行时使用 Spring Cloud Config 等项目读入。但是,配置值超出了本文的范围。现在,我们可以将数据库 URI、用户名、密码和数据库名称嵌入到属性文件中。
#database connection spring.neo4j.uri=<insert Neo4j URI here> spring.neo4j.authentication.username=<insert Neo4j username here> spring.neo4j.authentication.password=<insert Neo4j password here> spring.data.neo4j.database=<insert Neo4j database here>
注意:Database 应该是 neo4j,除非你专门使用命令来更改默认值。
项目代码
让我们从 domain 类开始浏览 Java 文件。
@Data @Node class Review { @Id @GeneratedValue private Long neoId; @NonNull private String review_id; private String book_id, review_text, date_added, date_updated, started_at, read_at; private Integer rating, n_comments, n_votes; }
这是一个 Lombok 注解,它为 domain 类生成 getter、setters、equals、hashCode 和 toString 方法。它减少了样板代码,所以这很好。接下来是 @Node 注释。这是一个 Spring Data Neo4j 注释,将其标记为 Neo4j 实体类(Neo4j 实体称为节点)。@Data
在 class 声明中,我们为 class 定义了一些字段 (properties)。'@Id' 注释将字段标记为唯一标识符,并表示该值是由 Neo4j 内部生成的。在我们的下一个字段review_id中,我们有一个 Lombok @NonNull 注释,指定此字段不能为 null。我们还要检索一些其他字段,用于评论文本、日期和评级信息。@GeneratedValue
接下来,我们需要一个存储库接口,我们可以在其中定义与数据库中的数据交互的方法。
interface ReviewRepository extends ReactiveCrudRepository<Review, Long> { Flux<Review> findFirst1000By(); @Query("MATCH (r:Review)-[rel:WRITTEN_FOR]->(b:Book {book_id: $book_id}) RETURN r;") Flux<Review> findReviewsByBook(String book_id); }
我们希望这个存储库扩展 ,这将允许我们使用响应式方法和类型来处理数据。然后,我们定义几个方法。虽然我们可以使用 Spring Data 的一些默认方法(在文档的代码示例中列出)的开箱即用的实现,但我们希望进行一些自定义,因此我们将定义我们自己的方法。我们不想使用默认方法,而是只想提取 1,000 个结果,因为提取所有 35,342 条评论可能会使客户端上的结果呈现过程过载。ReactiveCrudRepository.findAll()
请注意,我们没有任何带有该方法的实现代码(没有查询或逻辑)。相反,我们正在使用 Spring Data 的另一个功能:派生方法。这就是 Spring 根据方法名称构建(即“派生”)查询应该是什么的地方。在我们的示例中,我们的存储库正在处理 reviews (),因此正在查找前 1,000 条评论。通常,此语法会通过查找特定标准 (rating, reviewer, date, etc.) 的结果来继续。但是,由于我们想要提取任何随机的评论集,因此我们可以通过简单地从方法名称中省略 criteria 来欺骗 Spring。这就是我们得到 .findFirst1000By()ReactiveCrudRepository<Review, Long>findFirst1000byfindFirst1000By
注意:这是一个隐藏的解决方法,一旦你知道它就非常方便,但是如果 Spring 为这些情况提供开箱即用的解决方案,那就太好了。
我们的下一个方法 () 更简单一些。我们想要查找任何特定书籍的评论,因此我们需要按 查找评论。为此,我们将注释与数据库相关查询语言(即 Cypher for Neo4j)中的查询一起使用。此查询查找为具有指定书籍 ID 的书籍撰写的评论。findReviewsByBook()book_id@Query
存储库完成后,我们可以编写控制器类,为其他服务设置一些 REST 端点来访问数据。
@RestController @RequestMapping("/neo") @AllArgsConstructor class ReviewController { private final ReviewRepository reviewRepo; @GetMapping String liveCheck() { return "Neo4j Java Microservice is up"; } @GetMapping("/reviews") Flux<Review> getReviews() { return reviewRepo.findFirst1000By(); } @GetMapping("/reviews/{book_id}") Flux<Review> getBookReviews(@PathVariable String book_id) { return reviewRepo.findReviewsByBook(book_id); } }
Spring 注解将此块指定为 rest 控制器类,并为所有类方法定义高级端点。在类声明中,我们注入 ReviewRepository,以便我们可以利用我们的编写方法。@RestController@RequestMapping
接下来,我们为每个方法映射端点。该方法使用高级端点返回字符串,确保我们的服务处于活动状态且可访问。我们可以通过添加嵌套端点 () 来执行该方法。此方法使用我们在存储库中编写的方法,并返回一个反应式 Flux<> 类型,我们预计结果中有 0 条或多条评论。liveCheck()/neogetReviews()/reviewsfindFirst1000By()
我们的最后一个方法具有嵌套端点 ,其中书籍 ID 是一个路径变量,它会根据我们要搜索的书籍而变化。该方法传入指定的书籍 ID 作为 path 变量,然后从存储库中调用该方法并返回 of reviews。/reviews/{book_id}getBookReviews()findReviewsByBook()Flux<>
进行测试
是时候测试我们的新服务了!我喜欢从下到上启动项目,因此首先要确保 Neo4j AuraDB 实例仍在运行。
注意:AuraDB 免费套餐将在三天后自动暂停。您可以使用实例上的“播放”图标继续。
接下来,我们需要通过 IDE 或命令行启动我们的应用程序。运行后,我们可以使用以下命令测试应用程序。neo4j-java-microservice
1. 测试应用程序是否处于活动状态:打开浏览器并转到 或 转到 命令行。localhost:8080/neocurl localhost:8080/neo
2. 测试后端评论 api 查找评论:打开浏览器并转到命令行或使用 .localhost:8080/neo/reviewscurl localhost:8080/neo/reviews
3. 测试某本书的 API 查找评论:打开浏览器并转到命令行或使用 .localhost:8080/neo/reviews/178186curl localhost:8080/neo/178186
这是我们服务的 reviews api 结果的结果输出!
查找 1000 条评论
按书籍查找评论
结束语
我们演练了如何使用 Neo4j AuraDB 免费套餐创建图形数据库实例,并为书籍、作者和评论加载数据。然后,我们构建了一个微服务应用程序来连接到云数据库并检索评论。最后,我们通过启动应用程序并访问我们的每个端点来测试我们的所有代码,以确保我们可以访问数据。
我们可以做更多的事情来扩展我们已经建立的东西。为了保持敏感数据的私密性,但多个服务可以访问,我们可以使用 Spring Cloud Config 之类的东西将我们的数据库凭证外部化。我们还可以将此服务添加到编排工具(如 Docker Compose)中,以一起管理多个服务。另一个探索领域是通过从数据库中提取更多相关实体来更充分地利用图数据优势。