Consistent Schema Index Creation for based indexed properties

This commit is contained in:
Michael Hunger
2014-02-23 00:58:51 +01:00
parent 2a703036e2
commit c99289d9df
18 changed files with 252 additions and 67 deletions

View File

@@ -9,6 +9,7 @@
http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:annotation-config/>
<!-- Base Package is mandatory for using label based indexing -->
<neo4j:config graphDatabaseService="graphDatabaseService" base-package="org.springframework.data.neo4j.aspects.support.domain"/>
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown"/>

View File

@@ -27,7 +27,7 @@
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown" scope="singleton"/>
<bean id="conversionService" class="org.springframework.data.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean"/>
<bean id="indexCreationMappingEventListener" class="org.springframework.data.neo4j.support.mapping.IndexCreationMappingEventListener">
<bean id="indexCreationMappingEventListener" class="org.springframework.data.neo4j.support.mapping.EntityIndexCreator">
<constructor-arg ref="indexProvider" />
<constructor-arg ref="schemaIndexProvider" />
<constructor-arg ref="nodeTypeRepresentationStrategy" />

View File

@@ -30,7 +30,7 @@
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown" scope="singleton"/>
<bean id="conversionService" class="org.springframework.data.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean"/>
<bean id="indexCreationMappingEventListener" class="org.springframework.data.neo4j.support.mapping.IndexCreationMappingEventListener">
<bean id="indexCreationMappingEventListener" class="org.springframework.data.neo4j.support.mapping.EntityIndexCreator">
<constructor-arg ref="indexProvider" />
<constructor-arg ref="schemaIndexProvider" />
<constructor-arg ref="nodeTypeRepresentationStrategy" />

View File

@@ -30,7 +30,7 @@
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown" scope="singleton"/>
<bean id="conversionService" class="org.springframework.data.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean"/>
<bean id="indexCreationMappingEventListener" class="org.springframework.data.neo4j.support.mapping.IndexCreationMappingEventListener">
<bean id="indexCreationMappingEventListener" class="org.springframework.data.neo4j.support.mapping.EntityIndexCreator">
<constructor-arg ref="indexProvider" />
<constructor-arg ref="schemaIndexProvider" />
<constructor-arg ref="nodeTypeRepresentationStrategy" />

View File

@@ -0,0 +1,43 @@
package org.springframework.data.neo4j.config;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.annotation.RelationshipEntity;
import java.util.HashSet;
import java.util.Set;
/**
* @author mh
* @since 23.02.14
*/
public class BasePackageScanner {
public static Set<String> scanBasePackage(String basePackage) {
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(false);
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
Set<String> classes = new ManagedSet<String>();
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
classes.add(candidate.getBeanClassName());
}
return classes;
}
static Set<? extends Class<?>> scanBasePackageForClasses(String basePackage) throws ClassNotFoundException {
Set<Class<?>> classes = new HashSet<>();
for (String className : scanBasePackage(basePackage)){
classes.add(loadClass(className));
}
return classes;
}
private static Class loadClass(String className) throws ClassNotFoundException {
return Thread.currentThread().getContextClassLoader().loadClass(className);
}
}

View File

@@ -16,18 +16,11 @@
package org.springframework.data.neo4j.config;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.*;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.annotation.RelationshipEntity;
import org.springframework.data.neo4j.support.GraphDatabaseServiceFactoryBean;
import org.springframework.util.ClassUtils;
import org.w3c.dom.Element;
@@ -70,17 +63,7 @@ public class DataGraphBeanDefinitionParser extends AbstractBeanDefinitionParser
return null;
}
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(false);
componentProvider.addIncludeFilter(new AnnotationTypeFilter(NodeEntity.class));
componentProvider.addIncludeFilter(new AnnotationTypeFilter(RelationshipEntity.class));
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
Set<String> classes = new ManagedSet<String>();
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
classes.add(candidate.getBeanClassName());
}
return classes;
return BasePackageScanner.scanBasePackage(basePackage);
}
@@ -162,4 +145,4 @@ public class DataGraphBeanDefinitionParser extends AbstractBeanDefinitionParser
}
}

View File

@@ -203,6 +203,7 @@ public abstract class Neo4jConfiguration {
mappingContext.setInitialEntitySet(initialEntitySet);
}
mappingContext.setEntityAlias(entityAlias());
mappingContext.setEntityIndexCreator(entityIndexCreator());
return mappingContext;
}
@@ -238,11 +239,11 @@ public abstract class Neo4jConfiguration {
}
@Bean
public IndexCreationMappingEventListener indexCreationMappingEventListener() throws Exception {
return new IndexCreationMappingEventListener(
public EntityIndexCreator entityIndexCreator() throws Exception {
return new EntityIndexCreator(
indexProvider(),
schemaIndexProvider(),
nodeTypeRepresentationStrategy());
schemaIndexProvider()
);
}
@Bean
@@ -285,4 +286,16 @@ public abstract class Neo4jConfiguration {
public void setInitialEntitySet(Set<? extends Class<?>> initialEntitySet) {
this.initialEntitySet = initialEntitySet;
}
private String basePackage;
public String getBasePackage() {
return basePackage;
}
public void setBasePackage(String basePackage) throws ClassNotFoundException {
this.basePackage = basePackage;
setInitialEntitySet(BasePackageScanner.scanBasePackageForClasses(basePackage));
}
}

View File

@@ -68,4 +68,4 @@ TransactionalRepositoryFactoryBeanSupport<R, T, Long> {
super.afterPropertiesSet();
}
}
}

View File

@@ -61,7 +61,7 @@ public class Neo4jCdiRepositoryBean<T> extends CdiRepositoryBean<T> {
protected T create(CreationalContext<T> creationalContext, Class<T> repositoryType) {
Neo4jMappingContext neo4jMapCtx = new Neo4jMappingContext();
Neo4jTemplate neo4jTemplate = new Neo4jTemplate(getDependencyInstance(graphDatabase, GraphDatabase.class));
Neo4jTemplate neo4jTemplate = new Neo4jTemplate(getDependencyInstance(graphDatabase, GraphDatabase.class));
GraphRepositoryFactory factory = new GraphRepositoryFactory(neo4jTemplate, neo4jMapCtx);
return factory.getRepository(repositoryType);

View File

@@ -30,31 +30,18 @@ import org.springframework.data.neo4j.support.schema.SchemaIndexProvider;
* @author mh
* @since 12.04.12
*/
public class IndexCreationMappingEventListener implements ApplicationListener<MappingContextEvent<Neo4jPersistentEntity<?>, Neo4jPersistentProperty>> {
public class EntityIndexCreator {
private IndexProvider indexProvider;
private SchemaIndexProvider schemaIndexProvider;
private TypeRepresentationStrategy<Node> nodeTypeRepresentationStrategy;
public IndexCreationMappingEventListener(IndexProvider indexProvider, SchemaIndexProvider schemaIndexProvider, TypeRepresentationStrategy<Node> nodeTypeRepresentationStrategy) {
public EntityIndexCreator(IndexProvider indexProvider, SchemaIndexProvider schemaIndexProvider) {
this.indexProvider = indexProvider;
this.schemaIndexProvider = schemaIndexProvider;
this.nodeTypeRepresentationStrategy = nodeTypeRepresentationStrategy;
}
@Override
public void onApplicationEvent(MappingContextEvent<Neo4jPersistentEntity<?>, Neo4jPersistentProperty> event) {
if (!(event.getSource() instanceof Neo4jMappingContext)) return;
final Neo4jPersistentEntity entity = event.getPersistentEntity();
ensureEntityIndexes(entity);
}
private void ensureEntityIndexes(Neo4jPersistentEntity<?> entity) {
public void ensureEntityIndexes(Neo4jPersistentEntity<?> entity) {
final Class entityType = entity.getType();
// TODO only when TRS is non-label? I think we prob need to do it
// anyway as you can still mix legacy indexes with label based
// ones???
// Pass 1 - do schema based updates first to prevent locking
entity.doWithProperties(new PropertyHandler<Neo4jPersistentProperty>() {
@Override
public void doWithPersistentProperty(Neo4jPersistentProperty property) {

View File

@@ -20,7 +20,6 @@ import org.neo4j.graphdb.PropertyContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.annotation.Reference;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.AbstractMappingContext;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.MappingException;
@@ -37,8 +36,6 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;
import static java.lang.String.format;
/**
* Neo4J specific {@link MappingContext} implementation. Simply creates {@link Neo4jPersistentEntityImpl} and
* {@link org.springframework.data.neo4j.mapping.Neo4jPersistentProperty} instances.
@@ -49,8 +46,9 @@ public class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersistentE
private final static Logger log = LoggerFactory.getLogger(Neo4jMappingContext.class);
private final Map<Annotation, Boolean> referenceAnnotations = new IdentityHashMap<Annotation, java.lang.Boolean>();
private final Map<Annotation, Boolean> referenceAnnotations = new IdentityHashMap<>();
private EntityIndexCreator entityIndexCreator;
protected <T> Neo4jPersistentEntityImpl<?> createPersistentEntity(TypeInformation<T> typeInformation) {
final Class<T> type = typeInformation.getType();
if (type.isAnnotationPresent(NodeEntity.class)) {
@@ -66,10 +64,15 @@ public class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersistentE
protected Neo4jPersistentEntityImpl<?> addPersistentEntity(TypeInformation<?> typeInformation) {
final Neo4jPersistentEntityImpl<?> entity = super.addPersistentEntity(typeInformation);
Collection<Neo4jPersistentEntity<?>> superTypeEntities = addSuperTypes(entity);
entity.updateStoredType(new StoredEntityType(entity,superTypeEntities,entityAlias));
updateStoredEntityType(entity, superTypeEntities);
return entity;
}
private void updateStoredEntityType(Neo4jPersistentEntityImpl<?> entity, Collection<Neo4jPersistentEntity<?>> superTypeEntities) {
entity.updateStoredType(new StoredEntityType(entity, superTypeEntities, entityAlias));
entityIndexCreator.ensureEntityIndexes(entity);
}
private List<Neo4jPersistentEntity<?>> addSuperTypes(Neo4jPersistentEntity<?> entity) {
List<Neo4jPersistentEntity<?>> entities=new ArrayList<Neo4jPersistentEntity<?>>();
final Class<?> type = entity.getType();
@@ -87,6 +90,12 @@ public class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersistentE
return Collections.emptyList();
}
@Override
public void initialize() {
super.initialize();
setStrict(true);
}
private boolean isRelationshipEntityType(Class<?> type) {
return type.isAnnotationPresent(RelationshipEntity.class);
}
@@ -189,4 +198,8 @@ public class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersistentE
}
return false;
}
public void setEntityIndexCreator(EntityIndexCreator entityIndexCreator) {
this.entityIndexCreator = entityIndexCreator;
}
}

View File

@@ -34,16 +34,21 @@ public class SchemaIndexProvider {
public SchemaIndexProvider(GraphDatabase gd) {
this.gd = gd;
if (this.gd instanceof DelegatingGraphDatabase) {
TransactionalHandler handler = new TransactionalHandler();
((DelegatingGraphDatabase)this.gd).getGraphDatabaseService().registerTransactionEventHandler(handler);
indexCreator = handler;
} else {
indexCreator = new SeparateThreadIndexCreator();
}
indexCreator = setupIndexCreator();
cypher = gd.queryEngineFor(QueryType.Cypher);
}
private IndexCreator setupIndexCreator() {
// if (this.gd instanceof DelegatingGraphDatabase) {
// TransactionalHandler handler = new TransactionalHandler();
// ((DelegatingGraphDatabase)this.gd).getGraphDatabaseService().registerTransactionEventHandler(handler);
// return handler;
// } else {
// return new SeparateThreadIndexCreator();
// }
return new InlineCreator();
}
public void createIndex(Neo4jPersistentProperty property) {
indexCreator.deferCreateIndex(property);
}
@@ -51,7 +56,8 @@ public class SchemaIndexProvider {
String label = getLabel(property);
String prop = getName(property);
String query = indexQuery(label, prop, property.getIndexInfo().isUnique());
cypher.query(query,null);
System.err.println(query);
cypher.query(query, null);
}
private String getName(Neo4jPersistentProperty property) {
@@ -104,9 +110,20 @@ public class SchemaIndexProvider {
}
private void runAfterTransaction() {
if (gd.transactionIsRunning() || indexesToBeCreated.isEmpty()) return;
Neo4jPersistentProperty property = indexesToBeCreated.poll();
doCreateIndex(property);
while (gd.transactionIsRunning()) {
try {
Thread.sleep(1);
} catch (InterruptedException e) { }
}
if (gd.transactionIsRunning()) return;
if (indexesToBeCreated.isEmpty()) return;
try (Transaction tx = gd.beginTx()) {
while (!indexesToBeCreated.isEmpty()) {
Neo4jPersistentProperty property = indexesToBeCreated.poll();
doCreateIndex(property);
}
tx.success();
}
}
@Override
@@ -120,6 +137,13 @@ public class SchemaIndexProvider {
}
}
private class InlineCreator implements IndexCreator {
@Override
public void deferCreateIndex(Neo4jPersistentProperty property) {
doCreateIndex(property);
}
}
private class SeparateThreadIndexCreator implements IndexCreator {
private final ExecutorService pool = Executors.newFixedThreadPool(1);
@Override
@@ -150,7 +174,6 @@ public class SchemaIndexProvider {
return true;
}
}).get(2, TimeUnit.SECONDS);
pool.shutdown();
} catch (TimeoutException e) {
throw new MappingException(format(
"Timeour occured trying to create schema index %s on against label %s: " +

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/data/neo4j"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://www.springframework.org/schema/data/neo4j"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/tool"/>
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:import namespace="http://www.springframework.org/schema/data/repository"
schemaLocation="http://www.springframework.org/schema/data/repository/spring-repository.xsd"/>
<xsd:element name="config">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="storeDirectory" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
default store-directory of the Neo4j database
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="graphDatabaseService" type="graphDatabaseServiceRef" />
<xsd:attribute name="base-package" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
base package for persistent entities.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="entityManagerFactory" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
entity manager factory bean reference for cross-store configuration
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="repositories">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="repository:repositories">
<xsd:attribute name="neo4j-template-ref" type="neo4jTemplateRef"/>
<xsd:attributeGroup ref="repository:repository-attributes"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="auditing">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.data.neo4j.lifecycle.AuditingEventListener" />
<tool:exports type="org.springframework.data.auditing.IsNewAwareAuditingHandler" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:attributeGroup ref="repository:auditing-attributes" />
<xsd:attribute name="neo4jTemplate" type="mappingContextRef" />
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="neo4jTemplateRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.neo4j.support.Neo4jTemplate"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="mappingContextRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.mapping.context.MappingContext"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="graphDatabaseServiceRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.neo4j.graphdb.GraphDatabaseService"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="entityManagerFactoryRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.orm.jpa.AbstractEntityManagerFactoryBean"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
</xsd:schema>

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.annotation.relatedto;
package org.springframework.data.neo4j.annotation.relatedto.bidirectional;
import org.junit.Test;
import org.junit.runner.RunWith;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.annotation.relatedto;
package org.springframework.data.neo4j.annotation.relatedto.bidirectional;
import org.junit.Test;
import org.junit.runner.RunWith;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.annotation.relatedto;
package org.springframework.data.neo4j.annotation.relatedto.bidirectional;
import org.junit.Test;
import org.junit.runner.RunWith;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.annotation.relatedto;
package org.springframework.data.neo4j.annotation.relatedto.bidirectional;
import org.junit.Test;
import org.junit.runner.RunWith;

View File

@@ -11,7 +11,9 @@
<context:spring-configured/>
<context:annotation-config/>
<bean class="org.springframework.data.neo4j.config.Neo4jConfiguration"/>
<bean class="org.springframework.data.neo4j.config.Neo4jConfiguration">
<property name="basePackage" value="org.springframework.data.neo4j.annotation.relatedto.bidirectional"/>
</bean>
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown"/>