refactored repository queries

added support for like, not null, property-exists and index-query lookups for derived queries
fixed delete* methods in repositories
added singleOrNull() method to fluent result API
This commit is contained in:
Michael Hunger
2011-11-01 10:24:44 +01:00
parent a6f3a9a7c7
commit bf8e0f38d3
33 changed files with 798 additions and 459 deletions

2
.gitignore vendored
View File

@@ -14,4 +14,4 @@ src/ant/.ant-targets-upload-dist.xml
*.ipr
*.iws
spring-data-neo4j-examples/imdb/log.roo
src/docbkx/snippets

View File

@@ -31,7 +31,7 @@ public class Movie {
@RelatedTo(type="DIRECTED", direction = INCOMING)
Person director;
@RelatedTo(elementClass = Person.class, type = "ACTS_IN", direction = INCOMING)
@RelatedTo(type = "ACTS_IN", direction = INCOMING)
Set<Person> actors;
@RelatedToVia(elementClass = Role.class, type = "ACTS_IN", direction = INCOMING)

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.neo4j.rest;
import org.neo4j.helpers.collection.IteratorUtil;
import org.neo4j.rest.graphdb.util.ConvertedResult;
import org.springframework.data.neo4j.conversion.EndResult;
@@ -32,6 +33,11 @@ class SpringEndResult<R> implements EndResult<R> {
return result.single();
}
@Override
public R singleOrNull() {
return IteratorUtil.singleOrNull(result);
}
@Override
public void handle(final org.springframework.data.neo4j.conversion.Handler<R> rHandler) {
result.handle(new SpringHandler<R>(rHandler));

View File

@@ -55,6 +55,11 @@ class SpringRestResult<T> implements Result<T> {
}
@SuppressWarnings({"unchecked"})
@Override
public T singleOrNull() {
return (T) to(Object.class).singleOrNull();
}
@SuppressWarnings("unchecked")
@Override

View File

@@ -22,5 +22,7 @@ package org.springframework.data.neo4j.conversion;
*/
public interface EndResult<R> extends Iterable<R> {
R single();
R singleOrNull();
void handle(Handler<R> handler);
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.neo4j.conversion;
import org.neo4j.graphdb.index.IndexHits;
import org.neo4j.helpers.collection.ClosableIterable;
import org.neo4j.helpers.collection.IteratorUtil;
import org.neo4j.helpers.collection.IteratorWrapper;
import java.util.Iterator;
@@ -52,12 +53,15 @@ public class QueryResultBuilder<T> implements Result<T> {
@Override
public T single() {
try {
final Iterator<T> it = result.iterator();
if (!it.hasNext()) throw new IllegalStateException("Expected at least one result, got none.");
final T value = it.next();
if (it.hasNext())
throw new IllegalStateException("Expected at least one result, got more than one.");
return value;
return IteratorUtil.single(result);
} finally {
closeIfNeeded();
}
}
@Override
public T singleOrNull() {
try {
return IteratorUtil.singleOrNull(result);
} finally {
closeIfNeeded();
}
@@ -69,11 +73,16 @@ public class QueryResultBuilder<T> implements Result<T> {
@Override
public R single() {
try {
final Iterator<T> it = result.iterator();
if (!it.hasNext()) throw new IllegalStateException("Expected at least one result, got none.");
final T value = it.next();
if (it.hasNext())
throw new IllegalStateException("Expected at least one result, got more than one.");
final T value = IteratorUtil.single(result);
return resultConverter.convert(value, type);
} finally {
closeIfNeeded();
}
}
@Override
public R singleOrNull() {
try {
final T value = IteratorUtil.singleOrNull(result);
return resultConverter.convert(value, type);
} finally {
closeIfNeeded();

View File

@@ -27,11 +27,13 @@ public class IndexInfo {
private IndexType indexType;
private final String fieldName;
private final Indexed.Level level;
private String indexKey;
public IndexInfo(Indexed annotation, Neo4jPersistentProperty property) {
this.indexName = determineIndexName(annotation,property);
this.indexType = annotation.indexType();
fieldName = annotation.fieldName();
this.indexKey = fieldName.isEmpty() ? property.getNeo4jPropertyName() : fieldName;
level = annotation.level();
}
@@ -50,4 +52,12 @@ public class IndexInfo {
public IndexType getIndexType() {
return indexType;
}
public boolean isFullText() {
return indexType == IndexType.FULLTEXT;
}
public String getIndexKey() {
return indexKey;
}
}

View File

@@ -73,4 +73,6 @@ public interface Neo4jPersistentProperty extends PersistentProperty<Neo4jPersist
Object getValue(final Object entity);
Neo4jPersistentEntity<?> getOwner();
String getIndexKey();
}

View File

@@ -293,16 +293,7 @@ public abstract class AbstractGraphRepository<S extends PropertyContainer, T> im
@Override
public void delete(T entity) {
final PropertyContainer state = template.getPersistentState(entity);
if (state instanceof Node) {
Node node = (Node) state;
node.delete();
}
if (state instanceof Relationship) {
Relationship relationship = (Relationship) state;
relationship.delete();
}
template.delete(entity);
}
@Override

View File

@@ -16,23 +16,10 @@
package org.springframework.data.neo4j.repository;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.helpers.collection.IteratorUtil;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.annotation.QueryType;
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.repository.query.DerivedCypherRepositoryQuery;
import org.springframework.data.neo4j.repository.query.QueryTemplates;
import org.springframework.data.neo4j.support.GenericTypeExtractor;
import org.springframework.data.neo4j.repository.query.GraphQueryMethod;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
import org.springframework.data.neo4j.support.query.QueryEngine;
import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
@@ -42,10 +29,6 @@ import org.springframework.util.Assert;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author mh
@@ -54,7 +37,7 @@ import java.util.Map;
public class GraphRepositoryFactory extends RepositoryFactorySupport {
private final Neo4jTemplate template;
private final MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> mappingContext;
private final Neo4jMappingContext mappingContext;
/**
* Creates a new {@link GraphRepositoryFactory} from the given {@link org.springframework.data.neo4j.support.Neo4jTemplate} and
@@ -63,7 +46,7 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
* @param template must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
*/
public GraphRepositoryFactory(Neo4jTemplate template, MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> mappingContext) {
public GraphRepositoryFactory(Neo4jTemplate template, Neo4jMappingContext mappingContext) {
Assert.notNull(template);
Assert.notNull(mappingContext);
@@ -126,273 +109,11 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
return new QueryLookupStrategy() {
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata, NamedQueries namedQueries) {
final GraphQueryMethod queryMethod = new GraphQueryMethod(method, repositoryMetadata,namedQueries);
if (!queryMethod.hasAnnotation() && !namedQueries.hasQuery(queryMethod.getNamedQueryName())) {
return new DerivedCypherRepositoryQuery(mappingContext, queryMethod, template);
}
return queryMethod.createQuery(repositoryMetadata, GraphRepositoryFactory.this.template);
final GraphQueryMethod queryMethod = new GraphQueryMethod(method,repositoryMetadata,namedQueries,mappingContext);
return queryMethod.createQuery(GraphRepositoryFactory.this.template);
}
};
}
public static class GraphQueryMethod extends QueryMethod {
private final Method method;
private final Query queryAnnotation;
private final String query;
public GraphQueryMethod(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) {
super(method, metadata);
this.method = method;
queryAnnotation = method.getAnnotation(Query.class);
this.query = queryAnnotation != null ? queryAnnotation.value() : getNamedQuery(namedQueries);
}
public boolean isValid() {
return this.query!=null; // && this.compoundType != null
}
private String getNamedQuery(NamedQueries namedQueries) {
final String namedQueryName = getNamedQueryName();
if (namedQueries.hasQuery(namedQueryName)) {
return namedQueries.getQuery(namedQueryName);
}
return null;
}
public Class<?> getReturnType() {
return method.getReturnType();
}
private String prepareQuery(Object[] args) {
final Parameters parameters = getParameters();
String queryString = this.query;
if (parameters.hasSortParameter()) {
queryString = addSorting(queryString, (Sort) args[parameters.getSortIndex()]);
}
if (parameters.hasPageableParameter()) {
final Pageable pageable = getPageable(args);
if (pageable!=null) {
queryString = addSorting(queryString, pageable.getSort());
queryString = addPaging(queryString, pageable);
}
}
return queryString;
}
private Map<String, Object> resolveParams(Object[] parameters, Neo4jTemplate template) {
Map<String, Object> params = new HashMap<String, Object>();
for (Parameter parameter : getParameters().getBindableParameters()) {
final Object value = parameters[parameter.getIndex()];
final String parameterName = parameter.getName();
if (parameterName != null) params.put(parameterName, resolveParameter(value, template));
else params.put(String.format(QueryTemplates.PARAMETER, parameter.getIndex()), resolveParameter(value, template));
}
return params;
}
private Pageable getPageable(Object[] args) {
Parameters parameters = getParameters();
if (parameters.hasPageableParameter()) {
return (Pageable) args[parameters.getPageableIndex()];
}
return null;
}
private String addPaging(String baseQuery, Pageable pageable) {
if (pageable==null) {
return baseQuery;
}
return baseQuery + " skip "+pageable.getOffset() + " limit " + pageable.getPageSize();
}
private String addSorting(String baseQuery, Sort sort) {
if (sort==null)
{
return baseQuery; // || sort.isEmpty()
}
final String sortOrder = getSortOrder(sort);
if (sortOrder.isEmpty()) {
return baseQuery;
}
return baseQuery + " order by " + sortOrder;
}
private String getSortOrder(Sort sort) {
String result = "";
for (Sort.Order order : sort) {
result += order.getProperty() + " " + order.getDirection();
}
return result;
}
private Object resolveParameter(Object parameter, Neo4jTemplate template) {
final Class<?> type = parameter.getClass();
if (template.isNodeEntity(type)) {
final Node state = template.getPersistentState(parameter);
if (state != null) return state.getId();
}
if (template.isRelationshipEntity(type)) {
final Relationship state = template.getPersistentState(parameter);
if (state != null) return state.getId();
}
return parameter;
}
private Class<?> getCompoundType() {
final Class<?> elementClass = getElementClass();
if (elementClass!=null) {
return elementClass;
}
return GenericTypeExtractor.resolveReturnedType(method);
}
private Class<?> getElementClass() {
if (!hasAnnotation() || queryAnnotation.elementClass().equals(Object.class)) {
return null;
}
return queryAnnotation.elementClass();
}
public String getQueryString() {
return this.query;
}
public boolean hasAnnotation() {
return queryAnnotation!=null;
}
private boolean isIterableResult() {
return Iterable.class.isAssignableFrom(getReturnType());
}
private RepositoryQuery createQuery(RepositoryMetadata repositoryMetadata, final Neo4jTemplate context) {
if (!isValid()) {
return null;
}
if (queryAnnotation == null) {
return new CypherGraphRepositoryQuery(this, repositoryMetadata, context); // cypher is default for named queries
}
switch (queryAnnotation.type()) {
case Cypher:
return new CypherGraphRepositoryQuery(this, repositoryMetadata, context);
case Gremlin:
return new GremlinGraphRepositoryQuery(this, repositoryMetadata, context);
default:
throw new IllegalStateException("@Query Annotation has to be configured as Cypher or Gremlin Query");
}
}
}
private static class CypherGraphRepositoryQuery extends GraphRepositoryQuery {
private CypherQueryExecutor queryExecutor;
public CypherGraphRepositoryQuery(GraphQueryMethod queryMethod, RepositoryMetadata metadata, final Neo4jTemplate template) {
super(queryMethod, metadata, template);
}
private CypherQueryExecutor getQueryExecutor() {
if (this.queryExecutor!=null) return this.queryExecutor;
this.queryExecutor = new CypherQueryExecutor(getTemplate().queryEngineFor(QueryType.Cypher));
return this.queryExecutor;
}
@Override
protected Object dispatchQuery(String queryString, Map<String, Object> params, Pageable pageable) {
GraphQueryMethod queryMethod = getQueryMethod();
final Class<?> compoundType = queryMethod.getCompoundType();
if (queryMethod.isPageQuery()) {
return queryPaged(queryString,params,pageable);
}
if (queryMethod.isIterableResult()) {
if (compoundType.isAssignableFrom(Map.class)) {
return getQueryExecutor().queryForList(queryString, params);
}
return getQueryExecutor().query(queryString, queryMethod.getCompoundType(), params);
}
return getQueryExecutor().queryForObject(queryString, queryMethod.getReturnType(), params);
}
private Object queryPaged(String queryString, Map<String, Object> params, Pageable pageable) {
final Iterable<?> result = getQueryExecutor().query(queryString, getQueryMethod().getCompoundType(), params);
return createPage(result, pageable);
}
}
private static class GremlinGraphRepositoryQuery extends GraphRepositoryQuery {
private QueryEngine queryEngine;
public GremlinGraphRepositoryQuery(GraphQueryMethod queryMethod, RepositoryMetadata metadata, final Neo4jTemplate template) {
super(queryMethod, metadata, template);
}
private QueryEngine getQueryEngine() {
if (this.queryEngine !=null) return queryEngine;
this.queryEngine = getTemplate().queryEngineFor(QueryType.Gremlin);
return this.queryEngine;
}
@SuppressWarnings("unchecked")
@Override
protected Object dispatchQuery(String queryString, Map<String, Object> params, Pageable pageable) {
GraphQueryMethod queryMethod = getQueryMethod();
if (queryMethod.isPageQuery()) {
return queryPaged(queryString,params,pageable);
}
if (queryMethod.isIterableResult()) {
return getQueryEngine().query(queryString, params).to(queryMethod.getCompoundType());
}
return getQueryEngine().query(queryString, params).to(queryMethod.getReturnType()).single();
}
private Object queryPaged(String queryString, Map<String, Object> params, Pageable pageable) {
@SuppressWarnings("unchecked") final Iterable<?> result = getQueryEngine().query(queryString, params).to(getQueryMethod().getCompoundType());
return createPage(result, pageable);
}
}
private static abstract class GraphRepositoryQuery implements RepositoryQuery {
private final GraphQueryMethod queryMethod;
private final Neo4jTemplate template;
public GraphRepositoryQuery(GraphQueryMethod queryMethod, RepositoryMetadata metadata, final Neo4jTemplate template) {
this.queryMethod = queryMethod;
this.template = template;
}
protected Neo4jTemplate getTemplate() {
return template;
}
@Override
public Object execute(Object[] parameters) {
Map<String, Object> params = queryMethod.resolveParams(parameters, template);
final String queryString = queryMethod.prepareQuery(parameters);
return dispatchQuery(queryString,params,queryMethod.getPageable(parameters));
}
protected abstract Object dispatchQuery(String queryString, Map<String, Object> params, Pageable pageable);
@Override
public GraphQueryMethod getQueryMethod() {
return queryMethod;
}
@SuppressWarnings({"unchecked", "rawtypes"})
protected Object createPage(Iterable<?> result, Pageable pageable) {
final List resultList = IteratorUtil.addToCollection(result, new ArrayList());
if (pageable==null) {
return new PageImpl(resultList);
}
final int currentTotal = pageable.getOffset() + pageable.getPageSize();
return new PageImpl(resultList, pageable, currentTotal);
}
}
}

View File

@@ -34,7 +34,7 @@ public class GraphRepositoryFactoryBean<S extends PropertyContainer, R extends C
TransactionalRepositoryFactoryBeanSupport<R, T, Long> {
private Neo4jTemplate template;
private MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> mappingContext;
private Neo4jMappingContext mappingContext;
public void setNeo4jTemplate(Neo4jTemplate template) {
this.template = template;
@@ -44,7 +44,7 @@ TransactionalRepositoryFactoryBeanSupport<R, T, Long> {
* @param mappingContext the mappingContext to set
*/
public void setMappingContext(
MappingContext<Neo4jPersistentEntity<?>, Neo4jPersistentProperty> mappingContext) {
Neo4jMappingContext mappingContext) {
this.mappingContext = mappingContext;
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright 2011 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.data.neo4j.repository.query;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.neo4j.annotation.QueryType;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.query.QueryEngine;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import java.util.Map;
/**
* @author mh
* @since 31.10.11
*/
class CypherGraphRepositoryQuery extends GraphRepositoryQuery {
private QueryEngine<Object> queryEngine;
public CypherGraphRepositoryQuery(GraphQueryMethod queryMethod, final Neo4jTemplate template) {
super(queryMethod, template);
}
@Override
protected QueryEngine<Object> getQueryEngine() {
if (this.queryEngine != null) return this.queryEngine;
this.queryEngine = getTemplate().queryEngineFor(QueryType.Cypher);
return this.queryEngine;
}
private String addPaging(String baseQuery, Pageable pageable) {
if (pageable==null) {
return baseQuery;
}
return baseQuery + " skip "+pageable.getOffset() + " limit " + pageable.getPageSize();
}
private String addSorting(String baseQuery, Sort sort) {
if (sort==null)
{
return baseQuery; // || sort.isEmpty()
}
final String sortOrder = getSortOrder(sort);
if (sortOrder.isEmpty()) {
return baseQuery;
}
return baseQuery + " order by " + sortOrder;
}
private String getSortOrder(Sort sort) {
String result = "";
for (Sort.Order order : sort) {
result += order.getProperty() + " " + order.getDirection();
}
return result;
}
protected String createQueryWithPagingAndSorting(final ParameterAccessor accessor) {
final GraphQueryMethod queryMethod = getQueryMethod();
final Parameters parameters = queryMethod.getParameters();
String queryString = queryMethod.getQueryString();
if (parameters.hasSortParameter()) {
queryString = addSorting(queryString, accessor.getSort());
}
if (parameters.hasPageableParameter()) {
final Pageable pageable = accessor.getPageable();
if (pageable!=null) {
queryString = addSorting(queryString, pageable.getSort());
queryString = addPaging(queryString, pageable);
}
}
return queryString;
}
}

View File

@@ -76,14 +76,15 @@ class CypherQueryBuilder implements CypherQueryDefinition {
PersistentPropertyPath<Neo4jPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
String variable = variableContext.getVariableFor(path);
Neo4jPersistentProperty leafProperty = path.getLeafProperty();
if (!leafProperty.isRelationship()) {
if (leafProperty.isIndexed()) {
startClauses.add(new StartClause(path, variable, index++));
final PartInfo partInfo = new PartInfo(path, variable, part, index);
if (partInfo.isPrimitiveProperty()) {
if (partInfo.isIndexed()) {
startClauses.add(new StartClause(partInfo));
} else {
whereClauses.add(new WhereClause(path, variable, part.getType(), index++));
whereClauses.add(new WhereClause(path, variable, part.getType(), index, partInfo));
}
}
index += 1;
MatchClause matchClause = new MatchClause(path);
@@ -94,10 +95,21 @@ class CypherQueryBuilder implements CypherQueryDefinition {
return this;
}
public PartInfo getPartInfo(int parameterIndex) {
for (StartClause startClause : startClauses) {
if (startClause.getPartInfo().getParameterIndex()==parameterIndex) return startClause.getPartInfo();
}
for (WhereClause whereClause : whereClauses) {
if (whereClause.getPartInfo().getParameterIndex()==parameterIndex) return whereClause.getPartInfo();
}
throw new IllegalArgumentException("Index "+parameterIndex+" not valid");
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {

View File

@@ -49,4 +49,6 @@ interface CypherQueryDefinition {
* @return
*/
String toString(Pageable pageable);
PartInfo getPartInfo(int index);
}

View File

@@ -15,22 +15,13 @@
*/
package org.springframework.data.neo4j.repository.query;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.neo4j.annotation.QueryType;
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.repository.GraphRepositoryFactory.GraphQueryMethod;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext;
import org.springframework.data.repository.core.EntityMetadata;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.Assert;
@@ -40,78 +31,45 @@ import org.springframework.util.Assert;
*
* @author Oliver Gierke
*/
public class DerivedCypherRepositoryQuery implements RepositoryQuery {
public class DerivedCypherRepositoryQuery extends CypherGraphRepositoryQuery {
private final GraphQueryMethod method;
private final CypherQueryExecutor executor;
private final CypherQueryDefinition query;
/**
* Creates a new {@link DerivedCypherRepositoryQuery} from the given {@link MappingContext},
* {@link GraphQueryMethod} and {@link org.springframework.data.neo4j.support.Neo4jTemplate}.
*
* @param context must not be {@literal null}.
* @param method must not be {@literal null}.
* @param database must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
* @param queryMethod must not be {@literal null}.
* @param template must not be {@literal null}.
*/
public DerivedCypherRepositoryQuery(MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> context, GraphQueryMethod method, Neo4jTemplate database) {
public DerivedCypherRepositoryQuery(Neo4jMappingContext mappingContext, GraphQueryMethod queryMethod, Neo4jTemplate template) {
super(queryMethod, template);
Assert.notNull(mappingContext);
Assert.notNull(context);
Assert.notNull(method);
Assert.notNull(database);
EntityMetadata<?> info = queryMethod.getEntityInformation();
PartTree tree = new PartTree(queryMethod.getName(), info.getJavaType());
EntityMetadata<?> info = method.getEntityInformation();
PartTree tree = new PartTree(method.getName(), info.getJavaType());
this.query = new CypherQueryCreator(tree, context, info.getJavaType()).createQuery();
this.method = method;
this.executor = new CypherQueryExecutor(database.queryEngineFor(QueryType.Cypher));
this.query = new CypherQueryCreator(tree, mappingContext, info.getJavaType()).createQuery();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
*/
@Override
public Object execute(Object[] parameters) {
ParameterAccessor accessor = new ParametersParameterAccessor(method.getParameters(), parameters);
Map<String, Object> paramMap = new HashMap<String, Object>();
int counter = 0;
for (Object parameter : accessor) {
paramMap.put(String.format(QueryTemplates.PARAMETER, counter++), parameter);
public Object resolveParameter(Object value, String parameterName, int index) {
final Object newValue = super.resolveParameter(value, parameterName, index);
PartInfo info = query.getPartInfo(index);
if (info.isFullText()) {
return String.format(QueryTemplates.PARAMETER_INDEX_QUERY,info.getIndexKey(),newValue);
}
Class<?> type = method.getEntityInformation().getJavaType();
String query = getQuery(this.query, accessor);
if (method.isCollectionQuery()) {
return executor.query(query, type, paramMap);
} else {
return executor.queryForObject(query, type, paramMap);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
*/
@Override
public QueryMethod getQueryMethod() {
return method;
return newValue;
}
/**
* Returns the actual Cypher query applying {@link Pageable} or {@link Sort} instances.
*
* @param query
* @param accessor
* @return
* @param accessor parameters
* @return query string
*/
private String getQuery(CypherQueryDefinition query, ParameterAccessor accessor) {
protected String createQueryWithPagingAndSorting(ParameterAccessor accessor) {
if (accessor.getPageable() != null) {
return query.toString(accessor.getPageable());
} else if (accessor.getSort() != null) {

View File

@@ -0,0 +1,128 @@
/**
* Copyright 2011 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.data.neo4j.repository.query;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.support.GenericTypeExtractor;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @author mh
* @since 31.10.11
*/
public class GraphQueryMethod extends QueryMethod {
private final Method method;
private final NamedQueries namedQueries;
private final Neo4jMappingContext mappingContext;
private final Query queryAnnotation;
public GraphQueryMethod(Method method, RepositoryMetadata metadata, NamedQueries namedQueries, Neo4jMappingContext mappingContext) {
super(method, metadata);
this.method = method;
this.namedQueries = namedQueries;
this.mappingContext = mappingContext;
this.queryAnnotation = method.getAnnotation(Query.class);
}
public String getQueryString() {
return queryAnnotation != null ? queryAnnotation.value() : getNamedQuery();
}
public boolean isValid() {
return this.getQueryString() != null; // && this.compoundType != null
}
private String getNamedQuery() {
final String namedQueryName = getNamedQueryName();
if (namedQueries.hasQuery(namedQueryName)) {
return namedQueries.getQuery(namedQueryName);
}
return null;
}
public Class<?> getReturnType() {
return method.getReturnType();
}
protected Map<String, Object> resolveParams(ParameterAccessor accessor, ParameterResolver parameterResolver) {
Map<String, Object> params = new HashMap<String, Object>();
for (Parameter parameter : getParameters().getBindableParameters()) {
final Object value = accessor.getBindableValue(parameter.getIndex());
final String parameterName = getParameterName(parameter);
params.put(parameterName, parameterResolver.resolveParameter(value, parameterName, parameter.getIndex()));
}
return params;
}
private String getParameterName(Parameter parameter) {
final String parameterName = parameter.getName();
if (parameterName != null) {
return parameterName;
}
return String.format(QueryTemplates.PARAMETER, parameter.getIndex());
}
Class<?> getCompoundType() {
final Class<?> elementClass = getElementClass();
if (elementClass!=null) {
return elementClass;
}
return GenericTypeExtractor.resolveReturnedType(method);
}
private Class<?> getElementClass() {
if (!hasAnnotation() || queryAnnotation.elementClass().equals(Object.class)) {
return null;
}
return queryAnnotation.elementClass();
}
public boolean hasAnnotation() {
return queryAnnotation!=null;
}
boolean isIterableResult() {
return Iterable.class.isAssignableFrom(getReturnType());
}
public RepositoryQuery createQuery(final Neo4jTemplate template) {
if (queryAnnotation == null) {
if (namedQueries.hasQuery(getNamedQueryName())) {
return new CypherGraphRepositoryQuery(this, template); // cypher is default for named queries
} else {
return new DerivedCypherRepositoryQuery(mappingContext, this, template);
}
}
switch (queryAnnotation.type()) {
case Cypher:
return new CypherGraphRepositoryQuery(this, template);
case Gremlin:
return new GremlinGraphRepositoryQuery(this, template);
default:
throw new IllegalStateException("@Query Annotation has to be configured as Cypher or Gremlin Query");
}
}
}

View File

@@ -0,0 +1,114 @@
/**
* Copyright 2011 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.data.neo4j.repository.query;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.helpers.collection.IteratorUtil;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.query.QueryEngine;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author mh
* @since 31.10.11
*/
abstract class GraphRepositoryQuery implements RepositoryQuery, ParameterResolver {
private final GraphQueryMethod queryMethod;
private final Neo4jTemplate template;
public GraphRepositoryQuery(GraphQueryMethod queryMethod, final Neo4jTemplate template) {
Assert.notNull(queryMethod);
Assert.notNull(template);
this.queryMethod = queryMethod;
this.template = template;
}
protected Neo4jTemplate getTemplate() {
return template;
}
public Object resolveParameter(Object value, String parameterName, int index) {
final Class<?> type = value.getClass();
if (template.isNodeEntity(type)) {
final Node state = template.getPersistentState(value);
if (state != null) return state.getId();
}
if (template.isRelationshipEntity(type)) {
final Relationship state = template.getPersistentState(value);
if (state != null) return state.getId();
}
return value;
}
@Override
public Object execute(Object[] parameters) {
final ParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
Map<String, Object> params = resolveParams(accessor);
final String queryString = createQueryWithPagingAndSorting(accessor);
return dispatchQuery(queryString, params, accessor);
}
protected Map<String, Object> resolveParams(ParameterAccessor accessor) {
return queryMethod.resolveParams(accessor, this);
}
protected String createQueryWithPagingAndSorting(ParameterAccessor accessor) {
return queryMethod.getQueryString();
}
@SuppressWarnings("unchecked")
protected Object dispatchQuery(String queryString, Map<String, Object> params, ParameterAccessor accessor) {
GraphQueryMethod queryMethod = getQueryMethod();
final QueryEngine<?> queryEngine = getQueryEngine();
final Class<?> compoundType = queryMethod.getCompoundType();
if (queryMethod.isPageQuery()) {
@SuppressWarnings("unchecked") final Iterable<?> result = queryEngine.query(queryString, params).to(compoundType);
return createPage(result, accessor.getPageable());
}
if (queryMethod.isIterableResult()) {
return queryEngine.query(queryString, params).to(compoundType);
}
return queryEngine.query(queryString, params).to(queryMethod.getReturnType()).singleOrNull();
}
@Override
public GraphQueryMethod getQueryMethod() {
return queryMethod;
}
@SuppressWarnings({"unchecked", "rawtypes"})
protected Object createPage(Iterable<?> result, Pageable pageable) {
final List resultList = IteratorUtil.addToCollection(result, new ArrayList());
if (pageable==null) {
return new PageImpl(resultList);
}
final int currentTotal = pageable.getOffset() + pageable.getPageSize();
return new PageImpl(resultList, pageable, currentTotal);
}
protected abstract QueryEngine<?> getQueryEngine();
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright 2011 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.data.neo4j.repository.query;
import org.springframework.data.domain.Pageable;
import org.springframework.data.neo4j.annotation.QueryType;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.query.QueryEngine;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.ParameterAccessor;
import java.util.Map;
/**
* @author mh
* @since 31.10.11
*/
class GremlinGraphRepositoryQuery extends GraphRepositoryQuery {
private QueryEngine queryEngine;
public GremlinGraphRepositoryQuery(GraphQueryMethod queryMethod, final Neo4jTemplate template) {
super(queryMethod, template);
}
protected QueryEngine getQueryEngine() {
if (this.queryEngine !=null) return queryEngine;
this.queryEngine = getTemplate().queryEngineFor(QueryType.Gremlin);
return this.queryEngine;
}
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright 2011 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.data.neo4j.repository.query;
/**
* @author mh
* @since 31.10.11
*/
public interface ParameterResolver {
Object resolveParameter(Object value, String parameterName, int index);
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright 2011 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.data.neo4j.repository.query;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.neo4j.mapping.IndexInfo;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.util.Assert;
/**
* @author mh
* @since 31.10.11
*/
public class PartInfo {
private final PersistentPropertyPath<Neo4jPersistentProperty> path;
private final String variable;
private final Part part;
private final int index;
public PartInfo(PersistentPropertyPath<Neo4jPersistentProperty> path, String variable, Part part, int index) {
Assert.notNull(path);
Assert.hasText(variable);
this.path = path;
this.variable = variable;
this.part = part;
this.index = index;
}
protected Part.Type getType() {
return this.part.getType();
}
Neo4jPersistentProperty getLeafProperty() {
return path.getLeafProperty();
}
public boolean isPrimitiveProperty() {
return !isRelationship();
}
private boolean isRelationship() {
return getLeafProperty().isRelationship();
}
public boolean isIndexed() {
return getLeafProperty().isIndexed();
}
public String getVariable() {
return variable;
}
public int getParameterIndex() {
return index;
}
public String getIndexName() {
return getIndexInfo().getIndexName();
}
public boolean isFullText() {
return isIndexed() && getIndexInfo().isFullText();
}
private IndexInfo getIndexInfo() {
return getLeafProperty().getIndexInfo();
}
String getNeo4jPropertyName() {
Neo4jPersistentProperty leafProperty = getLeafProperty();
return leafProperty.getNeo4jPropertyName();
}
public String getIndexKey() {
return getIndexInfo().getIndexKey();
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.data.neo4j.mapping.RelationshipInfo;
public abstract class QueryTemplates {
public static final String PARAMETER = "_%d";
public static final String PARAMETER_INDEX_QUERY = "%s:%s";
public static final String PLACEHOLDER = String.format("{%s}", PARAMETER);
private static final String DIRECTION_INCOMING = "<-[:%s]-";
@@ -35,7 +36,9 @@ public abstract class QueryTemplates {
static final String DEFAULT_START_CLAUSE = "%s=node:__types__(className=\"%s\")";
static final String SKIP_LIMIT = " skip %d limit %d";
static final String START_CLAUSE = "%s=node:%s(%s=" + PLACEHOLDER + ")";
static final String WHERE_CLAUSE = "%s.%s %s " + PLACEHOLDER;
static final String START_CLAUSE_FULLTEXT = "%s=node:%s(" + PLACEHOLDER + ")";
static final String WHERE_CLAUSE_1 = "%s.%s %s " + PLACEHOLDER;
static final String WHERE_CLAUSE_0 = "%s.%s %s ";
static final String SORT_CLAUSE = "%s %s";
static final String ORDER_BY_CLAUSE = " order by %s";

View File

@@ -15,9 +15,7 @@
*/
package org.springframework.data.neo4j.repository.query;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.util.Assert;
/**
* Representation of a Cypher {@literal start} clause.
@@ -26,26 +24,16 @@ import org.springframework.util.Assert;
*/
class StartClause {
private final PersistentPropertyPath<Neo4jPersistentProperty> path;
private final String variable;
private final int index;
private final PartInfo partInfo;
/**
* Creates a new {@link StartClause} from the given {@link Neo4jPersistentProperty}, variable and the given
* parameter index.
*
* @param property must not be {@literal null}.
* @param variable must not be {@literal null} or empty.
* @param index
* @param partInfo
*/
public StartClause(PersistentPropertyPath<Neo4jPersistentProperty> property, String variable, int index) {
Assert.notNull(property);
Assert.hasText(variable);
this.path = property;
this.variable = variable;
this.index = index;
public StartClause(PartInfo partInfo) {
this.partInfo = partInfo;
}
/*
@@ -54,11 +42,16 @@ class StartClause {
*/
@Override
public String toString() {
final String variable = partInfo.getVariable();
final String indexName = partInfo.getIndexName();
final int parameterIndex = partInfo.getParameterIndex();
if (partInfo.isFullText()) {
return String.format(QueryTemplates.START_CLAUSE_FULLTEXT, variable, indexName, parameterIndex);
}
return String.format(QueryTemplates.START_CLAUSE, variable, indexName, partInfo.getNeo4jPropertyName(), parameterIndex);
}
Neo4jPersistentProperty leafProperty = path.getLeafProperty();
String indexName = leafProperty.getIndexInfo().getIndexName();
String propertyName = leafProperty.getNeo4jPropertyName();
return String.format(QueryTemplates.START_CLAUSE, variable, indexName, propertyName, index);
public PartInfo getPartInfo() {
return partInfo;
}
}

View File

@@ -24,6 +24,9 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.util.Assert;
import static org.springframework.data.neo4j.repository.query.QueryTemplates.WHERE_CLAUSE_0;
import static org.springframework.data.neo4j.repository.query.QueryTemplates.WHERE_CLAUSE_1;
/**
* Representation of a Cypher {@literal where} clause.
*
@@ -42,34 +45,24 @@ class WhereClause {
symbols.put(Type.LESS_THAN_EQUAL, "<=");
symbols.put(Type.NEGATING_SIMPLE_PROPERTY, "!=");
symbols.put(Type.SIMPLE_PROPERTY, "=");
symbols.put(Type.LIKE, "=~"); // n.name =~ /Tob.*/
//symbols.put(Type.EXISTS, ""); // property exists n.name
// compare when exists WHERE n.belt? = 'white'
symbols.put(Type.IS_NULL, "is null"); // WHERE r is null
SYMBOLS = Collections.unmodifiableMap(symbols);
}
private final PersistentPropertyPath<Neo4jPersistentProperty> path;
private final String variable;
private final Type type;
private final int index;
private final PartInfo partInfo;
/**
* Creates a new {@link WhereClause} for the given {@link Neo4jPersistentProperty}, variable, type and parameter
* index.
*
* @param path must not be {@literal null}.
* @param variable must not be {@literal null} or empty.
* @param type must not be {@literal null}.
* @param index
* @param partInfo
*/
public WhereClause(PersistentPropertyPath<Neo4jPersistentProperty> path, String variable, Type type, int index) {
Assert.notNull(path);
Assert.hasText(variable);
Assert.notNull(type);
this.path = path;
this.variable = variable;
this.type = type;
this.index = index;
public WhereClause(PersistentPropertyPath<Neo4jPersistentProperty> path, String variable, Type type, int index, PartInfo partInfo) {
Assert.notNull(partInfo.getType());
this.partInfo = partInfo;
}
/*
@@ -78,7 +71,17 @@ class WhereClause {
*/
@Override
public String toString() {
return String.format(QueryTemplates.WHERE_CLAUSE, variable, path.getLeafProperty().getNeo4jPropertyName(),
SYMBOLS.get(type), index);
final String propertyName = partInfo.getNeo4jPropertyName();
final String operator = SYMBOLS.get(partInfo.getType());
final String variable = partInfo.getVariable();
if (partInfo.getType().getNumberOfArguments()==0) {
return String.format(WHERE_CLAUSE_0, variable, propertyName, operator);
}
return String.format(WHERE_CLAUSE_1, variable, propertyName, operator, partInfo.getParameterIndex());
}
public PartInfo getPartInfo() {
return partInfo;
}
}

View File

@@ -38,15 +38,11 @@ import org.springframework.data.neo4j.support.index.IndexType;
import org.springframework.data.neo4j.support.mapping.EntityStateHandler;
import org.springframework.data.neo4j.support.mapping.Neo4jPersistentEntityImpl;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.mapping.RelationshipResult;
import org.springframework.data.neo4j.repository.GraphRepository;
import org.springframework.data.neo4j.repository.NodeGraphRepository;
import org.springframework.data.neo4j.repository.RelationshipGraphRepository;
import org.springframework.data.neo4j.support.index.IndexProvider;
import org.springframework.data.neo4j.support.index.IndexType;
import org.springframework.data.neo4j.support.mapping.EntityCreatingClosableIterable;
import org.springframework.data.neo4j.support.mapping.EntityStateHandler;
import org.springframework.data.neo4j.support.mapping.Neo4jPersistentEntityImpl;
import org.springframework.data.neo4j.support.query.QueryEngine;
import org.springframework.data.neo4j.template.GraphCallback;
import org.springframework.data.neo4j.template.Neo4jOperations;
@@ -570,7 +566,7 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister {
}
public String getIndexKey(Neo4jPersistentProperty property) {
return getIndexProvider().getIndexKey(property);
return property.getIndexKey();
}
public <S extends PropertyContainer> Index<S> getIndex(Neo4jPersistentProperty property, final Class<?> instanceType) {
return getIndexProvider().getIndex(property, instanceType);

View File

@@ -88,12 +88,6 @@ public class IndexProvider {
return graphDatabase.createIndex(type, indexName, fullText);
}
public String getIndexKey(Neo4jPersistentProperty property) {
Indexed indexed = property.getAnnotation(Indexed.class);
if (indexed==null || indexed.fieldName().isEmpty()) return property.getNeo4jPropertyName();
return indexed.fieldName();
}
public <S extends PropertyContainer> Index<S> getIndex(Neo4jPersistentProperty property, final Class<?> instanceType) {
final Indexed indexedAnnotation = property.getAnnotation(Indexed.class);
final Class<?> declaringType = property.getOwner().getType();

View File

@@ -214,4 +214,8 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty<Neo4jPersis
private boolean hasNodeEntityType() {
return getType().isAnnotationPresent(NodeEntity.class);
}
public String getIndexKey() {
return getIndexInfo().getIndexKey();
}
}

View File

@@ -40,7 +40,8 @@ import org.springframework.util.ObjectUtils;
public class Group {
public final static String OTHER_NAME_INDEX = "other_name";
public static final String SEARCH_GROUPS_INDEX = "search-groups";
public static final String SEARCH_GROUPS_INDEX = "search_groups";
public static final String SEARCH_GROUPS_INDEX_BUG = "search-groups";
@RelatedTo(direction = Direction.OUTGOING)
private Collection<Person> persons = new HashSet<Person>();
@@ -68,6 +69,10 @@ public class Group {
@Indexed(indexName = SEARCH_GROUPS_INDEX, indexType = IndexType.FULLTEXT)
private String fullTextName;
@GraphProperty
@Indexed(indexName = SEARCH_GROUPS_INDEX_BUG, indexType = IndexType.FULLTEXT)
private String fullTextNameBug;
@Indexed(fieldName = OTHER_NAME_INDEX)
private String otherName;
@@ -80,6 +85,14 @@ public class Group {
@Indexed(level = Indexed.Level.INSTANCE)
private String indexLevelName;
public String getFullTextNameBug() {
return fullTextNameBug;
}
public void setFullTextNameBug(String fullTextNameBug) {
this.fullTextNameBug = fullTextNameBug;
}
@GraphId
private Long id;

View File

@@ -18,7 +18,9 @@ package org.springframework.data.neo4j.repository;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.ejb.criteria.expression.function.AggregationFunction;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -37,7 +39,11 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.HashSet;
import java.util.Map;
@@ -55,7 +61,6 @@ import static org.neo4j.helpers.collection.IteratorUtil.asCollection;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
@Transactional
public class GraphRepositoryTest {
protected final Log log = LogFactory.getLog(getClass());
@@ -63,6 +68,9 @@ public class GraphRepositoryTest {
@Autowired
private Neo4jTemplate neo4jTemplate;
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private PersonRepository personRepository;
@Autowired
@@ -85,36 +93,82 @@ public class GraphRepositoryTest {
}
@Test
public void deleteAll() {
assertThat(personRepository.count(), is(3L));
new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
personRepository.deleteAll();
}
});
assertThat(personRepository.count(), is(0L));
}
@Test
public void deleteCollection() {
assertThat(personRepository.count(), is(3L));
new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
personRepository.delete(asList(testTeam.michael,testTeam.david));
}
});
assertThat(personRepository.count(), is(1L));
}
@Test
public void deleteById() {
final Long id = testTeam.michael.getId();
new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
personRepository.delete(id);
}
});
assertThat(personRepository.exists(id), is(false));
}
@Test
public void deleteSingle() {
new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
personRepository.delete(testTeam.michael);
}
});
assertThat(personRepository.exists(testTeam.michael.getId()), is(false));
}
@Test @Transactional
public void testFindIterableOfPersonWithQueryAnnotation() {
Iterable<Person> teamMembers = personRepository.findAllTeamMembers(testTeam.sdg);
assertThat( asCollection( teamMembers ), hasItems( testTeam.michael, testTeam.david, testTeam.emil ) );
}
@Test
@Test @Transactional
public void testFindIterableOfPersonWithQueryAnnotationSpatial() {
Iterable<Person> teamMembers = personRepository.findWithinBoundingBox("personLayer", 55, 15, 57, 17);
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david));
}
@Test
@Test @Transactional
public void testFindIterableOfPersonWithQueryAnnotationAndGremlin() {
Iterable<Person> teamMembers = personRepository.findAllTeamMembersGremlin( testTeam.sdg );
assertThat( asCollection( teamMembers ), hasItems( testTeam.michael, testTeam.david, testTeam.emil ) );
}
@Test
@Test @Transactional
public void testFindPersonWithQueryAnnotation() {
Person boss = personRepository.findBoss( testTeam.michael );
assertThat(boss, is( testTeam.emil ));
}
@Test
@Test @Transactional
public void testFindPersonWithQueryAnnotationUsingLongAsParameter() {
Person boss = personRepository.findBoss( testTeam.michael.getId() );
assertThat(boss, is( testTeam.emil ));
}
@Test
@Test @Transactional
public void shouldBeAbleToTurnQueryResultsToAMapResultInterface() throws Exception {
MemberData first = personRepository.findMemberData(testTeam.michael).iterator().next();
@@ -122,20 +176,29 @@ public class GraphRepositoryTest {
assertThat(asCollection(first.getTeams()), hasItem(testTeam.sdg));
}
@Test
@Test @Transactional
public void testFindIterableMapsWithQueryAnnotation() {
Iterable<Map<String, Object>> teamMembers = personRepository.findAllTeamMemberData(testTeam.sdg);
assertThat(asCollection(teamMembers), hasItems(testTeam.simpleRowFor(testTeam.michael, "member"), testTeam.simpleRowFor(testTeam.david, "member"), testTeam.simpleRowFor(testTeam.emil, "member")));
}
@Test
@Test @Transactional
@Ignore("untyil cypher supports parameters in path's and skip, limit")
public void testFindWithMultipleParameters() {
final int depth = 1;
final int limit = 2;
Iterable<Person> teamMembers = personRepository.findSomeTeamMembers(testTeam.sdg.getName(), 0, limit, depth);
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david));
}
@Test @Transactional
public void testFindPaged() {
final PageRequest page = new PageRequest(0, 1, Sort.Direction.ASC, "member.name");
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg, page);
assertThat(teamMemberPage1, hasItem(testTeam.david));
}
@Test
@Test @Transactional
public void testFindPagedDescending() {
final PageRequest page = new PageRequest(0, 2, Sort.Direction.DESC, "member.name");
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg, page);
@@ -143,7 +206,7 @@ public class GraphRepositoryTest {
assertThat(teamMemberPage1.isFirstPage(), is(true));
}
@Test
@Test @Transactional
public void testFindPagedNull() {
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg, null);
assertEquals(new HashSet(asList(testTeam.david, testTeam.emil, testTeam.michael)), addToCollection(teamMemberPage1, new HashSet()));
@@ -151,34 +214,46 @@ public class GraphRepositoryTest {
assertThat(teamMemberPage1.isLastPage(), is(false));
}
@Test
@Test @Transactional
public void testFindSortedDescending() {
final Sort sort = new Sort(Sort.Direction.DESC, "member.name");
Iterable<Person> teamMembers = personRepository.findAllTeamMembersSorted(testTeam.sdg, sort);
assertEquals(asList(testTeam.michael, testTeam.emil, testTeam.david), asCollection(teamMembers));
}
@Test
@Test @Transactional
public void testFindSortedNull() {
Iterable<Person> teamMembers = personRepository.findAllTeamMembersSorted(testTeam.sdg, null);
assertThat(teamMembers, hasItems(testTeam.michael, testTeam.emil, testTeam.david));
}
@Test
@Test @Transactional
public void testFindByNamedQuery() {
Group team = personRepository.findTeam(testTeam.michael);
assertThat(team, is(testTeam.sdg));
}
@Test
@Test @Transactional
public void findByName() {
Iterable<Person> findByName = personRepository.findByName(testTeam.michael.getName());
assertThat(findByName, hasItem(testTeam.michael));
}
@Test( expected = NoSuchColumnFoundException.class)
@Test( expected = NoSuchColumnFoundException.class) @Transactional
public void missingColumnIsReportedNicely() {
Iterable<MemberData> findByName = personRepository.nonWorkingQuery( testTeam.michael );
Person boss = findByName.iterator().next().getBoss();
}
@Test @Transactional
public void findByFullTextName() {
testTeam.sdg.setFullTextName("test");
neo4jTemplate.save(testTeam.sdg);
final Iterable<Group> groups = groupRepository.findByFullTextNameLike("te*");
assertThat(groups, hasItem(testTeam.sdg));
}
@Test @Transactional
public void findPageByName() {
final Iterable<Group> groups = groupRepository.findByName(testTeam.sdg.getName(), new PageRequest(0, 1));
assertThat(groups, hasItem(testTeam.sdg));
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.data.neo4j.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.neo4j.model.Group;
@@ -24,4 +26,6 @@ import org.springframework.data.neo4j.model.Group;
* @since 29.03.11
*/
public interface GroupRepository extends GraphRepository<Group>, NamedIndexRepository<Group> {
Iterable<Group> findByFullTextNameLike(String name);
Page<Group> findByName(String name, Pageable page);
}

View File

@@ -51,6 +51,8 @@ public interface PersonRepository extends GraphRepository<Person>, NamedIndexRep
@Query("start member=node({p_person}) match team-[:persons]->member<-[?:boss]-boss return member")
Iterable<MemberData> nonWorkingQuery(@Param("p_person") Person person);
@Query("start team=node:Group(name = {p_team}) match (team)-[:persons*1..1]->(member) return member.name,member.age skip {skip} limit {limit}")
Iterable<Person> findSomeTeamMembers(@Param("p_team") String team, @Param("skip") Integer skip,@Param("limit") Integer limit,@Param("depth") Integer depth);
@Query("start person=node({p_person}) match (boss)-[:boss]->(person) return boss")
Person findBoss(@Param("p_person") Person person);

View File

@@ -34,7 +34,8 @@ import org.springframework.data.repository.query.parser.Part;
public class CypherQueryBuilderUnitTests {
CypherQueryBuilder query;
private final String className = Person.class.getName();
private final String CLASS_NAME = Person.class.getName();
private String DEFAULT_START_CLAUSE = "start person=node:__types__(className=\"" + CLASS_NAME + "\")";
@Before
public void setUp() {
@@ -51,6 +52,40 @@ public class CypherQueryBuilderUnitTests {
assertThat(query.toString(), is("start person=node:Person(name={_0}) return person"));
}
@Test
public void createsQueryForLikePropertyIndex() {
Part part = new Part("titleLike", Person.class);
query.addRestriction(part);
assertThat(query.toString(), is("start person=node:title({_0}) return person"));
}
@Test
public void createsQueryForLikeProperty() {
Part part = new Part("infoLike", Person.class);
query.addRestriction(part);
assertThat(query.toString(), is(DEFAULT_START_CLAUSE+" where person.info =~ {_0} return person"));
}
@Test
public void createsQueryForGreaterThanPropertyReference() {
Part part = new Part("ageGreaterThan", Person.class);
query.addRestriction(part);
assertThat(query.toString(), is(DEFAULT_START_CLAUSE+" where person.age > {_0} return person"));
}
@Test
public void createsQueryForIsNullPropertyReference() {
Part part = new Part("ageIsNull", Person.class);
query.addRestriction(part);
assertThat(query.toString(), is(DEFAULT_START_CLAUSE+" where person.age is null return person"));
}
@Test
public void createsQueryForPropertyOnRelationShipReference() {
@@ -76,13 +111,12 @@ public class CypherQueryBuilderUnitTests {
query.addRestriction(new Part("age", Person.class));
final String className = Person.class.getName();
assertThat(query.toString(), is("start person=node:__types__(className=\"" + className + "\") where person.age = {_0} return person"));
assertThat(query.toString(), is(DEFAULT_START_CLAUSE +" where person.age = {_0} return person"));
}
@Test
public void createsSimpleTraversalClauseCorrectly() {
query.addRestriction(new Part("group", Person.class));
assertThat(query.toString(), is("start person=node:__types__(className=\"" + className + "\") match person<-[:members]-person_group return person"));
assertThat(query.toString(), is(DEFAULT_START_CLAUSE + " match person<-[:members]-person_group return person"));
}

View File

@@ -20,6 +20,7 @@ import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.Indexed;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.annotation.RelatedTo;
import org.springframework.data.neo4j.support.index.IndexType;
@NodeEntity
class Person {
@@ -28,6 +29,12 @@ class Person {
@Indexed
String name;
@Indexed(indexType = IndexType.FULLTEXT,indexName = "title")
String title;
String info;
int age;
@RelatedTo(type = "members", direction = Direction.INCOMING)

View File

@@ -9,8 +9,8 @@
to a remote database service instead. Neo4j can also run as a server. It exposes its operations via a HTTP
based REST API.
</para>
<para>
We decided to have a look to be at least knowledgeable about this deployment scenario. We were aware of the
<para>
We decided to have a look, to be at least knowledgeable about this deployment scenario. We were aware of the
difference of local, in-memory calls and higher latency network hops. That would be something
we would also look out for.
</para>