Spring Data Neo4j:按 LocalDate 过滤不起作用

Spring Data Neo4j: filter by LocalDate doesn't work

我的小型 Neo4j playground 应用程序(基于 Spring Boot 2、Spring Data Neo4j 和嵌入式驱动程序)是一个小型笔记软件。用户可以按创建日期过滤他们的笔记。为了更好地理解 Cypher,我使用 SDN 的 @Query (NoteRepo.findByDay(day)).

编写了 Cypher 查询

但是我无法使过滤工作。我很困惑 LocalDate 被转换成地图(控制台输出的最后一行)。在之前的查询中(NoteRepo.findBySnoozeUntil(day),使用 SDN 的 repo 查询关键字)一切正常,day 被转换为 ISO 8601 日期。

有人可以指出它有什么问题并修复它吗?

Note.java

@NodeEntity
class Note {
    @Id
    @GeneratedValue
    private Long id;
    private String content;
    private LocalDateTime created;
    private LocalDate snoozedUntil;
    // constructors, getters, setters, ... omitted
}

NoteRepo.java

import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.Neo4jRepository;

import java.time.LocalDate;
import java.util.List;

interface NoteRepo extends Neo4jRepository<Note, Long> {
    // FIXME find all notes created on a specific day
    // currently returns an empty list
    @Query("MATCH (n: Note) WHERE date(datetime(n.created)) = {0} RETURN n")
    List<Note> findByDay(LocalDate day);

    List<Note> findBySnoozeUntil(LocalDate day);
}

App.java

package com.example.neo4jquerywithlocaldate;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.time.LocalDate;
import java.time.LocalDateTime;

@SpringBootApplication
public class App {

    public static void main(String[] args) {
        var ctx = SpringApplication.run(App.class, args);
        var repo = ctx.getBean(NoteRepo.class);
        var tomorrow = LocalDate.now().plusDays(1);
        repo.save(new Note("learn neo4j", LocalDateTime.now(), tomorrow));

        var notesForTomorrow = repo.findBySnoozeUntil(tomorrow);
        System.out.println("notes snoozed until tomorrow = " + notesForTomorrow);

        var todaysNotes = repo.findByDay(LocalDate.now());
        System.out.println("today's notes = " + todaysNotes);
    }
}

Spring 启动应用程序的控制台输出(截断为 Neo4j 查询和 System.out.println)

UNWIND {rows} as row CREATE (n:`Note`) SET n=row.props RETURN row.nodeRef as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeRef=-1, props={snoozeUntil=2018-09-21, created=2018-09-20T19:38:54.732260, content=learn neo4j}}]}

MATCH (n:`Note`) WHERE n.`snoozeUntil` = { `snoozeUntil_0` } WITH n RETURN n, ID(n) with params {snoozeUntil_0=2018-09-21}

< notes for tomorrow = [Note{id=0, content='learn neo4j', created=2018-09-20T19:38:54.732260}]

MATCH (n: Note) WHERE date(datetime(n.created)) = {0} RETURN n with params {0={year=2018, month=SEPTEMBER, monthValue=9, dayOfMonth=20, chronology={id=ISO, calendarType=iso8601}, dayOfWeek=THURSDAY, era=CE, dayOfYear=263, leapYear=false}}

< today's notes = []

重现项目:Spring Initializr Project

将此添加到 build.gradle:

ext['neo4j-ogm.version'] = '3.1.3'

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-neo4j'
    runtimeOnly 'org.neo4j:neo4j:3.4.7'
    runtimeOnly 'org.neo4j:neo4j-ogm-embedded-driver:3.1.3'
}

并添加上面的类。

目前派生查询器(如 List<Note> findBySnoozedUntil(LocalDate day);)的处理方式不同于使用嵌入式驱动程序使用 @Query 注释的查询器。

嵌入式驱动程序当前将所有参数映射到字符串。为此,它无条件地使用 Jacksons ObjectMapper 的一个实例。

我们需要几个步骤来解决这个问题。

  1. 教ObjectMapper使用合理的格式。这很简单,你正在启动,所以请添加这个 starter compile('org.springframework.boot:spring-boot-starter-json'),它带来了很多有用的 Jackson 模块。如果你打算编写一个 web 应用程序并且已经有 spring-boot-starter-webspring-boot-starter-webflux,你可以省略 JSON starter,那些模块自带。

  2. 向 OGMs Embedded Objectmapper 注册包含的 Java 8 Jackson modules 并禁用将日期写为时间戳,即在您的 App.class:

final ObjectMapper ogmObjectMapper = org.neo4j.ogm.config.ObjectMapperFactory.objectMapper();
ogmObjectMapper.registerModule(new JavaTimeModule());
ogmObjectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
  1. 由于嵌入式驱动程序使用此配置将值映射为字符串,遗憾的是您必须调整查询:
@Query("MATCH (n: Note) WHERE date(datetime(n.created)) = date({0}) RETURN n")
List<Note> findByDay(LocalDate day);

相关输出现在

2018-09-24 10:50:53.313  INFO 2860 --- [           main] o.n.o.d.e.request.EmbeddedRequest        : Request: MATCH (n:`Note`) WHERE n.`snoozedUntil` = { `snoozedUntil_0` } WITH n RETURN n, ID(n) with params {snoozedUntil_0=2018-09-25}
notes snoozed until tomorrow = [com.example.demo.Note@2420e962]
2018-09-24 10:50:53.522  INFO 2860 --- [           main] o.n.o.d.e.request.EmbeddedRequest        : Request: MATCH (n: Note) WHERE date(datetime(n.created)) = date({0}) RETURN n with params {0=2018-09-24}
today's notes = [com.example.demo.Note@363d3958]

编辑: 对外部数据库使用 Bolt 也会看到同样的效果,解决方案目前是相同的。