Skip to content

Commit 23c5131

Browse files
committed
Update Neo4j extension to use Spring Data Neo4j.
1 parent c871dca commit 23c5131

File tree

10 files changed

+851
-646
lines changed

10 files changed

+851
-646
lines changed

spring-batch-neo4j/README.md

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,100 @@ This extension contains an `ItemReader` and `ItemWriter` implementations for [Ne
77
The `Neo4jItemReader` can be configured as follows:
88

99
```java
10-
SessionFactory sessionFactory = ...
11-
Neo4jItemReader<String> itemReader = new Neo4jItemReaderBuilder<String>()
12-
.sessionFactory(sessionFactory)
13-
.name("itemReader")
14-
.targetType(String.class)
15-
.startStatement("n=node(*)")
16-
.orderByStatement("n.age")
17-
.matchStatement("n -- m")
18-
.whereStatement("has(n.name)")
19-
.returnStatement("m")
10+
Neo4jItemReader<User> reader = new Neo4jItemReaderBuilder<User>()
11+
.neo4jTemplate(neo4jTemplate)
12+
.name("userReader")
13+
.statement(Cypher.match(userNode).returning(userNode))
14+
.targetType(User.class)
2015
.pageSize(50)
2116
.build();
2217
```
2318

2419
The `Neo4jItemWriter` can be configured as follows:
2520

2621
```java
27-
SessionFactory sessionFactory = ...
28-
Neo4jItemWriter<String> writer = new Neo4jItemWriterBuilder<String>()
29-
.sessionFactory(sessionFactory)
22+
Neo4jItemWriter<User> writer = new Neo4jItemWriterBuilder<User>()
23+
.neo4jTemplate(neo4jTemplate)
24+
.neo4jDriver(driver)
25+
.neo4jMappingContext(mappingContext)
3026
.build();
27+
```
28+
29+
## Minimal Spring Boot example
30+
31+
With a Spring Boot application containing the additional dependencies `spring-boot-starter-neo4j` and `spring-batch-neo4j`,
32+
the following _build.gradle_ dependency definition is the minimal needed.
33+
Please note the exclusion for Spring JDBC from the `spring-boot-starter-batch` to avoid any need for JDBC-based connections.
34+
35+
```groovy
36+
dependencies {
37+
implementation ('org.springframework.boot:spring-boot-starter-batch') {
38+
exclude group: 'org.springframework', module: 'spring-jdbc'
39+
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-jdbc'
40+
}
41+
// current development version 0.2.0-SNAPSHOT
42+
implementation 'org.springframework.batch.extensions:spring-batch-neo4j'
43+
implementation 'org.springframework.boot:spring-boot-starter-data-neo4j'
44+
testImplementation 'org.springframework.boot:spring-boot-starter-test'
45+
testImplementation 'org.springframework.batch:spring-batch-test'
46+
}
47+
```
48+
49+
```java
50+
@SpringBootApplication
51+
public class TestSpringBatchApplication implements CommandLineRunner {
52+
// those dependencies are created by Spring Boot's
53+
// spring-data-neo4j autoconfiguration
54+
@Autowired
55+
private Driver driver;
56+
@Autowired
57+
private Neo4jMappingContext mappingContext;
58+
@Autowired
59+
private Neo4jTemplate neo4jTemplate;
60+
61+
public static void main(String[] args) {
62+
SpringApplication.run(TestSpringBatchApplication.class, args);
63+
}
64+
65+
@Override
66+
public void run(String... args) {
67+
// writing
68+
Neo4jItemWriter<User> writer = new Neo4jItemWriterBuilder<User>()
69+
.neo4jTemplate(neo4jTemplate)
70+
.neo4jDriver(driver)
71+
.neo4jMappingContext(mappingContext)
72+
.build();
73+
writer.write(Chunk.of(new User("id1", "ab"), new User("id2", "bb")));
74+
75+
// reading
76+
org.neo4j.cypherdsl.core.Node userNode = Cypher.node("User");
77+
Neo4jItemReader<User> reader = new Neo4jItemReaderBuilder<User>()
78+
.neo4jTemplate(neo4jTemplate)
79+
.name("userReader")
80+
.statement(Cypher.match(userNode).returning(userNode))
81+
.targetType(User.class)
82+
.build();
83+
List<User> allUsers = new ArrayList<>();
84+
User user = null;
85+
while ((user = reader.read()) != null) {
86+
System.out.printf("Found user: %s%n", user.name);
87+
allUsers.add(user);
88+
}
89+
90+
// deleting
91+
writer.setDelete(true);
92+
writer.write(Chunk.of(allUsers.toArray(new User[]{})));
93+
}
94+
95+
@Node("User")
96+
public static class User {
97+
@Id public final String id;
98+
public final String name;
99+
100+
public User(String id, String name) {
101+
this.id = id;
102+
this.name = name;
103+
}
104+
}
105+
}
31106
```

spring-batch-neo4j/pom.xml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,16 @@
5454
<properties>
5555
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
5656
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
57-
<java.version>1.8</java.version>
57+
<java.version>17</java.version>
5858

5959
<!-- Production dependencies-->
60-
<spring.batch.version>4.3.3</spring.batch.version>
61-
<neo4j-ogm-core.version>3.2.21</neo4j-ogm-core.version>
60+
<spring.batch.version>5.1.0</spring.batch.version>
61+
<spring-data-neo4j.version>7.2.1</spring-data-neo4j.version>
6262

6363
<!-- Test Dependencies -->
6464
<assertj.version>3.18.1</assertj.version>
65-
<junit.version>4.13.2</junit.version>
66-
<mockito.version>3.6.0</mockito.version>
65+
<junit.version>5.10.1</junit.version>
66+
<mockito.version>5.8.0</mockito.version>
6767

6868
<!-- Maven plugins -->
6969
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
@@ -83,15 +83,15 @@
8383
<version>${spring.batch.version}</version>
8484
</dependency>
8585
<dependency>
86-
<groupId>org.neo4j</groupId>
87-
<artifactId>neo4j-ogm-core</artifactId>
88-
<version>${neo4j-ogm-core.version}</version>
86+
<groupId>org.springframework.data</groupId>
87+
<artifactId>spring-data-neo4j</artifactId>
88+
<version>${spring-data-neo4j.version}</version>
8989
</dependency>
9090

9191
<!-- Test Dependencies -->
9292
<dependency>
93-
<groupId>junit</groupId>
94-
<artifactId>junit</artifactId>
93+
<groupId>org.junit.jupiter</groupId>
94+
<artifactId>junit-jupiter-engine</artifactId>
9595
<version>${junit.version}</version>
9696
<scope>test</scope>
9797
</dependency>

spring-batch-neo4j/src/main/java/org/springframework/batch/extensions/neo4j/Neo4jItemReader.java

Lines changed: 35 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,19 @@
1616

1717
package org.springframework.batch.extensions.neo4j;
1818

19-
import java.util.ArrayList;
20-
import java.util.Iterator;
21-
import java.util.Map;
22-
2319
import org.apache.commons.logging.Log;
2420
import org.apache.commons.logging.LogFactory;
25-
import org.neo4j.ogm.session.Session;
26-
import org.neo4j.ogm.session.SessionFactory;
27-
21+
import org.neo4j.cypherdsl.core.Statement;
22+
import org.neo4j.cypherdsl.core.StatementBuilder;
23+
import org.neo4j.cypherdsl.core.renderer.Renderer;
2824
import org.springframework.batch.item.ItemReader;
2925
import org.springframework.batch.item.data.AbstractPaginatedDataItemReader;
3026
import org.springframework.beans.factory.InitializingBean;
27+
import org.springframework.data.neo4j.core.Neo4jTemplate;
3128
import org.springframework.util.Assert;
32-
import org.springframework.util.StringUtils;
29+
30+
import java.util.Iterator;
31+
import java.util.Map;
3332

3433
/**
3534
* <p>
@@ -38,15 +37,15 @@
3837
* </p>
3938
*
4039
* <p>
41-
* It executes cypher queries built from the statement fragments provided to
40+
* It executes cypher queries built from the statement provided to
4241
* retrieve the requested data. The query is executed using paged requests of
4342
* a size specified in {@link #setPageSize(int)}. Additional pages are requested
4443
* as needed when the {@link #read()} method is called. On restart, the reader
4544
* will begin again at the same number item it left off at.
4645
* </p>
4746
*
4847
* <p>
49-
* Performance is dependent on your Neo4J configuration (embedded or remote) as
48+
* Performance is dependent on your Neo4j configuration as
5049
* well as page size. Setting a fairly large page size and using a commit
5150
* interval that matches the page size should provide better performance.
5251
* </p>
@@ -58,20 +57,19 @@
5857
* environment (no restart available).
5958
* </p>
6059
*
60+
* @param <T> type of entity to load
61+
*
6162
* @author Michael Minella
6263
* @author Mahmoud Ben Hassine
64+
* @author Gerrit Meier
6365
*/
6466
public class Neo4jItemReader<T> extends AbstractPaginatedDataItemReader<T> implements InitializingBean {
6567

66-
protected Log logger = LogFactory.getLog(getClass());
68+
private final Log logger = LogFactory.getLog(getClass());
6769

68-
private SessionFactory sessionFactory;
70+
private Neo4jTemplate neo4jTemplate;
6971

70-
private String startStatement;
71-
private String returnStatement;
72-
private String matchStatement;
73-
private String whereStatement;
74-
private String orderByStatement;
72+
private StatementBuilder.OngoingReadingAndReturn statement;
7573

7674
private Class<T> targetType;
7775

@@ -86,76 +84,23 @@ public void setParameterValues(Map<String, Object> parameterValues) {
8684
this.parameterValues = parameterValues;
8785
}
8886

89-
protected final Map<String, Object> getParameterValues() {
90-
return this.parameterValues;
91-
}
92-
93-
/**
94-
* The start segment of the cypher query. START is prepended
95-
* to the statement provided and should <em>not</em> be
96-
* included.
97-
*
98-
* @param startStatement the start fragment of the cypher query.
99-
*/
100-
public void setStartStatement(String startStatement) {
101-
this.startStatement = startStatement;
102-
}
103-
104-
/**
105-
* The return statement of the cypher query. RETURN is prepended
106-
* to the statement provided and should <em>not</em> be
107-
* included
108-
*
109-
* @param returnStatement the return fragment of the cypher query.
110-
*/
111-
public void setReturnStatement(String returnStatement) {
112-
this.returnStatement = returnStatement;
113-
}
114-
11587
/**
116-
* An optional match fragment of the cypher query. MATCH is
117-
* prepended to the statement provided and should <em>not</em>
118-
* be included.
88+
* Cypher-DSL's {@link org.neo4j.cypherdsl.core.StatementBuilder.OngoingReadingAndReturn} statement
89+
* without skip and limit segments. Those will get added by the pagination mechanism later.
11990
*
120-
* @param matchStatement the match fragment of the cypher query
91+
* @param statement the Cypher-DSL statement-in-construction.
12192
*/
122-
public void setMatchStatement(String matchStatement) {
123-
this.matchStatement = matchStatement;
93+
public void setStatement(StatementBuilder.OngoingReadingAndReturn statement) {
94+
this.statement = statement;
12495
}
12596

12697
/**
127-
* An optional where fragment of the cypher query. WHERE is
128-
* prepended to the statement provided and should <em>not</em>
129-
* be included.
98+
* Establish the Neo4jTemplate for the reader.
13099
*
131-
* @param whereStatement where fragment of the cypher query
100+
* @param neo4jTemplate the template to use for the reader.
132101
*/
133-
public void setWhereStatement(String whereStatement) {
134-
this.whereStatement = whereStatement;
135-
}
136-
137-
/**
138-
* A list of properties to order the results by. This is
139-
* required so that subsequent page requests pull back the
140-
* segment of results correctly. ORDER BY is prepended to
141-
* the statement provided and should <em>not</em> be included.
142-
*
143-
* @param orderByStatement order by fragment of the cypher query.
144-
*/
145-
public void setOrderByStatement(String orderByStatement) {
146-
this.orderByStatement = orderByStatement;
147-
}
148-
149-
protected SessionFactory getSessionFactory() {
150-
return sessionFactory;
151-
}
152-
153-
/**
154-
* Establish the session factory for the reader.
155-
* @param sessionFactory the factory to use for the reader.
156-
*/
157-
public void setSessionFactory(SessionFactory sessionFactory) {
158-
this.sessionFactory = sessionFactory;
102+
public void setNeo4jTemplate(Neo4jTemplate neo4jTemplate) {
103+
this.neo4jTemplate = neo4jTemplate;
159104
}
160105

161106
/**
@@ -167,28 +112,16 @@ public void setTargetType(Class<T> targetType) {
167112
this.targetType = targetType;
168113
}
169114

170-
protected final Class<T> getTargetType() {
171-
return this.targetType;
172-
}
173-
174-
protected String generateLimitCypherQuery() {
175-
StringBuilder query = new StringBuilder(128);
176-
177-
query.append("START ").append(startStatement);
178-
query.append(matchStatement != null ? " MATCH " + matchStatement : "");
179-
query.append(whereStatement != null ? " WHERE " + whereStatement : "");
180-
query.append(" RETURN ").append(returnStatement);
181-
query.append(" ORDER BY ").append(orderByStatement);
182-
query.append(" SKIP " + (pageSize * page));
183-
query.append(" LIMIT " + pageSize);
184-
185-
String resultingQuery = query.toString();
186-
115+
private Statement generateStatement() {
116+
Statement builtStatement = statement
117+
.skip(page * pageSize)
118+
.limit(pageSize)
119+
.build();
187120
if (logger.isDebugEnabled()) {
188-
logger.debug(resultingQuery);
121+
logger.debug(Renderer.getDefaultRenderer().render(builtStatement));
189122
}
190123

191-
return resultingQuery;
124+
return builtStatement;
192125
}
193126

194127
/**
@@ -197,28 +130,15 @@ protected String generateLimitCypherQuery() {
197130
* @see InitializingBean#afterPropertiesSet()
198131
*/
199132
@Override
200-
public void afterPropertiesSet() throws Exception {
201-
Assert.state(sessionFactory != null,"A SessionFactory is required");
133+
public void afterPropertiesSet() {
134+
Assert.state(neo4jTemplate != null, "A Neo4jTemplate is required");
202135
Assert.state(targetType != null, "The type to be returned is required");
203-
Assert.state(StringUtils.hasText(startStatement), "A START statement is required");
204-
Assert.state(StringUtils.hasText(returnStatement), "A RETURN statement is required");
205-
Assert.state(StringUtils.hasText(orderByStatement), "A ORDER BY statement is required");
136+
Assert.state(statement != null, "A statement is required");
206137
}
207138

208139
@SuppressWarnings("unchecked")
209140
@Override
210141
protected Iterator<T> doPageRead() {
211-
Session session = getSessionFactory().openSession();
212-
213-
Iterable<T> queryResults = session.query(getTargetType(),
214-
generateLimitCypherQuery(),
215-
getParameterValues());
216-
217-
if(queryResults != null) {
218-
return queryResults.iterator();
219-
}
220-
else {
221-
return new ArrayList<T>().iterator();
222-
}
142+
return neo4jTemplate.findAll(generateStatement(), parameterValues, targetType).iterator();
223143
}
224144
}

0 commit comments

Comments
 (0)