Spring(引导,数据Neo4j),Neo4j:无法使用Kotlin类创建@QueryResult

时间:2019-01-03 15:08:02

标签: kotlin neo4j spring-data-neo4j

我正在努力使用Spring Data Neo4j的Event和自定义@QueryResult用Kotlin类创建节点实体@Query的投影。

NodeEntity:Event.java

@NodeEntity
// getter, setter, ... boilerplate (lombok)
@Data @NoArgsConstructor @AllArgsConstructor
class Event {
    @Id
    @GeneratedValue
    private Long id;
    private LocalDateTime timestamp;
    private String name;
}

回购:EventRepo.java

@Repository
interface EventRepo extends Neo4jRepository<Event, Long> {
    // works
    @Query("MATCH (e:Event) " +
            "RETURN id(e) AS id, e.timestamp AS timestamp, e.name AS name")
    EventDtoJava findAsJavaDto();

    // meh
    @Query("MATCH (e:Event) " +
            "RETURN id(e) AS id, e.timestamp AS timestamp, e.name AS name")
    EventDtoKotlin findAsKotlinDto();
}

预测:Java和Kotlin版本

 
// Projection with a Java class: works
@QueryResult
// getter, setter, ... boilerplate (lombok)
@Data
class EventDtoJava {
    private Long id;
    private LocalDateTime timestamp;
    private String name;
}
// Projection with a Kotlin class: meh
@QueryResult
data class EventDtoKotlin(
    var id: Long,
    var timestamp: LocalDateTime,
    var name: String
)

EventDtoJavaEventRepo.findAsJavaDto())相同的查询因Kotlin类(EventRepo.findAsKotlinDto())而失败。

作为一种解决方法,我可以使用Java DTO,但这不能解决根本问题。

根据https://stackoverflow.com/a/52475719中的建议,我已经为Neo4j的java.time添加了ObjectMapper支持。

我怀疑com.fasterxml.jackson.module:jackson-module-kotlin中缺少ObjectMapper,但这并没有改变。

编辑:也可以通过在EventDtoJava中创建all args构造函数来触发异常。

MATCH(e:Event)返回ID(e)AS ID,e.timestamp AS时间戳,e.name AS名称

Exception in thread "main" org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate com.example.demo.EventDtoKotlin using constructor fun <init>(kotlin.Long, java.time.LocalDateTime, kotlin.String): com.example.demo.EventDtoKotlin with arguments 0,2019-01-03T15:11:04.581095,test!
    at org.springframework.data.convert.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:228)
    at org.springframework.data.convert.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:84)
    at org.springframework.data.neo4j.conversion.Neo4jOgmEntityInstantiatorAdapter.createInstance(Neo4jOgmEntityInstantiatorAdapter.java:58)
    at org.springframework.data.neo4j.repository.query.QueryResultInstantiator.createInstance(QueryResultInstantiator.java:52)
    at org.neo4j.ogm.metadata.reflect.EntityFactory.instantiate(EntityFactory.java:121)
    at org.neo4j.ogm.metadata.reflect.EntityFactory.newObject(EntityFactory.java:90)
    at org.neo4j.ogm.context.SingleUseEntityMapper.map(SingleUseEntityMapper.java:91)
    at org.springframework.data.neo4j.repository.query.CustomResultConverter.convert(CustomResultConverter.java:80)
    at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:224)
    at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.lambda$and$0(ResultProcessor.java:210)
    at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:224)
    at org.springframework.data.repository.query.ResultProcessor.processResult(ResultProcessor.java:166)
    at org.springframework.data.neo4j.repository.query.GraphRepositoryQuery.doExecute(GraphRepositoryQuery.java:72)
    at org.springframework.data.neo4j.repository.query.AbstractGraphRepositoryQuery.execute(AbstractGraphRepositoryQuery.java:52)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:605)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.example.demo.$Proxy97.findAsKotlinDto(Unknown Source)
    at com.example.demo.DemoApplication.main(DemoApplication.java:24)
Caused by: java.lang.ClassCastException: class java.lang.String cannot be cast to class java.time.LocalDateTime (java.lang.String and java.time.LocalDateTime are in module java.base of loader 'bootstrap')
    at com.example.demo.EventDtoKotlin_Instantiator_5tss1f.newInstance(Unknown Source)
    at org.springframework.data.convert.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:226)
    ... 31 more

我尝试将e.timestamp包裹在localdatetime()中,但没有成功:

MATCH(e:Event)返回ID(e)AS ID,localdatetime(e.timestamp)AS时间戳,e.name AS名称

// with MATCH (e:Event) RETURN id(e) AS id, localdatetime(e.timestamp) AS timestamp, e.name AS name

Exception in thread "main" java.lang.ClassCastException: class java.time.LocalDateTime cannot be cast to class java.lang.String (java.time.LocalDateTime and java.lang.String are in module java.base of loader 'bootstrap')
    at org.neo4j.ogm.typeconversion.LocalDateTimeStringConverter.toEntityAttribute(LocalDateTimeStringConverter.java:25)
    at org.neo4j.ogm.metadata.FieldInfo.write(FieldInfo.java:368)
    at org.neo4j.ogm.context.SingleUseEntityMapper.writeProperty(SingleUseEntityMapper.java:143)
    at org.neo4j.ogm.context.SingleUseEntityMapper.setPropertiesOnEntity(SingleUseEntityMapper.java:99)
    at org.neo4j.ogm.context.SingleUseEntityMapper.map(SingleUseEntityMapper.java:92)
    at org.springframework.data.neo4j.repository.query.CustomResultConverter.convert(CustomResultConverter.java:80)
    at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:224)
    at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.lambda$and$0(ResultProcessor.java:210)
    at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:224)
    at org.springframework.data.repository.query.ResultProcessor.processResult(ResultProcessor.java:166)
    at org.springframework.data.neo4j.repository.query.GraphRepositoryQuery.doExecute(GraphRepositoryQuery.java:72)
    at org.springframework.data.neo4j.repository.query.AbstractGraphRepositoryQuery.execute(AbstractGraphRepositoryQuery.java:52)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:605)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.example.demo.$Proxy97.findAsJavaDto(Unknown Source)
    at com.example.demo.DemoApplication.main(DemoApplication.java:23)

Repro

使用Gradle创建一个Spring Initializer项目,并将build.gradle替换为以下内容:

build.gradle

plugins {
    id "org.jetbrains.kotlin.jvm" version "1.3.11"

    id("io.spring.dependency-management") version "1.0.6.RELEASE"
    id("org.springframework.boot") version "2.1.1.RELEASE"
    id("org.jetbrains.kotlin.plugin.spring") version "1.3.11"
    id("org.jetbrains.kotlin.plugin.noarg") version "1.3.11"
}

repositories {
    jcenter()
}
sourceCompatibility = 11
targetCompatibility = 11

ext['jackson.version'] = '2.9.8'

noArg {
    annotation("org.neo4j.ogm.annotation.NodeEntity")
    annotation("org.neo4j.ogm.annotation.RelationshipEntity")
    annotation("org.springframework.data.neo4j.annotation.QueryResult")
}

compileKotlin {
    kotlinOptions {
        jvmTarget = 1.8
        javaParameters = true
    }
}


dependencies {
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    implementation('org.springframework.boot:spring-boot-starter-json')
    // jackson kotlin classes support
    implementation('com.fasterxml.jackson.module:jackson-module-kotlin')

    implementation('org.springframework.boot:spring-boot-starter-data-neo4j')
    runtimeOnly('org.neo4j:neo4j:3.5.1')
    runtimeOnly('org.neo4j:neo4j-ogm-embedded-driver:3.1.5')

    compileOnly('org.projectlombok:lombok')
    annotationProcessor('org.projectlombok:lombok')
}

DemoApplication.java

package com.example.demo;

import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.kotlin.KotlinModule;
import lombok.val;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.time.LocalDateTime;
import java.util.Objects;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        var ctx = SpringApplication.run(DemoApplication.class, args);
        setupNeo4jJavaTimeSupport();
        var repo = ctx.getBean(EventRepo.class);

        var e = repo.save(new Event(null, LocalDateTime.now(), "test!"));
        System.out.println();
        System.out.println("e = " + e);
        System.out.println("java: " + Objects.requireNonNull(repo.findAsJavaDto()));
        System.out.println("kotlin" + Objects.requireNonNull(repo.findAsKotlinDto()));
    }

    private static void setupNeo4jJavaTimeSupport() {
        // see https://stackoverflow.com/a/52475719
        var ogmObjectMapper = org.neo4j.ogm.config.ObjectMapperFactory.objectMapper();
        ogmObjectMapper.registerModule(new JavaTimeModule());
        ogmObjectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

        ogmObjectMapper.registerModule(new KotlinModule());
    }
}

0 个答案:

没有答案