我一直在研究使用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和一个嵌入式databsase。
现在,我有了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实例中运行相同的查询时,它运行良好。在那没有语法错误。该应用程序具有默认的neo4j版本2.3.3,该版本来自nosqlunit依赖项。我曾尝试在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>
以前有人遇到过类似的问题吗?有人可以帮我解决这个问题吗?预先感谢。