我正在努力使用Spring Data Neo4j的Event
和自定义@QueryResult
用Kotlin类创建节点实体@Query
的投影。
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();
}
// 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
)
与EventDtoJava
(EventRepo.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构造函数来触发异常。
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()
中,但没有成功:
// 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)
使用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());
}
}