Spring Data Neo4j: filter by LocalDate doesn't work

时光毁灭记忆、已成空白 提交于 2020-01-06 06:52:10

问题


My small Neo4j playground application (based on Spring Boot 2, Spring Data Neo4j and the embedded driver) is a small note-keeping software. The users can filter their notes by creation date. To get a better understanding of Cypher I wrote the Cypher query using SDN's @Query (NoteRepo.findByDay(day)).

However I can't get the filtering working. I'm confused that the LocalDate was transformed into a map (last line of the console output). In the previous query (NoteRepo.findBySnoozeUntil(day), using SDN's repo query keywords) everything was fine and the day was transformed to a ISO 8601 date.

Can somebody please point out what's wrong with it and fix it?

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);
    }
}

console output of the Spring Boot application (truncated to the Neo4j queries and 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 = []

repro project: Spring Initializr Project

add this to 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'
}

and add the classes above.


回答1:


At the moment the derived queriers (one like List<Note> findBySnoozedUntil(LocalDate day);) are handled differently than the ones annotated with @Query using the embedded driver.

The embedded driver currently maps all parameters onto strings. To two this, it uses unconditionally an instance of Jacksons ObjectMapper.

We need several steps to fix this.

  1. Teach ObjectMapper to use a reasonable format. That's easy, you're on Boot, so please add this starter compile('org.springframework.boot:spring-boot-starter-json'), it brings a lot of useful Jackson modules. If you intend to write a web application and already have spring-boot-starter-web or spring-boot-starter-webflux, you can omit the JSON starter, those modules bring it.

  2. Register the included Java 8 Jackson modules with OGMs Embedded Objectmapper and disable writing dates as timestamps, i.e. in your App.class:

final ObjectMapper ogmObjectMapper = org.neo4j.ogm.config.ObjectMapperFactory.objectMapper();
ogmObjectMapper.registerModule(new JavaTimeModule());
ogmObjectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
  1. As the embedded driver uses this config to map the value as String, you sadly have to adapt the query:
@Query("MATCH (n: Note) WHERE date(datetime(n.created)) = date({0}) RETURN n")
List<Note> findByDay(LocalDate day);

Related output is now

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]

Edit: The same effect would be seen using Bolt against an external database as well, solution is currently the same.



来源:https://stackoverflow.com/questions/52431160/spring-data-neo4j-filter-by-localdate-doesnt-work

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!