Created new Neo4J ItemReader to support Spring Data 4
This adds a new ItemReader that works with the Spring Data 4 updated Neo4JOperations implementation as well as the updated data conversion techniques. Resolves BATCH-2473
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.batch.item.data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.data.neo4j.template.Neo4jOperations;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Restartable {@link ItemReader} that reads objects from the graph database Neo4j
|
||||
* via a paging technique.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It executes cypher queries built from the statement fragments provided to
|
||||
* retrieve the requested data. The query is executed using paged requests of
|
||||
* a size specified in {@link #setPageSize(int)}. Additional pages are requested
|
||||
* as needed when the {@link #read()} method is called. On restart, the reader
|
||||
* will begin again at the same number item it left off at.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Performance is dependent on your Neo4J configuration (embedded or remote) as
|
||||
* well as page size. Setting a fairly large page size and using a commit
|
||||
* interval that matches the page size should provide better performance.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This implementation is thread-safe between calls to
|
||||
* {@link #open(org.springframework.batch.item.ExecutionContext)}, however you
|
||||
* should set <code>saveState=false</code> if used in a multi-threaded
|
||||
* environment (no restart available).
|
||||
* </p>
|
||||
*
|
||||
* @author Michael Minella
|
||||
* @since 3.07
|
||||
*/
|
||||
public abstract class AbstractNeo4jItemReader<T> extends
|
||||
AbstractPaginatedDataItemReader<T> implements InitializingBean {
|
||||
|
||||
protected Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private Neo4jOperations template;
|
||||
|
||||
private String startStatement;
|
||||
private String returnStatement;
|
||||
private String matchStatement;
|
||||
private String whereStatement;
|
||||
private String orderByStatement;
|
||||
|
||||
private Class<T> targetType;
|
||||
|
||||
private Map<String, Object> parameterValues;
|
||||
|
||||
/**
|
||||
* Optional parameters to be used in the cypher query.
|
||||
*
|
||||
* @param parameterValues the parameter values to be used in the cypher query
|
||||
*/
|
||||
public void setParameterValues(Map<String, Object> parameterValues) {
|
||||
this.parameterValues = parameterValues;
|
||||
}
|
||||
|
||||
protected final Map<String, Object> getParameterValues() {
|
||||
return this.parameterValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* The start segment of the cypher query. START is prepended
|
||||
* to the statement provided and should <em>not</em> be
|
||||
* included.
|
||||
*
|
||||
* @param startStatement the start fragment of the cypher query.
|
||||
*/
|
||||
public void setStartStatement(String startStatement) {
|
||||
this.startStatement = startStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* The return statement of the cypher query. RETURN is prepended
|
||||
* to the statement provided and should <em>not</em> be
|
||||
* included
|
||||
*
|
||||
* @param returnStatement the return fragment of the cypher query.
|
||||
*/
|
||||
public void setReturnStatement(String returnStatement) {
|
||||
this.returnStatement = returnStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional match fragment of the cypher query. MATCH is
|
||||
* prepended to the statement provided and should <em>not</em>
|
||||
* be included.
|
||||
*
|
||||
* @param matchStatement the match fragment of the cypher query
|
||||
*/
|
||||
public void setMatchStatement(String matchStatement) {
|
||||
this.matchStatement = matchStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional where fragment of the cypher query. WHERE is
|
||||
* prepended to the statement provided and should <em>not</em>
|
||||
* be included.
|
||||
*
|
||||
* @param whereStatement where fragment of the cypher query
|
||||
*/
|
||||
public void setWhereStatement(String whereStatement) {
|
||||
this.whereStatement = whereStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of properties to order the results by. This is
|
||||
* required so that subsequent page requests pull back the
|
||||
* segment of results correctly. ORDER BY is prepended to
|
||||
* the statement provided and should <em>not</em> be included.
|
||||
*
|
||||
* @param orderByStatement order by fragment of the cypher query.
|
||||
*/
|
||||
public void setOrderByStatement(String orderByStatement) {
|
||||
this.orderByStatement = orderByStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to perform operations against the Neo4J database.
|
||||
*
|
||||
* @param template the Neo4jOperations instance to use
|
||||
* @see Neo4jOperations
|
||||
*/
|
||||
public void setTemplate(Neo4jOperations template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
protected final Neo4jOperations getTemplate() {
|
||||
return this.template;
|
||||
}
|
||||
|
||||
/**
|
||||
* The object type to be returned from each call to {@link #read()}
|
||||
*
|
||||
* @param targetType the type of object to return.
|
||||
*/
|
||||
public void setTargetType(Class<T> targetType) {
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
protected final Class<T> getTargetType() {
|
||||
return this.targetType;
|
||||
}
|
||||
|
||||
protected String generateLimitCypherQuery() {
|
||||
StringBuilder query = new StringBuilder();
|
||||
|
||||
query.append("START ").append(startStatement);
|
||||
query.append(matchStatement != null ? " MATCH " + matchStatement : "");
|
||||
query.append(whereStatement != null ? " WHERE " + whereStatement : "");
|
||||
query.append(" RETURN ").append(returnStatement);
|
||||
query.append(" ORDER BY ").append(orderByStatement);
|
||||
query.append(" SKIP " + (pageSize * page));
|
||||
query.append(" LIMIT " + pageSize);
|
||||
|
||||
String resultingQuery = query.toString();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(resultingQuery);
|
||||
}
|
||||
|
||||
return resultingQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks mandatory properties
|
||||
*
|
||||
* @see InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.state(template != null, "A Neo4JOperations implementation is required");
|
||||
Assert.state(targetType != null, "The type to be returned is required");
|
||||
Assert.state(StringUtils.hasText(startStatement), "A START statement is required");
|
||||
Assert.state(StringUtils.hasText(returnStatement), "A RETURN statement is required");
|
||||
Assert.state(StringUtils.hasText(orderByStatement), "A ORDER BY statement is required");
|
||||
}
|
||||
}
|
||||
@@ -22,153 +22,32 @@ import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
import org.springframework.data.neo4j.conversion.DefaultConverter;
|
||||
import org.springframework.data.neo4j.conversion.Result;
|
||||
import org.springframework.data.neo4j.conversion.ResultConverter;
|
||||
import org.springframework.data.neo4j.template.Neo4jOperations;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Restartable {@link ItemReader} that reads objects from the graph database Neo4j
|
||||
* via a paging technique.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It executes cypher queries built from the statement fragments provided to
|
||||
* retrieve the requested data. The query is executed using paged requests of
|
||||
* a size specified in {@link #setPageSize(int)}. Additional pages are requested
|
||||
* as needed when the {@link #read()} method is called. On restart, the reader
|
||||
* will begin again at the same number item it left off at.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Performance is dependent on your Neo4J configuration (embedded or remote) as
|
||||
* well as page size. Setting a fairly large page size and using a commit
|
||||
* interval that matches the page size should provide better performance.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This implementation is thread-safe between calls to
|
||||
* {@link #open(org.springframework.batch.item.ExecutionContext)}, however you
|
||||
* should set <code>saveState=false</code> if used in a multi-threaded
|
||||
* environment (no restart available).
|
||||
* Extensions of the {@link AbstractNeo4jItemReader} intended for use with versions of
|
||||
* Spring Data Neo4J < 4. Conversions of the results are done using an external
|
||||
* {@link ResultConverter}.
|
||||
* </p>
|
||||
*
|
||||
* @author Michael Minella
|
||||
*
|
||||
* @see org.springframework.batch.item.data.Neo4j4ItemReader
|
||||
*/
|
||||
public class Neo4jItemReader<T> extends AbstractPaginatedDataItemReader<T> implements
|
||||
InitializingBean {
|
||||
public class Neo4jItemReader<T> extends AbstractNeo4jItemReader {
|
||||
|
||||
protected Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private Neo4jOperations template;
|
||||
|
||||
private String startStatement;
|
||||
private String returnStatement;
|
||||
private String matchStatement;
|
||||
private String whereStatement;
|
||||
private String orderByStatement;
|
||||
|
||||
private Class<T> targetType;
|
||||
|
||||
private Map<String, Object> parameterValues;
|
||||
|
||||
private ResultConverter<Map<String, Object>, T> resultConverter;
|
||||
|
||||
public Neo4jItemReader() {
|
||||
setName(ClassUtils.getShortName(Neo4jItemReader.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional parameters to be used in the cypher query.
|
||||
*
|
||||
* @param parameterValues the parameter values to be used in the cypher query
|
||||
*/
|
||||
public void setParameterValues(Map<String, Object> parameterValues) {
|
||||
this.parameterValues = parameterValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* The start segment of the cypher query. START is prepended
|
||||
* to the statement provided and should <em>not</em> be
|
||||
* included.
|
||||
*
|
||||
* @param startStatement the start fragment of the cypher query.
|
||||
*/
|
||||
public void setStartStatement(String startStatement) {
|
||||
this.startStatement = startStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* The return statement of the cypher query. RETURN is prepended
|
||||
* to the statement provided and should <em>not</em> be
|
||||
* included
|
||||
*
|
||||
* @param returnStatement the return fragment of the cypher query.
|
||||
*/
|
||||
public void setReturnStatement(String returnStatement) {
|
||||
this.returnStatement = returnStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional match fragment of the cypher query. MATCH is
|
||||
* prepended to the statement provided and should <em>not</em>
|
||||
* be included.
|
||||
*
|
||||
* @param matchStatement the match fragment of the cypher query
|
||||
*/
|
||||
public void setMatchStatement(String matchStatement) {
|
||||
this.matchStatement = matchStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional where fragment of the cypher query. WHERE is
|
||||
* prepended to the statement provided and should <em>not</em>
|
||||
* be included.
|
||||
*
|
||||
* @param whereStatement where fragment of the cypher query
|
||||
*/
|
||||
public void setWhereStatement(String whereStatement) {
|
||||
this.whereStatement = whereStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of properties to order the results by. This is
|
||||
* required so that subsequent page requests pull back the
|
||||
* segment of results correctly. ORDER BY is prepended to
|
||||
* the statement provided and should <em>not</em> be included.
|
||||
*
|
||||
* @param orderByStatement order by fragment of the cypher query.
|
||||
*/
|
||||
public void setOrderByStatement(String orderByStatement) {
|
||||
this.orderByStatement = orderByStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to perform operations against the Neo4J database.
|
||||
*
|
||||
* @param template the Neo4jOperations instance to use
|
||||
* @see Neo4jOperations
|
||||
*/
|
||||
public void setTemplate(Neo4jOperations template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
/**
|
||||
* The object type to be returned from each call to {@link #read()}
|
||||
*
|
||||
* @param targetType the type of object to return.
|
||||
*/
|
||||
public void setTargetType(Class<T> targetType) {
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the converter used to convert node to the targetType. By
|
||||
* default, {@link DefaultConverter} is used.
|
||||
@@ -181,53 +60,19 @@ InitializingBean {
|
||||
|
||||
@Override
|
||||
protected Iterator<T> doPageRead() {
|
||||
Result<Map<String, Object>> queryResults = template.query(
|
||||
generateLimitCypherQuery(), parameterValues);
|
||||
Result<Map<String, Object>> queryResults = getTemplate().query(
|
||||
generateLimitCypherQuery(), getParameterValues());
|
||||
|
||||
if(queryResults != null) {
|
||||
if (resultConverter != null) {
|
||||
return queryResults.to(targetType, resultConverter).iterator();
|
||||
return queryResults.to(getTargetType(), resultConverter).iterator();
|
||||
}
|
||||
else {
|
||||
return queryResults.to(targetType).iterator();
|
||||
return queryResults.to(getTargetType()).iterator();
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new ArrayList<T>().iterator();
|
||||
}
|
||||
}
|
||||
|
||||
private String generateLimitCypherQuery() {
|
||||
StringBuilder query = new StringBuilder();
|
||||
|
||||
query.append("START ").append(startStatement);
|
||||
query.append(matchStatement != null ? " MATCH " + matchStatement : "");
|
||||
query.append(whereStatement != null ? " WHERE " + whereStatement : "");
|
||||
query.append(" RETURN ").append(returnStatement);
|
||||
query.append(" ORDER BY ").append(orderByStatement);
|
||||
query.append(" SKIP " + (pageSize * page));
|
||||
query.append(" LIMIT " + pageSize);
|
||||
|
||||
String resultingQuery = query.toString();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(resultingQuery);
|
||||
}
|
||||
|
||||
return resultingQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks mandatory properties
|
||||
*
|
||||
* @see InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.state(template != null, "A Neo4JOperations implementation is required");
|
||||
Assert.state(targetType != null, "The type to be returned is required");
|
||||
Assert.state(StringUtils.hasText(startStatement), "A START statement is required");
|
||||
Assert.state(StringUtils.hasText(returnStatement), "A RETURN statement is required");
|
||||
Assert.state(StringUtils.hasText(orderByStatement), "A ORDER BY statement is required");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user