java - Spring Data Neo4j - findById 方法在升级到 Spring Boot 2 后抛出无效查询异常
问题描述
我一直在研究使用 Spring Boot 和 Neo4j 作为数据库创建的微服务。因此,其中 Product 和 AccessoryRelation 中的两个相关实体是:
@NodeEntity
@QueryEntity
public class Product extends AbstractNeo4jEntity {
@Index(primary = true, unique = true)
private String productId;
@Transient
private String market;
@JsonIgnore
@Relationship(type = "HAS_ACCESSORY")
private Set<AccessoryRelation> relations = new HashSet<>();
public Product() {
}
public Product(final String productId) {
this.productId = productId;
}
public String getProductId() {
return productId;
}
public String getMarket() {
return market;
}
public void setMarket(final String market) {
this.market = market;
}
public Set<AccessoryRelation> getRelations() {
return new HashSet<>(relations);
}
public void addAccessory(final Product accessory) {
Assert.notNull(accessory, "Accessory product can not be null");
Assert.hasText(accessory.getProductId(),
"Accessory product should have a valid identifier");
Assert.hasText(accessory.getMarket(),
"Accessory product should be available atleast in one market");
Assert.isTrue(!this.equals(accessory),
"A product can't be an accessory of itself.");
final AccessoryRelation relation = this.relations.stream() //
.filter(rel -> rel.getAccessory().equals(accessory)) //
.findAny() //
.orElseGet(() -> {
final AccessoryRelation newRelation = new AccessoryRelation(this,
accessory, Sets.newHashSet());
this.relations.add(newRelation);
return newRelation;
});
relation.addMarket(accessory.getMarket());
}
public void removeAccessory(final Product accessory) {
Assert.notNull(accessory, "Accessory product can not be null");
Assert.hasText(accessory.getProductId(),
"Accessory product should have a valid identifier");
Assert.hasText(accessory.getMarket(),
"Accessory product should be available atleast in one market");
final Optional<AccessoryRelation> relation = this.relations.stream() //
.filter(rel -> rel.getAccessory().equals(accessory)) //
.findAny();
if (relation.isPresent()) {
relation.get().removeMarket(accessory.getMarket());
if (relation.get().getMarkets().isEmpty()) {
this.relations.remove(relation.get());
}
}
}
public void removeAccessories() {
this.relations.clear();
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof Product)) {
return false;
}
final Product product = (Product) obj;
return new EqualsBuilder() //
.append(getProductId(), product.getProductId()) //
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder() //
.append(getProductId()) //
.toHashCode();
}
@Override
public String toString() {
return new ToStringBuilder(this) //
.append("ProductId", getProductId()) //
.toString();
}
}
@RelationshipEntity(type = "HAS_ACCESSORY")
public class AccessoryRelation extends AbstractNeo4jEntity {
@StartNode
private Product product;
@EndNode
private Product accessory;
private Set<String> markets = new HashSet<>();
public AccessoryRelation() {
}
public AccessoryRelation(final Product product, final Product accessory,
final Set<String> markets) {
this.product = product;
this.accessory = accessory;
this.markets = markets;
}
public Product getProduct() {
return product;
}
public void setProduct(final Product product) {
this.product = product;
}
public Product getAccessory() {
return accessory;
}
public void setAccessory(final Product accessory) {
this.accessory = accessory;
}
public Set<String> getMarkets() {
return new HashSet<>(markets);
}
public void addMarket(final String market) {
this.markets.add(market);
}
public void removeMarket(final String market) {
this.markets.remove(market);
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof AccessoryRelation)) {
return false;
}
final AccessoryRelation relation = (AccessoryRelation) obj;
return new EqualsBuilder() //
.append(getProduct(), relation.getProduct()) //
.append(getAccessory(), relation.getAccessory()) //
.isEquals();
}
@Override
public final int hashCode() {
return new HashCodeBuilder() //
.append(getProduct()) //
.append(getAccessory()) //
.toHashCode();
}
@Override
public String toString() {
return new ToStringBuilder(this) //
.append("Product", getProduct()) //
.append("Accessory", getAccessory()) //
.append("Markets", getMarkets()) //
.toString();
}
}
对于 JUnit 测试用例,我使用的是 nosqlunit-neo4j 版本 1.0.0-rc.5 和嵌入式数据库。
现在我有了 findById(productId) 方法,该方法将返回 Product 以及相关的 AccessoryRelation 对象。但是,升级到 Spring Boot 2 后,这种方法就不起作用了。它因此引发异常:
org.springframework.data.neo4j.exception.UncategorizedNeo4jException:
Error executing Cypher; Code: Neo.ClientError.Statement.InvalidSyntax;
Description: Invalid input '|': expected whitespace, comment, a
relationship pattern, '.', node labels, '[', "=~", IN, STARTS, ENDS,
CONTAINS, IS, '^', '*', '/', '%', '+', '-', '=', "<>", "!=", '<', '>',
"<=", ">=", AND, XOR, OR, ',' or ']' (line 1, column 113 (offset:
112))
"MATCH (n:`Product`) WHERE n.`productId` = { id } WITH n RETURN n,[ [
(n)-[r_h1:`HAS_ACCESSORY`]->(p1:`Product`) | [ r_h1, p1 ] ] ]"
^; nested exception is org.neo4j.ogm.exception.CypherException: Error
executing Cypher; Code: Neo.ClientError.Statement.InvalidSyntax;
Description: Invalid input '|': expected whitespace, comment, a
relationship pattern, '.', node labels, '[', "=~", IN, STARTS, ENDS,
CONTAINS, IS, '^', '*', '/', '%', '+', '-', '=', "<>", "!=", '<', '>',
"<=", ">=", AND, XOR, OR, ',' or ']' (line 1, column 113 (offset:
112))
"MATCH (n:`Product`) WHERE n.`productId` = { id } WITH n RETURN n,[ [
(n)-[r_h1:`HAS_ACCESSORY`]->(p1:`Product`) | [ r_h1, p1 ] ] ]"
从外观上看,它正在处理'|' 字符作为查询中的无效字符。但是,当我在浏览器上的 neo4 3.4.9 实例中运行相同的查询时,它运行良好。那个没有语法错误。该应用程序具有来自 nosqlunit 依赖项的默认 neo4j 版本 2.3.3。我尝试在我的 pom 中包含 neo4j 3.4.9 依赖项,但这会产生一整套新问题。
以下是我的 pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-
8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<project.build.itDirectory>src/it/java</project.build.itDirectory>
<neo4j-cypher-dsl.version>2.3-RELEASE</neo4j-cypher-dsl.version>
<querydsl.version>4.1.4</querydsl.version>
<querydsl-apt.version>1.1.3</querydsl-apt.version>
<cglib.version>2.2.2</cglib.version>
<spring-cloud.version>Finchley.RC2</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${cglib.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-lucene3</artifactId>
<version>${querydsl.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<!-- This jar is used by CypherQueryDSL at runtime. If its not present
in classpath then java.lang.ClassNotFoundException: org.apache.lucene.search.Query
error is thrown -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>3.6.2</version>
</dependency>
<dependency>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-core</artifactId>
<version>2.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-http</artifactId>
<version>2.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.consol.citrus</groupId>
<artifactId>citrus-java-dsl</artifactId>
<version>2.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-cypher-dsl</artifactId>
<version>${neo4j-cypher-dsl.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>com.lordofthejars</groupId>
<artifactId>nosqlunit-neo4j</artifactId>
<version>1.0.0-rc.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.openpojo</groupId>
<artifactId>openpojo</artifactId>
<version>0.8.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
<version>2.1.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-embedded-driver</artifactId>
<version>3.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-all</artifactId>
<version>6.0_ALPHA</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>product-accessory-service</finalName>
<plugins>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>${querydsl-apt.version}</version>
<dependencies>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
</dependency>
</dependencies>
<configuration>
<processor>com.querydsl.apt.QuerydslAnnotationProcessor</processor>
</configuration>
<executions>
<execution>
<id>add-sources</id>
<phase>generate-sources</phase>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/querydsl</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.4.13</version>
<configuration>
有没有人遇到过类似的问题?谁能帮我解决这个问题?提前致谢。
解决方案
推荐阅读
- python - 用 pandas df 的 lambda 函数替换嵌套的 for 循环
- excel - 我可以使用函数返回两个值吗?
- sqlite - F#:SQLiteCommand 序列不起作用,但单个 SQLiteCommand 起作用。没有给出错误
- c - 如何知道数组中有多少个模式段?
- swift - Swift:使用国际格式化字符串执行 Bash 命令
- api - Stardog:如何通过 http 协议将参数传递给 SPARQL 存储查询
- google-maps - MapFragment 加载但无法访问
- sql - 在 SQL Server 的组中选择不包含值的列
- search - 如何在可以是文本文件或日志文件的大文件中有效地搜索字符串(属性)?
- .htaccess - 将所有页面重定向到新域,但将特定 URL 重定向到新域上的特定页面