very large changeset
repackaged datastore-graph added support for keeping state outside of the transaction + dirty handling to the Neo4jNodeBacked Aspect and flushing it on reconnect added transaction handling for the EntityManager had to add handling for mapped classes -> add them to persistence.xml and use as lookup list for query target add Query support, including first and maxresults issues with EM creation + JPADialect
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package org.springframework.datastore.graph.neo4j.fieldaccess;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
@@ -23,6 +24,7 @@ public class FieldAccessorFactory {
|
||||
}
|
||||
|
||||
public FieldAccessor forField(Field field) {
|
||||
if (Modifier.isTransient(field.getModifiers())) return null;
|
||||
GraphEntityRelationship relAnnotation = field.getAnnotation(GraphEntityRelationship.class);
|
||||
if (isSingleRelationshipField(field)) {
|
||||
Class<? extends NodeBacked> relatedType = (Class<? extends NodeBacked>) field.getType();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.springframework.datastore.graph.neo4j.finder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
@@ -30,6 +31,7 @@ public class Finder<T extends NodeBacked> {
|
||||
|
||||
public Iterable<T> findAll() {
|
||||
Node subrefNode = Neo4jHelper.findSubreferenceNode(clazz, graphDatabaseService);
|
||||
if (subrefNode==null) return Collections.emptyList();
|
||||
// TODO add lazy list on top of graph
|
||||
List<T> result = new ArrayList<T>((int) count());
|
||||
for (Relationship rel : subrefNode.getRelationships(Neo4jHelper.INSTANCE_OF_RELATIONSHIP_TYPE, Direction.INCOMING)) {
|
||||
|
||||
@@ -3,20 +3,29 @@ package org.springframework.datastore.graph.neo4j.finder;
|
||||
import org.neo4j.graphdb.GraphDatabaseService;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.springframework.datastore.graph.api.NodeBacked;
|
||||
import org.springframework.datastore.graph.neo4j.spi.node.Neo4jHelper;
|
||||
import org.springframework.persistence.support.EntityInstantiator;
|
||||
|
||||
public class FinderFactory {
|
||||
|
||||
private final GraphDatabaseService graphDatabaseService;
|
||||
private final EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
|
||||
public FinderFactory(GraphDatabaseService graphDatabaseService, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
|
||||
this.graphDatabaseService = graphDatabaseService;
|
||||
this.graphEntityInstantiator = graphEntityInstantiator;
|
||||
}
|
||||
private final GraphDatabaseService graphDatabaseService;
|
||||
private final EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
|
||||
public <T extends NodeBacked> Finder<T> getFinderForClass(Class<T> clazz) {
|
||||
return new Finder<T>(clazz, graphDatabaseService, graphEntityInstantiator);
|
||||
}
|
||||
public FinderFactory(GraphDatabaseService graphDatabaseService, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
|
||||
this.graphDatabaseService = graphDatabaseService;
|
||||
this.graphEntityInstantiator = graphEntityInstantiator;
|
||||
}
|
||||
|
||||
public <T extends NodeBacked> Finder<T> getFinderForClass(Class<T> clazz) {
|
||||
return new Finder<T>(clazz, graphDatabaseService, graphEntityInstantiator);
|
||||
}
|
||||
|
||||
public Class<NodeBacked> getEntityClass(String shortName) {
|
||||
final String className = Neo4jHelper.getClassNameForShortName(graphDatabaseService, shortName);
|
||||
try {
|
||||
return (Class<NodeBacked>) Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException("Unable to find class for " + shortName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package org.springframework.datastore.graph.neo4j.jpa;
|
||||
|
||||
import org.springframework.datastore.graph.api.NodeBacked;
|
||||
import org.springframework.datastore.graph.neo4j.finder.Finder;
|
||||
import org.springframework.datastore.graph.neo4j.finder.FinderFactory;
|
||||
|
||||
import javax.persistence.FlushModeType;
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.TemporalType;
|
||||
import javax.persistence.spi.PersistenceUnitInfo;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Michael Hunger
|
||||
* @since 29.08.2010
|
||||
*/
|
||||
public class Neo4JQuery implements Query {
|
||||
protected final Finder<? extends NodeBacked> finder;
|
||||
protected final Class<? extends NodeBacked> entityClass;
|
||||
protected final String qlString;
|
||||
private final PersistenceUnitInfo info;
|
||||
private final Pattern fromPattern = Pattern.compile("^.*\\bfrom\\s+([A-Z][A-Za-z0-9]+)\\b.*");
|
||||
private int startPosition=0;
|
||||
private int maxResult=-1;
|
||||
private QueryExectuor queryExectuor;
|
||||
|
||||
public Neo4JQuery(final String qlString, final FinderFactory finderFactory, final PersistenceUnitInfo info) {
|
||||
this.qlString = qlString;
|
||||
this.info = info;
|
||||
final Matcher matcher = fromPattern.matcher(qlString);
|
||||
if (matcher.matches()) {
|
||||
final String shortName = matcher.group(1);
|
||||
entityClass=getEntityClass(shortName);
|
||||
finder = finderFactory.getFinderForClass(entityClass);
|
||||
queryExectuor = createExecutor(qlString);
|
||||
} else {
|
||||
throw new IllegalAccessError("Unable to parse query "+qlString);
|
||||
}
|
||||
}
|
||||
|
||||
private QueryExectuor createExecutor(String qlString) {
|
||||
if (qlString.contains(" count(")) return new QueryExectuor() {
|
||||
@Override
|
||||
protected Iterable<?> findList() {
|
||||
return Collections.singleton(finder.count());
|
||||
}
|
||||
};
|
||||
return new QueryExectuor() {
|
||||
@Override
|
||||
protected Iterable<?> findList() {
|
||||
return finder.findAll();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
abstract static class QueryExectuor {
|
||||
protected abstract Iterable<?> findList();
|
||||
|
||||
}
|
||||
|
||||
private Class<NodeBacked> getEntityClass(final String shortName) {
|
||||
try {
|
||||
final String className = getFQN(shortName);
|
||||
return (Class<NodeBacked>) Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException("Error resolving class "+shortName,e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getFQN(final String shortName) throws ClassNotFoundException {
|
||||
for (final String className : info.getManagedClassNames()) {
|
||||
if (className.endsWith(shortName)) return className;
|
||||
}
|
||||
throw new ClassNotFoundException("No mapped class found for "+shortName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List getResultList() {
|
||||
final List<Object> result = new ArrayList<Object>();
|
||||
int count=0;
|
||||
for (final Object nodeBacked : queryExectuor.findList()) {
|
||||
if (maxResult>=0 && count==startPosition+maxResult) break;
|
||||
if (count>=startPosition) {
|
||||
result.add(nodeBacked);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSingleResult() {
|
||||
final Iterator<?> found = queryExectuor.findList().iterator();
|
||||
return found.hasNext() ? found.next() : null; // todo errors when none or too many ?
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeUpdate() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setMaxResults(final int maxResult) {
|
||||
this.maxResult = maxResult;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setFirstResult(final int startPosition) {
|
||||
this.startPosition = startPosition;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setHint(final String hintName, final Object value) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setParameter(final String name, final Object value) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setParameter(final String name, final Date value, final TemporalType temporalType) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setParameter(final String name, final Calendar value, final TemporalType temporalType) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setParameter(final int position, final Object value) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setParameter(final int position, final Date value, final TemporalType temporalType) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setParameter(final int position, final Calendar value, final TemporalType temporalType) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query setFlushMode(final FlushModeType flushMode) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,20 @@
|
||||
package org.springframework.datastore.graph.neo4j.jpa;
|
||||
|
||||
import org.neo4j.graphdb.GraphDatabaseService;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
import org.neo4j.graphdb.*;
|
||||
import org.neo4j.graphdb.Transaction;
|
||||
import org.neo4j.kernel.EmbeddedGraphDatabase;
|
||||
import org.springframework.beans.factory.annotation.Configurable;
|
||||
import org.springframework.datastore.graph.api.NodeBacked;
|
||||
import org.springframework.datastore.graph.neo4j.finder.FinderFactory;
|
||||
import org.springframework.persistence.support.EntityInstantiator;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.persistence.*;
|
||||
import javax.persistence.spi.PersistenceUnitInfo;
|
||||
import javax.transaction.*;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Michael Hunger
|
||||
@@ -17,20 +22,27 @@ import javax.transaction.*;
|
||||
* TODO Relationships
|
||||
*/
|
||||
//@Service
|
||||
@Transactional
|
||||
@Configurable
|
||||
public class Neo4jEntityManager implements EntityManager {
|
||||
@Resource
|
||||
GraphDatabaseService graphDatabaseService;
|
||||
|
||||
@Resource
|
||||
EntityInstantiator<NodeBacked, Node> nodeInstantiator;
|
||||
private volatile boolean closed;
|
||||
private PersistenceUnitInfo info;
|
||||
|
||||
public Neo4jEntityManager(final GraphDatabaseService graphDatabaseService, final EntityInstantiator<NodeBacked, Node> nodeInstantiator) {
|
||||
private Map params;
|
||||
private volatile boolean closed;
|
||||
private final FinderFactory finderFactory;
|
||||
|
||||
public Neo4jEntityManager(final GraphDatabaseService graphDatabaseService, final EntityInstantiator<NodeBacked, Node> nodeInstantiator, PersistenceUnitInfo info, Map params) {
|
||||
this.graphDatabaseService = graphDatabaseService;
|
||||
this.nodeInstantiator = nodeInstantiator;
|
||||
this.info = info;
|
||||
this.params = params;
|
||||
finderFactory = new FinderFactory(graphDatabaseService, nodeInstantiator);
|
||||
}
|
||||
|
||||
public Neo4jEntityManager() {
|
||||
finderFactory = new FinderFactory(graphDatabaseService, nodeInstantiator);
|
||||
}
|
||||
|
||||
private Node nodeFor(final Object entity) {
|
||||
@@ -44,6 +56,13 @@ public class Neo4jEntityManager implements EntityManager {
|
||||
@Override
|
||||
public void persist(final Object entity) {
|
||||
checkClosed();
|
||||
final Transaction tx = graphDatabaseService.beginTx();
|
||||
try {
|
||||
|
||||
tx.success();
|
||||
} finally {
|
||||
tx.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,6 +121,7 @@ public class Neo4jEntityManager implements EntityManager {
|
||||
@Override
|
||||
public void refresh(final Object entity) {
|
||||
nodeFor(entity);
|
||||
// todo NodeBacked.refresh -> discard dirty
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,7 +149,7 @@ public class Neo4jEntityManager implements EntityManager {
|
||||
@Override
|
||||
public Query createQuery(final String qlString) {
|
||||
checkClosed();
|
||||
throw new UnsupportedOperationException();
|
||||
return new Neo4JQuery(qlString, finderFactory,info);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -209,4 +229,5 @@ public class Neo4jEntityManager implements EntityManager {
|
||||
return new Neo4jEntityTransaction(transactionManager);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.springframework.persistence.support.EntityInstantiator;
|
||||
import javax.annotation.Resource;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.spi.PersistenceUnitInfo;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -15,28 +16,27 @@ import java.util.Map;
|
||||
* @since 23.08.2010
|
||||
*/
|
||||
public class Neo4jEntityManagerFactory implements EntityManagerFactory {
|
||||
@Resource
|
||||
GraphDatabaseService graphDatabaseService;
|
||||
@Resource
|
||||
EntityInstantiator<NodeBacked, Node> nodeInstantiator;
|
||||
private PersistenceUnitInfo info;
|
||||
private Map params;
|
||||
|
||||
public Neo4jEntityManagerFactory() {
|
||||
}
|
||||
|
||||
public Neo4jEntityManagerFactory(GraphDatabaseService graphDatabaseService, EntityInstantiator<NodeBacked, Node> nodeInstantiator) {
|
||||
public Neo4jEntityManagerFactory(GraphDatabaseService graphDatabaseService, EntityInstantiator<NodeBacked, Node> nodeInstantiator, PersistenceUnitInfo info, Map params) {
|
||||
this.graphDatabaseService = graphDatabaseService;
|
||||
this.nodeInstantiator = nodeInstantiator;
|
||||
this.info = info;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityManager createEntityManager() {
|
||||
return new Neo4jEntityManager(graphDatabaseService,nodeInstantiator);
|
||||
return new Neo4jEntityManager(graphDatabaseService,nodeInstantiator,info,params);
|
||||
}
|
||||
|
||||
/* TODO handle different directories for target datastore */
|
||||
@Override
|
||||
public EntityManager createEntityManager(Map map) {
|
||||
return new Neo4jEntityManager(graphDatabaseService,nodeInstantiator);
|
||||
return new Neo4jEntityManager(graphDatabaseService,nodeInstantiator,info,params);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,7 +2,9 @@ package org.springframework.datastore.graph.neo4j.jpa;
|
||||
|
||||
import org.neo4j.graphdb.GraphDatabaseService;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.springframework.beans.factory.annotation.Configurable;
|
||||
import org.springframework.datastore.graph.api.NodeBacked;
|
||||
import org.springframework.datastore.graph.neo4j.spi.node.Neo4jHelper;
|
||||
import org.springframework.persistence.support.EntityInstantiator;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -16,6 +18,7 @@ import java.util.Map;
|
||||
* @since 23.08.2010
|
||||
*/
|
||||
// todo handle additinal info + database path
|
||||
@Configurable
|
||||
public class Neo4jPersistenceProvider implements PersistenceProvider {
|
||||
@Resource
|
||||
GraphDatabaseService graphDatabaseService;
|
||||
@@ -24,12 +27,21 @@ public class Neo4jPersistenceProvider implements PersistenceProvider {
|
||||
EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
|
||||
@Override
|
||||
public EntityManagerFactory createEntityManagerFactory(String emName, Map map) {
|
||||
return new Neo4jEntityManagerFactory(graphDatabaseService,graphEntityInstantiator);
|
||||
public EntityManagerFactory createEntityManagerFactory(String emName, Map params) {
|
||||
System.out.println("emName = " + emName);
|
||||
System.out.println("params = " + params);
|
||||
return new Neo4jEntityManagerFactory(graphDatabaseService,graphEntityInstantiator,null,params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {
|
||||
return new Neo4jEntityManagerFactory(graphDatabaseService,graphEntityInstantiator);
|
||||
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map params) {
|
||||
System.out.println("info.getPersistenceProviderClassName() = " + info.getPersistenceProviderClassName());
|
||||
System.out.println("info.getManagedClassNames() = " + info.getManagedClassNames());
|
||||
System.out.println("info.getMappingFileNames() = " + info.getMappingFileNames());
|
||||
System.out.println("info.getTransactionType() = " + info.getTransactionType());
|
||||
System.out.println("info.getProperties() = " + info.getProperties());
|
||||
System.out.println("info.getPersistenceUnitName() = " + info.getPersistenceUnitName());
|
||||
System.out.println("params = " + params);
|
||||
return new Neo4jEntityManagerFactory(graphDatabaseService,graphEntityInstantiator,info,params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package org.springframework.datastore.graph.neo4j.spi.node;
|
||||
|
||||
import org.neo4j.graphdb.DynamicRelationshipType;
|
||||
import org.neo4j.graphdb.GraphDatabaseService;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
import org.neo4j.graphdb.RelationshipType;
|
||||
import org.neo4j.graphdb.*;
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.neo4j.util.GraphDatabaseUtil;
|
||||
import org.springframework.datastore.graph.api.NodeBacked;
|
||||
import org.springframework.datastore.graph.api.*;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class Neo4jHelper {
|
||||
|
||||
@@ -16,17 +16,36 @@ public abstract class Neo4jHelper {
|
||||
public final static RelationshipType INSTANCE_OF_RELATIONSHIP_TYPE = DynamicRelationshipType.withName("INSTANCE_OF");
|
||||
|
||||
public final static String SUBREFERENCE_NODE_COUNTER_KEY = "count";
|
||||
|
||||
public static Node findSubreferenceNode(Class<? extends NodeBacked> entityClass, GraphDatabaseService gds) {
|
||||
RelationshipType subRefRelType = DynamicRelationshipType.withName("SUBREF_" + entityClass.getName());
|
||||
public static final String SUBREF_PREFIX = "SUBREF_";
|
||||
|
||||
public static Node obtainSubreferenceNode(Class<? extends NodeBacked> entityClass, GraphDatabaseService gds) {
|
||||
RelationshipType subRefRelType = DynamicRelationshipType.withName(SUBREF_PREFIX + entityClass.getName());
|
||||
return new GraphDatabaseUtil(gds).getOrCreateSubReferenceNode(subRefRelType);
|
||||
}
|
||||
|
||||
public static Node findSubreferenceNode(Class<? extends NodeBacked> entityClass, GraphDatabaseService gds) {
|
||||
RelationshipType subRefRelType = DynamicRelationshipType.withName(SUBREF_PREFIX + entityClass.getName());
|
||||
final Iterator<Relationship> it = gds.getReferenceNode().getRelationships(subRefRelType, Direction.OUTGOING).iterator();
|
||||
return it.hasNext() ? it.next().getEndNode() : null;
|
||||
}
|
||||
|
||||
public static long count(Class<? extends NodeBacked> entityClass, GraphDatabaseService gds) {
|
||||
Node subrefNode = findSubreferenceNode(entityClass, gds);
|
||||
if (subrefNode==null) return 0;
|
||||
return (Integer) subrefNode.getProperty(SUBREFERENCE_NODE_COUNTER_KEY, 0);
|
||||
}
|
||||
|
||||
public static String getClassNameForShortName( GraphDatabaseService gds, String shortName) {
|
||||
final Node referenceNode = gds.getReferenceNode();
|
||||
for (Relationship relationship : referenceNode.getRelationships(Direction.OUTGOING)) {
|
||||
final String relationshipName = relationship.getType().name();
|
||||
if (relationshipName.endsWith(shortName) && relationshipName.startsWith(SUBREF_PREFIX)) {
|
||||
return relationshipName.substring(SUBREF_PREFIX.length());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static void cleanDb(GraphDatabaseService graphDatabaseService) {
|
||||
Node refNode = graphDatabaseService.getReferenceNode();
|
||||
for (Node node : graphDatabaseService.getAllNodes()) {
|
||||
@@ -39,4 +58,11 @@ public abstract class Neo4jHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static void createSubreferenceNodesFor(GraphDatabaseService gds, List<String> classNames) {
|
||||
final GraphDatabaseUtil graphDatabaseUtil = new GraphDatabaseUtil(gds);
|
||||
for (String className : classNames) {
|
||||
RelationshipType subRefRelType = DynamicRelationshipType.withName(SUBREF_PREFIX + className);
|
||||
graphDatabaseUtil.getOrCreateSubReferenceNode(subRefRelType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.springframework.datastore.graph.neo4j.spi.node;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
import org.aspectj.lang.reflect.FieldSignature;
|
||||
import org.neo4j.graphdb.GraphDatabaseService;
|
||||
@@ -8,6 +9,7 @@ import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.NotInTransactionException;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
import org.neo4j.graphdb.RelationshipType;
|
||||
import org.neo4j.kernel.EmbeddedGraphDatabase;
|
||||
import org.neo4j.util.GraphDatabaseUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.InvalidDataAccessResourceUsageException;
|
||||
@@ -19,6 +21,10 @@ import org.springframework.datastore.graph.neo4j.fieldaccess.FieldAccessor;
|
||||
import org.springframework.datastore.graph.neo4j.fieldaccess.FieldAccessorFactory;
|
||||
import org.springframework.persistence.support.AbstractTypeAnnotatingMixinFields;
|
||||
import org.springframework.persistence.support.EntityInstantiator;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import javax.transaction.Status;
|
||||
import javax.transaction.SystemException;
|
||||
|
||||
/**
|
||||
* Aspect to turn an object annotated with GraphEntity into a graph entity using Neo4J.
|
||||
@@ -64,27 +70,44 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
|
||||
|
||||
// Create a new node in the Graph if no Node was passed in a constructor
|
||||
before(NodeBacked entity) : arbitraryUserConstructorOfNodeBackedObject(entity) {
|
||||
if (!transactionIsRunning()) {
|
||||
log.warn("New Nodebacked created outside of transaction "+ entity.getClass());
|
||||
|
||||
} else {
|
||||
createAndAssignNode(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void createAndAssignNode(NodeBacked entity) {
|
||||
try {
|
||||
entity.setUnderlyingNode(graphDatabaseService.createNode());
|
||||
log.info("User-defined constructor called on class " + entity.getClass() + "; created Node [" + entity.getUnderlyingNode() +"]; " +
|
||||
"Updating metamodel");
|
||||
// TODO pull naming out into a strategy interface, todo a separate one, or the Entity Instatiator
|
||||
// graphEntityInstantiator.postEntityCreation(entity);
|
||||
postEntityCreation(entity);
|
||||
} catch(NotInTransactionException e) {
|
||||
throw new InvalidDataAccessResourceUsageException("Not in a Neo4j transaction.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void postEntityCreation(NodeBacked entity) {
|
||||
Node subReference = Neo4jHelper.obtainSubreferenceNode(entity.getClass(), graphDatabaseService);
|
||||
entity.getUnderlyingNode().createRelationshipTo(subReference, Neo4jHelper.INSTANCE_OF_RELATIONSHIP_TYPE);
|
||||
GraphDatabaseUtil.incrementAndGetCounter(subReference, Neo4jHelper.SUBREFERENCE_NODE_COUNTER_KEY);
|
||||
}
|
||||
|
||||
private boolean transactionIsRunning() {
|
||||
try {
|
||||
return ((EmbeddedGraphDatabase)graphDatabaseService).getConfig().getTxModule().getTxManager().getStatus() != Status.STATUS_NO_TRANSACTION;
|
||||
} catch (SystemException e) {
|
||||
log.error("Error accessing TransactionManager",e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void postEntityCreation(NodeBacked entity) {
|
||||
Node subReference = Neo4jHelper.findSubreferenceNode(entity.getClass(), graphDatabaseService);
|
||||
entity.getUnderlyingNode().createRelationshipTo(subReference, Neo4jHelper.INSTANCE_OF_RELATIONSHIP_TYPE);
|
||||
GraphDatabaseUtil.incrementAndGetCounter(subReference, Neo4jHelper.SUBREFERENCE_NODE_COUNTER_KEY);
|
||||
}
|
||||
|
||||
// Introduced field
|
||||
private Node NodeBacked.underlyingNode;
|
||||
|
||||
private Map<Field,Object> NodeBacked.dirty;
|
||||
|
||||
public void NodeBacked.setUnderlyingNode(Node n) {
|
||||
this.underlyingNode = n;
|
||||
}
|
||||
@@ -93,21 +116,48 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
|
||||
return underlyingNode;
|
||||
}
|
||||
|
||||
public boolean NodeBacked.hasUnderlyingNode() {
|
||||
return underlyingNode!=null;
|
||||
}
|
||||
|
||||
public Relationship NodeBacked.relateTo(NodeBacked nb, RelationshipType type) {
|
||||
return this.underlyingNode.createRelationshipTo(nb.getUnderlyingNode(), type);
|
||||
}
|
||||
|
||||
public Long NodeBacked.getId() {
|
||||
if (!hasUnderlyingNode()) return null;
|
||||
return underlyingNode.getId();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private boolean NodeBacked.isDirty() {
|
||||
return this.dirty!=null && !this.dirty.isEmpty();
|
||||
}
|
||||
private boolean NodeBacked.isDirty(Field f) {
|
||||
return this.dirty!=null && this.dirty.containsKey(f);
|
||||
}
|
||||
|
||||
private void NodeBacked.clearDirty() {
|
||||
if (this.dirty!=null) this.dirty.clear();
|
||||
}
|
||||
|
||||
private void NodeBacked.addDirty(Field f, Object previousValue) {
|
||||
if (this.dirty==null) this.dirty=new IdentityHashMap<Field, Object>();
|
||||
this.dirty.put(f,previousValue);
|
||||
}
|
||||
|
||||
private Iterable<Map.Entry<Field,Object>> NodeBacked.eachDirty() {
|
||||
return this.dirty!=null ? this.dirty.entrySet() : Collections.<Field, Object>emptyMap().entrySet();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Equals and hashCode for Neo4j entities.
|
||||
// Final to prevent overriding.
|
||||
//-------------------------------------------------------------------------
|
||||
// TODO could use template method for further checks if needed
|
||||
public final boolean NodeBacked.equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!hasUnderlyingNode()) return false;
|
||||
if (obj instanceof NodeBacked) {
|
||||
return this.getUnderlyingNode().equals(((NodeBacked) obj).getUnderlyingNode());
|
||||
}
|
||||
@@ -115,53 +165,153 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
|
||||
}
|
||||
|
||||
public final int NodeBacked.hashCode() {
|
||||
if (!hasUnderlyingNode()) return System.identityHashCode(this);
|
||||
return getUnderlyingNode().hashCode();
|
||||
}
|
||||
|
||||
Object around(NodeBacked entity) : entityFieldGet(entity) {
|
||||
FieldSignature fieldSignature=(FieldSignature) thisJoinPoint.getSignature();
|
||||
Field f = fieldSignature.getField();
|
||||
|
||||
// TODO fix arrays, TODO serialize other types as byte[] or string (for indexing, querying) via Annotation
|
||||
if (isPropertyType(f.getType())) {
|
||||
String propName = FieldAccessorFactory.getNeo4jPropertyName(f);
|
||||
log.info("GET " + f + " <- Neo4J simple node property [" + propName + "]");
|
||||
return entity.getUnderlyingNode().getProperty(propName, null);
|
||||
}
|
||||
|
||||
FieldAccessor accessor = fieldAccessorFactory.forField(f);
|
||||
Object obj = accessor.readObject(entity);
|
||||
if (obj == null) {
|
||||
log.info("Ignored GET " + f + ": " + f.getType().getName() + " not primitive or GraphEntity");
|
||||
return proceed(entity);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
Object around(NodeBacked entity, Object newVal) : entityFieldSet(entity, newVal) {
|
||||
try {
|
||||
FieldSignature fieldSignature=(FieldSignature) thisJoinPoint.getSignature();
|
||||
Field f = fieldSignature.getField();
|
||||
// TODO fix arrays
|
||||
Class<?> fieldType = f.getType();
|
||||
if (isPropertyType(fieldType)) {
|
||||
String propName = FieldAccessorFactory.getNeo4jPropertyName(f);
|
||||
entity.getUnderlyingNode().setProperty(propName, newVal);
|
||||
log.info("SET " + f + " -> Neo4J simple node property [" + propName + "] with value=[" + newVal + "]");
|
||||
return proceed(entity, newVal);
|
||||
}
|
||||
|
||||
FieldAccessor accessor = fieldAccessorFactory.forField(f);
|
||||
if (accessor == null) {
|
||||
log.info("Ignored SET " + f + ": " + f.getType().getName() + " not primitive or GraphEntity");
|
||||
return proceed(entity, newVal);
|
||||
}
|
||||
log.info("SET " + f + " -> Neo4J relationship with value=[" + newVal + "]");
|
||||
Object result = accessor.apply(entity, newVal);
|
||||
return proceed(entity,result);
|
||||
} catch(NotInTransactionException e) {
|
||||
throw new InvalidDataAccessResourceUsageException("Not in a Neo4j transaction.", e);
|
||||
}
|
||||
|
||||
private Object getValueFromEntity(Field field, NodeBacked entity) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
return field.get(entity);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Error accessing field "+field+" in "+entity.getClass(),e);
|
||||
}
|
||||
}
|
||||
/*
|
||||
always runs inside a transaction
|
||||
*/
|
||||
private ShouldProceedOrReturn getNodePropertyOrRelationship(Field field, NodeBacked entity) {
|
||||
// TODO fix arrays, TODO serialize other types as byte[] or string (for indexing, querying) via Annotation
|
||||
if (isIdField(field)) return new ShouldProceedOrReturn(entity.getUnderlyingNode().getId());
|
||||
if (isPropertyType(field.getType())) {
|
||||
String propName = FieldAccessorFactory.getNeo4jPropertyName(field);
|
||||
log.info("GET " + field + " <- Neo4J simple node property [" + propName + "]");
|
||||
Node node = entity.getUnderlyingNode();
|
||||
Object nodeProperty = node.getProperty(propName, getDefaultValue(field.getType()));
|
||||
return new ShouldProceedOrReturn(nodeProperty);
|
||||
}
|
||||
|
||||
FieldAccessor accessor = fieldAccessorFactory.forField(field);
|
||||
if (accessor!=null) {
|
||||
Object obj = accessor.readObject(entity);
|
||||
if (obj != null) {
|
||||
return new ShouldProceedOrReturn(obj);
|
||||
}
|
||||
}
|
||||
log.info("Ignored GET " + field + ": " + field.getType().getName() + " not primitive or GraphEntity");
|
||||
return new ShouldProceedOrReturn();
|
||||
}
|
||||
|
||||
private boolean isIdField(Field field) {
|
||||
if (!field.getName().equals("id")) return false;
|
||||
final Class<?> type = field.getType();
|
||||
return type.equals(Long.class) || type.equals(long.class);
|
||||
}
|
||||
|
||||
private Object getDefaultValue(Class<?> type) {
|
||||
if (type.isPrimitive()) {
|
||||
if (type.equals(boolean.class)) return false;
|
||||
return 0;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void flushDirty(NodeBacked entity) {
|
||||
if (transactionIsRunning()) {
|
||||
final boolean newNode=!entity.hasUnderlyingNode();
|
||||
if (newNode) {
|
||||
createAndAssignNode(entity);
|
||||
}
|
||||
if (entity.isDirty()) {
|
||||
for (final Map.Entry<Field,Object> entry : entity.eachDirty()) {
|
||||
final Field field = entry.getKey();
|
||||
if (!newNode) {
|
||||
checkConcurrentModification(entity, entry, field);
|
||||
}
|
||||
setNodePropertyOrRelationship(field, entity, getValueFromEntity(field,entity));
|
||||
}
|
||||
entity.clearDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkConcurrentModification(NodeBacked entity, Map.Entry<Field, Object> entry, Field field) {
|
||||
final Object nodeValue = getNodePropertyOrRelationship(field, entity).value;
|
||||
final Object previousValue = entry.getValue();
|
||||
if (!ObjectUtils.nullSafeEquals(nodeValue,previousValue)) {
|
||||
throw new ConcurrentModificationException("Node "+entity.getUnderlyingNode()+" field "+field+" changed in between previous "+ previousValue +" current "+nodeValue); // todo or just overwrite
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
always called inside a transaction
|
||||
*/
|
||||
|
||||
private ShouldProceedOrReturn setNodePropertyOrRelationship(Field field, NodeBacked entity, Object newVal) {
|
||||
try {
|
||||
if (isIdField(field)) return new ShouldProceedOrReturn(null);
|
||||
if (isPropertyType(field.getType())) {
|
||||
String propName = FieldAccessorFactory.getNeo4jPropertyName(field);
|
||||
if (newVal==null) {
|
||||
entity.getUnderlyingNode().removeProperty(propName);
|
||||
} else {
|
||||
entity.getUnderlyingNode().setProperty(propName, newVal);
|
||||
}
|
||||
log.info("SET " + field + " -> Neo4J simple node property [" + propName + "] with value=[" + newVal + "]");
|
||||
return new ShouldProceedOrReturn(true,newVal);
|
||||
}
|
||||
|
||||
FieldAccessor accessor = fieldAccessorFactory.forField(field);
|
||||
if (accessor == null) {
|
||||
log.info("Ignored SET " + field + ": " + field.getType().getName() + " not primitive or GraphEntity");
|
||||
return new ShouldProceedOrReturn(true,newVal);
|
||||
}
|
||||
log.info("SET " + field + " -> Neo4J relationship with value=[" + newVal + "]");
|
||||
Object result = accessor.apply(entity, newVal);
|
||||
return new ShouldProceedOrReturn(true,result);
|
||||
} catch(NotInTransactionException e) {
|
||||
throw new InvalidDataAccessResourceUsageException("Not in a Neo4j transaction.", e);
|
||||
}
|
||||
}
|
||||
Object around(NodeBacked entity): entityFieldGet(entity) {
|
||||
FieldSignature fieldSignature = (FieldSignature) thisJoinPoint.getSignature();
|
||||
Field f = fieldSignature.getField();
|
||||
|
||||
if (!transactionIsRunning()) {
|
||||
if (!entity.hasUnderlyingNode() || (entity.isDirty(f))) {
|
||||
return proceed(entity);
|
||||
}
|
||||
}
|
||||
|
||||
flushDirty(entity);
|
||||
|
||||
ShouldProceedOrReturn shouldProceedOrReturn=getNodePropertyOrRelationship(f,entity);
|
||||
if (shouldProceedOrReturn.proceed) {
|
||||
return proceed(entity);
|
||||
} else {
|
||||
return shouldProceedOrReturn.value;
|
||||
}
|
||||
}
|
||||
|
||||
Object around(NodeBacked entity, Object newVal) : entityFieldSet(entity, newVal) {
|
||||
FieldSignature fieldSignature=(FieldSignature) thisJoinPoint.getSignature();
|
||||
Field f = fieldSignature.getField();
|
||||
if (!transactionIsRunning()) {
|
||||
if (!entity.isDirty(f)) {
|
||||
Object existingValue = entity.hasUnderlyingNode() ? getDefaultValue(f.getType()) : getValueFromEntity(f,entity);
|
||||
entity.addDirty(f,existingValue);
|
||||
}
|
||||
return proceed(entity, newVal);
|
||||
}
|
||||
flushDirty(entity);
|
||||
|
||||
ShouldProceedOrReturn shouldProceedOrReturn=setNodePropertyOrRelationship(f,entity,newVal);
|
||||
if (shouldProceedOrReturn.proceed) {
|
||||
return proceed(entity,shouldProceedOrReturn.value);
|
||||
} else {
|
||||
return shouldProceedOrReturn.value;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPropertyType(Class<?> fieldType) {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.springframework.datastore.graph.neo4j.spi.node;
|
||||
|
||||
/**
|
||||
* @author Michael Hunger
|
||||
* @since 29.08.2010
|
||||
*/
|
||||
public class ShouldProceedOrReturn {
|
||||
public final boolean proceed;
|
||||
public final Object value;
|
||||
|
||||
public ShouldProceedOrReturn() {
|
||||
this.proceed = true;
|
||||
this.value = null;
|
||||
}
|
||||
|
||||
public ShouldProceedOrReturn(final Object value) {
|
||||
this.proceed = false;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public ShouldProceedOrReturn(final boolean proceed, final Object value) {
|
||||
this.proceed = true;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,8 @@ public aspect Neo4jRelationshipBacking extends AbstractTypeAnnotatingMixinFields
|
||||
private EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
|
||||
@Autowired
|
||||
public void init(EntityInstantiator<NodeBacked, Node> gei) {
|
||||
this.graphEntityInstantiator = gei;
|
||||
public void setEntityInstantiator(EntityInstantiator<NodeBacked, Node> entityInstantiator) {
|
||||
this.graphEntityInstantiator = entityInstantiator;
|
||||
}
|
||||
|
||||
// Introduced fields
|
||||
@@ -46,8 +46,12 @@ public aspect Neo4jRelationshipBacking extends AbstractTypeAnnotatingMixinFields
|
||||
public Relationship RelationshipBacked.getUnderlyingRelationship() {
|
||||
return underlyingRelationship;
|
||||
}
|
||||
public boolean RelationshipBacked.hasUnderlyingRelationship() {
|
||||
return underlyingRelationship!=null;
|
||||
}
|
||||
|
||||
public long RelationshipBacked.getId() {
|
||||
public Long RelationshipBacked.getId() {
|
||||
if (!hasUnderlyingRelationship()) return null;
|
||||
return underlyingRelationship.getId();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.springframework.datastore.graph.neo4j;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.neo4j.graphdb.GraphDatabaseService;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
@@ -45,7 +44,7 @@ privileged aspect Person_Graph_Entity {
|
||||
}
|
||||
|
||||
public static List<Person> Person.findAllPeople() {
|
||||
Node subrefNode = Neo4jHelper.findSubreferenceNode(Person.class, graphDatabaseService());
|
||||
Node subrefNode = Neo4jHelper.obtainSubreferenceNode(Person.class, graphDatabaseService());
|
||||
// TODO Neo4j should add lazy list on top of graph
|
||||
List<Person> people = new ArrayList<Person>((int) countPeople());
|
||||
for (Relationship rel : subrefNode.getRelationships(Neo4jHelper.INSTANCE_OF_RELATIONSHIP_TYPE, org.neo4j.graphdb.Direction.INCOMING)) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.springframework.datastore.graph.neo4j.jpa;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@@ -9,11 +10,11 @@ import javax.annotation.Resource;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityTransaction;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.*;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.neo4j.graphdb.GraphDatabaseService;
|
||||
import org.neo4j.graphdb.Node;
|
||||
@@ -24,6 +25,10 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* @author Michael Hunger
|
||||
* @since 20.08.2010
|
||||
@@ -43,8 +48,8 @@ public class Neo4jEntityManagerTest {
|
||||
@Before
|
||||
public void cleanDb() {
|
||||
Neo4jHelper.cleanDb(graphDatabaseService);
|
||||
node = graphDatabaseService.createNode();
|
||||
person = new Person(node);
|
||||
person = new Person("Michael",35);
|
||||
node = person.getUnderlyingNode();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -70,6 +75,45 @@ public class Neo4jEntityManagerTest {
|
||||
final Person found = entityManager.find(Person.class, node.getId());
|
||||
assertEquals(person,found);
|
||||
}
|
||||
@Test
|
||||
public void testFindSingle() throws Exception {
|
||||
final Query query = entityManager.createQuery("select o from Person o");
|
||||
final Person found = (Person) query.getSingleResult();
|
||||
assertEquals(person,found);
|
||||
}
|
||||
@Test
|
||||
public void testFindAll() throws Exception {
|
||||
final Query query = entityManager.createQuery("select o from Person o");
|
||||
Collection<Person> people=query.getResultList();
|
||||
Assert.assertEquals(asList(person),people);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAll2() throws Exception {
|
||||
final Person person2 = new Person("Rod", 39);
|
||||
final Query query = entityManager.createQuery("select o from Person o");
|
||||
Collection<Person> people=query.getResultList();
|
||||
Assert.assertEquals(new HashSet<Person>(asList(person,person2)),new HashSet<Person>(people));
|
||||
}
|
||||
@Test
|
||||
public void testFindAllStart() throws Exception {
|
||||
final Query query = entityManager.createQuery("select o from Person o").setFirstResult(1);
|
||||
Collection<Person> people=query.getResultList();
|
||||
Assert.assertEquals(Collections.<Person>emptySet(),new HashSet<Person>(people));
|
||||
}
|
||||
@Test
|
||||
public void testFindAllEnd() throws Exception {
|
||||
final Query query = entityManager.createQuery("select o from Person o").setMaxResults(0);
|
||||
Collection<Person> people=query.getResultList();
|
||||
Assert.assertEquals(Collections.<Person>emptySet(),new HashSet<Person>(people));
|
||||
}
|
||||
@Test
|
||||
public void testFindAllStartEnd() throws Exception {
|
||||
new Person("Rod", 39);
|
||||
final Query query = entityManager.createQuery("select o from Person o").setMaxResults(1).setFirstResult(1);
|
||||
Collection<Person> people=query.getResultList();
|
||||
Assert.assertEquals(1,people.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetReference() throws Exception {
|
||||
|
||||
@@ -134,12 +134,13 @@ public class Neo4jGraphPersistenceTest {
|
||||
Assert.assertEquals(boss, p.getBoss());
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessResourceUsageException.class)
|
||||
// @Test(expected = InvalidDataAccessResourceUsageException.class)
|
||||
public void testCreateOutsideTransaction() {
|
||||
Person p = new Person("Michael", 35);
|
||||
Assert.assertEquals(35,p.getAge());
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessResourceUsageException.class)
|
||||
// @Test(expected = InvalidDataAccessResourceUsageException.class)
|
||||
public void testSetPropertyOutsideTransaction() {
|
||||
Transaction tx = graphDatabaseService.beginTx();
|
||||
Person p = null;
|
||||
@@ -150,9 +151,19 @@ public class Neo4jGraphPersistenceTest {
|
||||
tx.finish();
|
||||
}
|
||||
p.setAge(25);
|
||||
Assert.assertEquals(25,p.getAge());
|
||||
tx = graphDatabaseService.beginTx();
|
||||
try {
|
||||
Assert.assertEquals(25,p.getAge());
|
||||
p.setAge(20);
|
||||
tx.success();
|
||||
} finally {
|
||||
tx.finish();
|
||||
}
|
||||
Assert.assertEquals(20,p.getAge());
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessResourceUsageException.class)
|
||||
// @Test(expected = InvalidDataAccessResourceUsageException.class)
|
||||
public void testCreateRelationshipOutsideTransaction() {
|
||||
Transaction tx = graphDatabaseService.beginTx();
|
||||
Person p = null;
|
||||
@@ -165,6 +176,17 @@ public class Neo4jGraphPersistenceTest {
|
||||
tx.finish();
|
||||
}
|
||||
p.setSpouse(spouse);
|
||||
Assert.assertEquals(spouse,p.getSpouse());
|
||||
Person spouse2;
|
||||
tx = graphDatabaseService.beginTx();
|
||||
try {
|
||||
Assert.assertEquals(spouse,p.getSpouse());
|
||||
spouse2 = new Person("Rana", 5);
|
||||
tx.success();
|
||||
} finally {
|
||||
tx.finish();
|
||||
}
|
||||
Assert.assertEquals(spouse2,p.getSpouse());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -179,7 +201,14 @@ public class Neo4jGraphPersistenceTest {
|
||||
}
|
||||
Assert.assertEquals("Wrong age.", (int)35, (int)p.getAge());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFindOutsideTransaction() {
|
||||
final FinderFactory factory = new FinderFactory(graphDatabaseService, graphEntityInstantiator);
|
||||
final Finder<Person> finder = factory.getFinderForClass(Person.class);
|
||||
Assert.assertEquals(false,finder.findAll().iterator().hasNext());
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
@Transactional
|
||||
public void testCircularRelationship() {
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
<persistence-unit name="neo4j-persistence" transaction-type="RESOURCE_LOCAL">
|
||||
<provider>org.springframework.datastore.graph.neo4j.jpa.Neo4jPersistenceProvider</provider>
|
||||
<class>org.springframework.datastore.graph.neo4j.Person</class>
|
||||
<class>org.springframework.datastore.graph.neo4j.Group</class>
|
||||
<class>org.springframework.datastore.graph.neo4j.Friendship</class>
|
||||
<properties>
|
||||
<property name="neo4j.path" value="test/data"/> <!-- TODO handle this -->
|
||||
</properties>
|
||||
|
||||
@@ -71,7 +71,9 @@
|
||||
|
||||
<bean class="org.springframework.datastore.graph.neo4j.spi.node.Neo4jNodeBacking" factory-method="aspectOf" />
|
||||
|
||||
<bean class="org.springframework.datastore.graph.neo4j.spi.relationship.Neo4jRelationshipBacking" factory-method="aspectOf" />
|
||||
<bean class="org.springframework.datastore.graph.neo4j.spi.relationship.Neo4jRelationshipBacking" factory-method="aspectOf" >
|
||||
<property name="entityInstantiator" ref="gei"/>
|
||||
</bean>
|
||||
|
||||
<!--
|
||||
<bean class="org.springframework.persistence.graph.Neo4jSimpleNodePropertyStorageForeignStoreKeyManager"/>
|
||||
|
||||
Reference in New Issue
Block a user