spring-projects / spring-data-neo4j

Provide support to increase developer productivity in Java when using Neo4j. Uses familiar Spring concepts such as a template classes for core API usage and lightweight repository style data access.
http://spring.io/projects/spring-data-neo4j
Apache License 2.0
825 stars 619 forks source link

Issues with Spring Data Neo4j when switching from Maven to Gradle #2799

Closed realfabianw closed 1 year ago

realfabianw commented 1 year ago

Hello,

I have been working on an IoT application for some time now and recently decided to switch from Maven to Gradle (Groovy) for dependency management. Unfortunately I can't get my code to run when using Gradle. I outsourced the affected part into two projects (Maven and Gradle) to be able to analyze the problem in more detail.

Neo4j (using Gradle) seems to have a problem resolving a custom query using the @Query annotation without errors. Both projects use the same code but only when using Gradle this code results in an error. See the complete setup below.

Gradle Project (Results in an error)

## build.gradle (Generated by [Spring Initializr](https://start.spring.io/)) ``` plugins { id 'java' id 'org.springframework.boot' version '3.1.3' id 'io.spring.dependency-management' version '1.1.3' } group = 'com.error' version = '0.0.1-SNAPSHOT' java { sourceCompatibility = '17' } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-neo4j' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' } tasks.named('test') { useJUnitPlatform() } ``` ## Entity ```java @Node @Data public class MqttSource { @Id @GeneratedValue(generatorClass = UUIDStringGenerator.class) private String nodeId; private String name; private String description; @CreatedDate private Instant createdDate; @LastModifiedDate private Instant lastModifiedDate; @CompositeProperty private Map properties; private String targetKafkaTopic; private String topicFilter; @CompositeProperty private Map mappings; } ``` ## Repository (Contains the code that results in an error) ```java public interface MqttSourceRepository extends Neo4jRepository { @Query("MATCH (gateway: Gateway {nodeId: $gatewayId})-[*]-(sources: MqttSource) RETURN DISTINCT sources;") List findAllRelatedMqttSources(String gatewayId); } ``` ## Main / CLR ```java @Slf4j @SpringBootApplication public class CheckGradleApplication implements CommandLineRunner { @Autowired private MqttSourceRepository mqttSourceRepository; public static void main(String[] args) { SpringApplication.run(CheckGradleApplication.class, args); } @Override public void run(String... args) throws Exception { List sources = mqttSourceRepository.findAllRelatedMqttSources("gateway-dev"); log.info("Sources size: {}", sources.size()); } } ``` ## Output ``` 2023-09-22T13:43:18.864+02:00 INFO 27384 --- [ main] c.error.gradle.CheckGradleApplication : Started CheckGradleApplication in 1.738 seconds (process running for 2.106) 2023-09-22T13:43:19.278+02:00 INFO 27384 --- [ main] .s.b.a.l.ConditionEvaluationReportLogger : Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. 2023-09-22T13:43:19.293+02:00 ERROR 27384 --- [ main] o.s.boot.SpringApplication : Application run failed java.lang.IllegalStateException: Failed to execute CommandLineRunner at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:774) ~[spring-boot-3.1.3.jar:3.1.3] at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:755) ~[spring-boot-3.1.3.jar:3.1.3] at org.springframework.boot.SpringApplication.run(SpringApplication.java:319) ~[spring-boot-3.1.3.jar:3.1.3] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-3.1.3.jar:3.1.3] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-3.1.3.jar:3.1.3] at com.error.gradle.CheckGradleApplication.main(CheckGradleApplication.java:20) ~[main/:na] Caused by: org.springframework.dao.InvalidDataAccessResourceUsageException: Expected parameter(s): gatewayId; Error code 'Neo.ClientError.Statement.ParameterMissing' at org.springframework.data.neo4j.core.Neo4jPersistenceExceptionTranslator.translateImpl(Neo4jPersistenceExceptionTranslator.java:107) ~[spring-data-neo4j-7.1.3.jar:7.1.3] at org.springframework.data.neo4j.core.Neo4jPersistenceExceptionTranslator.translateExceptionIfPossible(Neo4jPersistenceExceptionTranslator.java:91) ~[spring-data-neo4j-7.1.3.jar:7.1.3] at org.springframework.data.neo4j.core.DefaultNeo4jClient.potentiallyConvertRuntimeException(DefaultNeo4jClient.java:211) ~[spring-data-neo4j-7.1.3.jar:7.1.3] at org.springframework.data.neo4j.core.DefaultNeo4jClient$DefaultRecordFetchSpec.all(DefaultNeo4jClient.java:469) ~[spring-data-neo4j-7.1.3.jar:7.1.3] at java.base/java.util.Optional.map(Unknown Source) ~[na:na] at org.springframework.data.neo4j.core.Neo4jTemplate$DefaultExecutableQuery.getResults(Neo4jTemplate.java:1101) ~[spring-data-neo4j-7.1.3.jar:7.1.3] at org.springframework.data.neo4j.repository.query.Neo4jQueryExecution$DefaultQueryExecution.execute(Neo4jQueryExecution.java:51) ~[spring-data-neo4j-7.1.3.jar:7.1.3] at org.springframework.data.neo4j.repository.query.AbstractNeo4jQuery.execute(AbstractNeo4jQuery.java:93) ~[spring-data-neo4j-7.1.3.jar:7.1.3] at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:136) ~[spring-data-commons-3.1.3.jar:3.1.3] at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:120) ~[spring-data-commons-3.1.3.jar:3.1.3] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:164) ~[spring-data-commons-3.1.3.jar:3.1.3] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143) ~[spring-data-commons-3.1.3.jar:3.1.3] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar:6.0.11] at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:72) ~[spring-data-commons-3.1.3.jar:3.1.3] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar:6.0.11] at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-6.0.11.jar:6.0.11] at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:391) ~[spring-tx-6.0.11.jar:6.0.11] at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.0.11.jar:6.0.11] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar:6.0.11] at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) ~[spring-tx-6.0.11.jar:6.0.11] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar:6.0.11] at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-6.0.11.jar:6.0.11] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar:6.0.11] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:244) ~[spring-aop-6.0.11.jar:6.0.11] at jdk.proxy2/jdk.proxy2.$Proxy61.findAllRelatedMqttSources(Unknown Source) ~[na:na] at com.error.gradle.CheckGradleApplication.run(CheckGradleApplication.java:25) ~[main/:na] at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:771) ~[spring-boot-3.1.3.jar:3.1.3] ... 5 common frames omitted Caused by: org.neo4j.driver.exceptions.ClientException: Expected parameter(s): gatewayId at org.neo4j.driver.internal.util.Futures.blockingGet(Futures.java:110) ~[neo4j-java-driver-5.11.0.jar:5.11.0-ea56edc8c126b032c4a7dd8c4c571ec35419ca44] at org.neo4j.driver.internal.InternalSession.run(InternalSession.java:63) ~[neo4j-java-driver-5.11.0.jar:5.11.0-ea56edc8c126b032c4a7dd8c4c571ec35419ca44] at org.neo4j.driver.internal.InternalSession.run(InternalSession.java:48) ~[neo4j-java-driver-5.11.0.jar:5.11.0-ea56edc8c126b032c4a7dd8c4c571ec35419ca44] at org.neo4j.driver.internal.AbstractQueryRunner.run(AbstractQueryRunner.java:34) ~[neo4j-java-driver-5.11.0.jar:5.11.0-ea56edc8c126b032c4a7dd8c4c571ec35419ca44] at org.neo4j.driver.internal.AbstractQueryRunner.run(AbstractQueryRunner.java:39) ~[neo4j-java-driver-5.11.0.jar:5.11.0-ea56edc8c126b032c4a7dd8c4c571ec35419ca44] at org.springframework.data.neo4j.core.DefaultNeo4jClient$DelegatingQueryRunner.run(DefaultNeo4jClient.java:125) ~[spring-data-neo4j-7.1.3.jar:7.1.3] at org.springframework.data.neo4j.core.DefaultNeo4jClient$RunnableStatement.runWith(DefaultNeo4jClient.java:197) ~[spring-data-neo4j-7.1.3.jar:7.1.3] at org.springframework.data.neo4j.core.DefaultNeo4jClient$DefaultRecordFetchSpec.all(DefaultNeo4jClient.java:464) ~[spring-data-neo4j-7.1.3.jar:7.1.3] ... 28 common frames omitted Suppressed: org.neo4j.driver.internal.util.ErrorUtil$InternalExceptionCause: null at org.neo4j.driver.internal.util.ErrorUtil.newNeo4jError(ErrorUtil.java:78) ~[neo4j-java-driver-5.11.0.jar:5.11.0-ea56edc8c126b032c4a7dd8c4c571ec35419ca44] at org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher.handleFailureMessage(InboundMessageDispatcher.java:113) ~[neo4j-java-driver-5.11.0.jar:5.11.0-ea56edc8c126b032c4a7dd8c4c571ec35419ca44] at org.neo4j.driver.internal.messaging.common.CommonMessageReader.unpackFailureMessage(CommonMessageReader.java:64) ~[neo4j-java-driver-5.11.0.jar:5.11.0-ea56edc8c126b032c4a7dd8c4c571ec35419ca44] at org.neo4j.driver.internal.messaging.common.CommonMessageReader.read(CommonMessageReader.java:48) ~[neo4j-java-driver-5.11.0.jar:5.11.0-ea56edc8c126b032c4a7dd8c4c571ec35419ca44] at org.neo4j.driver.internal.async.inbound.InboundMessageHandler.channelRead0(InboundMessageHandler.java:80) ~[neo4j-java-driver-5.11.0.jar:5.11.0-ea56edc8c126b032c4a7dd8c4c571ec35419ca44] at org.neo4j.driver.internal.async.inbound.InboundMessageHandler.channelRead0(InboundMessageHandler.java:36) ~[neo4j-java-driver-5.11.0.jar:5.11.0-ea56edc8c126b032c4a7dd8c4c571ec35419ca44] at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) ~[netty-codec-4.1.97.Final.jar:4.1.97.Final] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) ~[netty-codec-4.1.97.Final.jar:4.1.97.Final] at org.neo4j.driver.internal.async.inbound.MessageDecoder.channelRead(MessageDecoder.java:42) ~[neo4j-java-driver-5.11.0.jar:5.11.0-ea56edc8c126b032c4a7dd8c4c571ec35419ca44] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) ~[netty-codec-4.1.97.Final.jar:4.1.97.Final] at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:333) ~[netty-codec-4.1.97.Final.jar:4.1.97.Final] at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:454) ~[netty-codec-4.1.97.Final.jar:4.1.97.Final] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.97.Final.jar:4.1.97.Final] at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.97.Final.jar:4.1.97.Final] at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.97.Final.jar:4.1.97.Final] at java.base/java.lang.Thread.run(Unknown Source) ~[na:na] 2023-09-22T13:43:19.304+02:00 INFO 27384 --- [ main] o.neo4j.driver.internal.InternalDriver : Closing driver instance 783172425 ```

Maven Project (Works as expected)

## pom.xml (Generated by [Spring Initializr](https://start.spring.io/)) ```xml 4.0.0 org.springframework.boot spring-boot-starter-parent 3.1.3 com.error maven 0.0.1-SNAPSHOT maven-check Demo project for Spring Boot 17 org.springframework.boot spring-boot-starter-data-neo4j org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok ``` ## Entity ```java @Node @Data public class MqttSource { @Id @GeneratedValue(generatorClass = UUIDStringGenerator.class) private String nodeId; private String name; private String description; @CreatedDate private Instant createdDate; @LastModifiedDate private Instant lastModifiedDate; @CompositeProperty private Map properties; private String targetKafkaTopic; private String topicFilter; @CompositeProperty private Map mappings; } ``` ## Repository ```java public interface MqttSourceRepository extends Neo4jRepository { @Query("MATCH (gateway: Gateway {nodeId: $gatewayId})-[*]-(sources: MqttSource) RETURN DISTINCT sources;") List findAllRelatedMqttSources(String gatewayId); } ``` ## Main / CLR ```java @Slf4j @SpringBootApplication public class CheckMavenApplication implements CommandLineRunner { @Autowired private MqttSourceRepository mqttSourceRepository; public static void main(String[] args) { SpringApplication.run(CheckMavenApplication.class, args); } @Override public void run(String... args) throws Exception { List sources = mqttSourceRepository.findAllRelatedMqttSources("gateway-dev"); log.info("Sources size: {}", sources.size()); } } ``` ## Output ``` 2023-09-22T13:50:26.179+02:00 INFO 35456 --- [ main] com.error.maven.CheckMavenApplication : Started CheckMavenApplication in 1.563 seconds (process running for 1.885) 2023-09-22T13:50:26.583+02:00 INFO 35456 --- [ main] com.error.maven.CheckMavenApplication : Sources size: 5 2023-09-22T13:50:26.586+02:00 INFO 35456 --- [ionShutdownHook] o.neo4j.driver.internal.InternalDriver : Closing driver instance 1684802151 ```

As you can read in the build.gradle and pom.xml, i encountered this problem with Spring Boot Version 3.1.3. I've tested the recent 3.1.4 release and the problem persists. I really have no idea what exactly is triggering this error and would appreciate any help.

Kind Regards

michael-simons commented 1 year ago

Hi @realfabianw you must compile your code with the -parameters flag, otherwise the names of parameters will not be available.

IDK if this is still the right way to do it in Gradle

tasks.withType(JavaCompile) {
    options.compilerArgs << '-parameters'
}

or if there's a better way. TBH I thought Spring Boot plugin does that automatically for you, the same way as Maven Spring Boot Plugin does.

You can alternatively use @Param on the corresponding parameter and specify the name.

realfabianw commented 1 year ago

thanks @michael-simons for your quick reply. I think you're right. Now that i know what i'm looking for i actually found the warning that reports this behaviour:

2023-09-22T15:15:34.157+02:00  WARN 31720 --- [           main] ocalVariableTableParameterNameDiscoverer : Using deprecated '-debug' fallback for parameter name resolution. Compile the affected code with '-parameters' instead or avoid its introspection: com.error.gradle.MqttSourceRepository

I've tried to adjust my build.gradle file with your suggested fix but failed to get it working. The gradle docs mark the CompileOptions accessed through "options" as read only, which is weird. I've also tried to use the existing compileJava Task, but that also didn't work.

compileJava {
    options.compilerArgs.add("-parameters")
}

Anyway, manually annotating the Parameter with the @Param Tag solved my issue. Everything else is unchanged.

public interface MqttSourceRepository extends Neo4jRepository<MqttSource, String> {
    @Query("MATCH (gateway: Gateway {nodeId: $gatewayId})-[*]-(sources: MqttSource) RETURN DISTINCT sources;")
    List<MqttSource> findAllRelatedMqttSources(@Param("gatewayId") String gatewayId);
}

I will close this issue as my problems are resolved.