在为 Neo4j Spring Boot JPA 使用自定义 AttributeConverter 时获取 Neo.ClientError.Statement.TypeError

Getting a Neo.ClientError.Statement.TypeError when using custom AttributeConverter for Neo4j Spring Boot JPA

我有一个 Kotlin (v1.1.4) Spring Boot (v2.0.0.BUILD-SNAPSHOT) 应用程序 运行 Spring Data Neo4j (v5.0.0 RC2)、Neo4j (v3.2.1) 和 Neo4j OGM (v3.0.0)。

我在尝试将自定义 JPA @Query 与具有自定义 AttributeConverter.

的字段一起使用时收到以下错误

堆栈跟踪:

    org.springframework.dao.InvalidDataAccessResourceUsageException: Error executing Cypher; Code: Neo.ClientError.Statement.TypeError; Description: Property values can only be of primitive types or arrays thereof; nested exception is org.neo4j.ogm.exception.CypherException: Error executing Cypher; Code: Neo.ClientError.Statement.TypeError; Description: Property values can only be of primitive types or arrays thereof

        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at org.springframework.data.neo4j.transaction.SessionFactoryUtils.convertOgmAccessException(SessionFactoryUtils.java:153)
        at org.springframework.data.neo4j.repository.support.SessionBeanDefinitionRegistrarPostProcessor.translateExceptionIfPossible(SessionBeanDefinitionRegistrarPostProcessor.java:71)
        at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
        at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:216)
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
        at com.sun.proxy.$Proxy158.createBelongsToRelationship(Unknown Source)
        at com.yellowbird.mbi.service.StoreServiceImpl.updateVendorStoreRelationship(StoreServiceImpl.kt:26)
        at com.yellowbird.mbi.service.StoreServiceImpl$$FastClassBySpringCGLIB$$e7299ffb.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:747)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at org.springframework.aop.interceptor.AbstractTraceInterceptor.invoke(AbstractTraceInterceptor.java:133)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
        at com.yellowbird.mbi.service.StoreServiceImpl$$EnhancerBySpringCGLIB$e3291f9.updateVendorStoreRelationship(<generated>)
        at com.yellowbird.mbi.service.StoreServiceIT.givenVendorId13_getVendorByVendorId_returnsVendorWithId13(StoreServiceIT.kt:28)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
        at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
        at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
        at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
        at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access[=12=]0(ParentRunner.java:58)
        at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
        at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
        at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
        at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
        at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
        at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    Caused by: org.neo4j.ogm.exception.CypherException: Error executing Cypher; Code: Neo.ClientError.Statement.TypeError; Description: Property values can only be of primitive types or arrays thereof
        at org.neo4j.ogm.drivers.embedded.request.EmbeddedRequest.executeRequest(EmbeddedRequest.java:176)
        at org.neo4j.ogm.drivers.embedded.request.EmbeddedRequest.execute(EmbeddedRequest.java:78)
        at org.neo4j.ogm.session.delegates.ExecuteQueriesDelegate.executeAndMap(ExecuteQueriesDelegate.java:117)
        at org.neo4j.ogm.session.delegates.ExecuteQueriesDelegate.query(ExecuteQueriesDelegate.java:86)
        at org.neo4j.ogm.session.Neo4jSession.query(Neo4jSession.java:391)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.data.neo4j.transaction.SharedSessionCreator$SharedSessionInvocationHandler.invoke(SharedSessionCreator.java:131)
        at com.sun.proxy.$Proxy145.query(Unknown Source)
        at org.springframework.data.neo4j.repository.query.GraphQueryExecution$SingleEntityExecution.execute(GraphQueryExecution.java:74)
        at org.springframework.data.neo4j.repository.query.GraphRepositoryQuery.doExecute(GraphRepositoryQuery.java:77)
        at org.springframework.data.neo4j.repository.query.AbstractGraphRepositoryQuery.execute(AbstractGraphRepositoryQuery.java:51)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:565)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:549)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:60)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        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:185)
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
        ... 47 more
    Caused by: org.neo4j.graphdb.QueryExecutionException: Property values can only be of primitive types or arrays thereof
        at org.neo4j.kernel.impl.query.QueryExecutionKernelException.asUserException(QueryExecutionKernelException.java:35)
        at org.neo4j.kernel.impl.factory.ClassicCoreSPI.executeQuery(ClassicCoreSPI.java:82)
        at org.neo4j.kernel.impl.factory.GraphDatabaseFacade.execute(GraphDatabaseFacade.java:430)
        at org.neo4j.kernel.impl.factory.GraphDatabaseFacade.execute(GraphDatabaseFacade.java:413)
        at org.neo4j.ogm.drivers.embedded.request.EmbeddedRequest.executeRequest(EmbeddedRequest.java:172)
        ... 69 more
    Caused by: org.neo4j.kernel.impl.query.QueryExecutionKernelException: Property values can only be of primitive types or arrays thereof
        at org.neo4j.cypher.internal.javacompat.ExecutionEngine.executeQuery(ExecutionEngine.java:65)
        at org.neo4j.kernel.impl.factory.ClassicCoreSPI.executeQuery(ClassicCoreSPI.java:78)
        ... 72 more
    Caused by: org.neo4j.cypher.CypherTypeException: Property values can only be of primitive types or arrays thereof
        at org.neo4j.cypher.internal.compatibility.v3_2.exceptionHandler$.cypherTypeException(exceptionHandler.scala:60)
        at org.neo4j.cypher.internal.compatibility.v3_2.exceptionHandler$.cypherTypeException(exceptionHandler.scala:27)
        at org.neo4j.cypher.internal.frontend.v3_2.CypherTypeException.mapToPublic(CypherException.scala:48)
        at org.neo4j.cypher.internal.compatibility.v3_2.exceptionHandler$runSafely$.apply(exceptionHandler.scala:95)
        at org.neo4j.cypher.internal.compatibility.v3_2.Compatibility$ExecutionPlanWrapper.run(Compatibility.scala:100)
        at org.neo4j.cypher.internal.PreparedPlanExecution.execute(PreparedPlanExecution.scala:26)
        at org.neo4j.cypher.internal.ExecutionEngine.execute(ExecutionEngine.scala:107)
        at org.neo4j.cypher.internal.javacompat.ExecutionEngine.executeQuery(ExecutionEngine.java:61)
        ... 73 more
    Caused by: org.neo4j.cypher.internal.frontend.v3_2.CypherTypeException: Property values can only be of primitive types or arrays thereof
        at org.neo4j.cypher.internal.compiler.v3_2.helpers.CastSupport$.getConverter(CastSupport.scala:106)
        at org.neo4j.cypher.internal.compiler.v3_2.mutation.makeValueNeoSafe$.transformTraversableToArray(makeValueNeoSafe.scala:54)
        at org.neo4j.cypher.internal.compiler.v3_2.mutation.makeValueNeoSafe$.apply(makeValueNeoSafe.scala:29)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.BaseRelationshipPipe.org$neo4j$cypher$internal$compiler$v3_2$pipes$BaseRelationshipPipe$$setProperty(CreateRelationshipPipe.scala:77)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.BaseRelationshipPipe$$anonfun$setProperties$$anonfun$apply.apply(CreateRelationshipPipe.scala:63)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.BaseRelationshipPipe$$anonfun$setProperties$$anonfun$apply.apply(CreateRelationshipPipe.scala:62)
        at scala.collection.immutable.Map$Map3.foreach(Map.scala:161)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.BaseRelationshipPipe$$anonfun$setProperties.apply(CreateRelationshipPipe.scala:62)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.BaseRelationshipPipe$$anonfun$setProperties.apply(CreateRelationshipPipe.scala:56)
        at scala.Option.foreach(Option.scala:257)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.BaseRelationshipPipe.setProperties(CreateRelationshipPipe.scala:56)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.BaseRelationshipPipe.org$neo4j$cypher$internal$compiler$v3_2$pipes$BaseRelationshipPipe$$createRelationship(CreateRelationshipPipe.scala:45)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.BaseRelationshipPipe$$anonfun$internalCreateResults.apply(CreateRelationshipPipe.scala:38)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.BaseRelationshipPipe$$anonfun$internalCreateResults.apply(CreateRelationshipPipe.scala:38)
        at scala.collection.Iterator$$anon.next(Iterator.scala:409)
        at scala.collection.Iterator$class.foreach(Iterator.scala:893)
        at scala.collection.AbstractIterator.foreach(Iterator.scala:1336)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.EagerAggregationPipe.internalCreateResults(EagerAggregationPipe.scala:89)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.PipeWithSource.createResults(Pipe.scala:82)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.PipeWithSource.createResults(Pipe.scala:79)
        at org.neo4j.cypher.internal.compiler.v3_2.pipes.PipeWithSource.createResults(Pipe.scala:79)
        at org.neo4j.cypher.internal.compiler.v3_2.executionplan.DefaultExecutionResultBuilderFactory$ExecutionWorkflowBuilder.createResults(DefaultExecutionResultBuilderFactory.scala:96)
        at org.neo4j.cypher.internal.compiler.v3_2.executionplan.DefaultExecutionResultBuilderFactory$ExecutionWorkflowBuilder.build(DefaultExecutionResultBuilderFactory.scala:74)
        at org.neo4j.cypher.internal.compiler.v3_2.BuildInterpretedExecutionPlan$$anonfun$getExecutionPlanFunction.apply(BuildInterpretedExecutionPlan.scala:103)
        at org.neo4j.cypher.internal.compiler.v3_2.BuildInterpretedExecutionPlan$$anonfun$getExecutionPlanFunction.apply(BuildInterpretedExecutionPlan.scala:86)
        at org.neo4j.cypher.internal.compiler.v3_2.BuildInterpretedExecutionPlan$$anon.run(BuildInterpretedExecutionPlan.scala:55)
        at org.neo4j.cypher.internal.compatibility.v3_2.Compatibility$ExecutionPlanWrapper$$anonfun$run.apply(Compatibility.scala:102)
        at org.neo4j.cypher.internal.compatibility.v3_2.Compatibility$ExecutionPlanWrapper$$anonfun$run.apply(Compatibility.scala:100)
        at org.neo4j.cypher.internal.compatibility.v3_2.exceptionHandler$runSafely$.apply(exceptionHandler.scala:90)
        ... 77 more

这是我的转换器:

class ZonedDateTimeStringConverter : AttributeConverter<ZonedDateTime, String> {

    companion object {
        private val DATE_TIME_FORMAT = DateTimeFormatter.ISO_OFFSET_DATE_TIME
    }

    override fun toGraphProperty(value: ZonedDateTime?): String? = when (value) {
        null -> null
        else -> DATE_TIME_FORMAT.format(value)
    }

    override fun toEntityAttribute(value: String?): ZonedDateTime? = when (value) {
        null -> null
        else -> ZonedDateTime.parse(value, DATE_TIME_FORMAT)
    }
}

我这里有一个关系实体:

@RelationshipEntity(type = "PART_OF")
data class PartOf(

    /*
     * @GraphId, @StartNode, @EndNode defined here)
     */

    @Convert(ZonedDateTimeStringConverter::class)
    var sinceDate: ZonedDateTime? = null

)

我的JPA查询如下:

@Query("MATCH (s:EntityOne {identifier: {entityOneIdentifier}})" +
        " MATCH (v:EntityTwo {identifier: {entitiyTwoIdentifier}})" +
        " CREATE (v)-[:PART_OF {createDate: {createDate}, sinceDate: {sinceDate}, active: {active}}]->(s) " +
        " RETURN count(s) > 0")
fun createPartOfRelationship(@Param("entityOneIdentifier") entityOneId: Long,
                                @Param("entityTwoIdentifier") entityTwoId: Long,
                                @Param("createDate") createDate: ZonedDateTime,
                                @Param("sinceDate") sinceDate: ZonedDateTime,
                                @Param("active") active: Boolean): Boolean

查看 Spring 在线 Neo4j 参考数据,看起来我已经准备好了一切。 OGM 正确地看到转换器(我能够调试并看到正在创建/命中 class 内部的静态 companion object

所以,我假设 Spring 数据可能需要了解转换器。因此,根据文档,我在 spring 配置中添加了一个 ConversionService

@Bean
fun conversionService(@Autowired sessionFactory: SessionFactory): ConversionService =
    MetaDataDrivenConversionService(sessionFactory.metaData())

使用 java.util.Date 确实有效,就像 Spring Data Neo4j 文档说它应该(它的自动配置)在这里说明:http://docs.spring.io/spring-data/neo4j/docs/4.0.0.RELEASE/reference/html/#reference_programming-model_conversion

有没有人有任何尝试或为什么会发生这种情况的建议?

不可能有这样的参数

@Param("createDate") createDate: ZonedDateTime

@Query 注释存储库方法中。

本例中的目标类型是 String,但也可能是 IntegerDouble 等。查询是相同的——SDN 不知道类型您需要转换为,因此您需要自己进行转换以更正类型(对于查询参数)。