2012-01-13 16 views
15

Tengo una configuración sencilla y se encontró con una desconcertante (al menos para mí) problema:Lazy/carga Eager/ir a buscar en Neo4j/Primavera-Data

tengo tres POJOs que están relacionados entre sí:

@NodeEntity 
public class Unit { 
    @GraphId Long nodeId; 
    @Indexed int type; 
    String description; 
} 


@NodeEntity 
public class User { 
    @GraphId Long nodeId; 
    @RelatedTo(type="user", direction = Direction.INCOMING) 
    @Fetch private Iterable<Worker> worker; 
    @Fetch Unit currentUnit; 

    String name; 

} 

@NodeEntity 
public class Worker { 
    @GraphId Long nodeId; 
    @Fetch User user; 
    @Fetch Unit unit; 
    String description; 
} 

Tiene unidad de usuario-usuario con una "unidad de corriente" que marca en el usuario que permite saltar directamente a la "unidad actual". Cada usuario puede tener varios trabajadores, pero un trabajador solo está asignado a una unidad (una unidad puede tener varios trabajadores).

Lo que me preguntaba es cómo controlar la anotación @Fetch en "Usuario.trabajador". De hecho, quiero que esto se lamente solo cuando sea necesario, porque la mayoría de las veces solo trabajo con "Trabajador".

Fui a través http://static.springsource.org/spring-data/data-neo4j/docs/2.0.0.RELEASE/reference/html/ y no es muy claro para mí:

  • trabajador es iterable, ya que debe ser leído solamente (relación de entrada) - en la documentación así se indique clarly, pero en los ejemplos '' Set '' se usa la mayor parte del tiempo. ¿Por qué? o no importa ...
  • ¿Cómo hago para que el trabajador solo cargue en el acceso? (carga lenta)
  • ¿Por qué tengo que anotar incluso las relaciones simples (worker.unit) con @Fetch. ¿No hay una mejor manera? Tengo otra entidad con MUCHAS relaciones tan simples: realmente quiero evitar tener que cargar todo el gráfico solo porque quiero las propiedades de un objeto.
  • ¿Me falta una configuración de muelles para que funcione con carga diferida?
  • ¿Hay alguna forma de cargar cualquier relación (que no esté marcada como @Fetch) a través de una llamada adicional?

Según mi opinión, este constructo carga toda la base de datos tan pronto como quiero un Trabajador, incluso si no me importa el Usuario la mayor parte del tiempo.

La única solución que encontré es utilizar el repositorio y cargar manualmente las entidades cuando sea necesario.

------- ------- actualización

He estado trabajando con Neo4j desde hace bastante tiempo y encontré una solución para el problema anterior que no requiere llamar obtener todos el tiempo (y por lo tanto no carga todo el gráfico). El único inconveniente: es un aspecto de tiempo de ejecución:

import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Around; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.annotation.Pointcut; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.data.mapping.model.MappingException; 
import org.springframework.data.neo4j.annotation.NodeEntity; 
import org.springframework.data.neo4j.support.Neo4jTemplate; 

import my.modelUtils.BaseObject; 

@Aspect 
public class Neo4jFetchAspect { 

    // thew neo4j template - make sure to fill it 
    @Autowired private Neo4jTemplate template; 

    @Around("modelGetter()") 
    public Object autoFetch(ProceedingJoinPoint pjp) throws Throwable { 
     Object o = pjp.proceed(); 
     if(o != null) { 
      if(o.getClass().isAnnotationPresent(NodeEntity.class)) { 
       if(o instanceof BaseObject<?>) { 
        BaseObject<?> bo = (BaseObject<?>)o; 
        if(bo.getId() != null && !bo.isFetched()) { 
         return template.fetch(o); 
        } 
        return o; 
       } 
       try { 
        return template.fetch(o); 
       } catch(MappingException me) { 
        me.printStackTrace(); 
       } 
      } 
     } 
     return o; 
    } 

    @Pointcut("execution(public my.model.package.*.get*())") 
    public void modelGetter() {} 

} 

Sólo hay que adaptar la ruta de clase en la que se debe aplicar el aspecto: my.model.package. .get()) ")

Aplico el aspecto a TODO obtener métodos en mis clases modelo.Esto requiere unos prerequesites:

  • DEBE utilizar captadores en las clases del modelo (el aspecto no funciona en los atributos públicos - lo que no se debe utilizar de todos modos)
  • todas las clases del modelo están en el mismo paquete (lo que es necesario adaptar el código un poco) - Creo que se puede adaptar el filtro de
  • aspectj como un componente de tiempo de ejecución se requiere (un poco complicado cuando se utiliza Tomcat) - pero funciona :)
  • TODAS las clases del modelo debe implementar la interfaz BaseObject que proporciona:

    interfaz pública BaseObject { public boolean isFetched(); }

Esto evita la doble búsqueda. Solo compruebo una subclase o atributo que es obligatorio (es decir, el nombre u otra cosa, excepto nodeId) para ver si realmente se obtuvo. Neo4j creará un objeto pero solo llenará el nodeId y dejará intacto todo lo demás (para que todo lo demás sea NULL).

decir

@NodeEntity 
public class User implements BaseObject{ 
    @GraphId 
    private Long nodeId; 

     String username = null; 

    @Override 
    public boolean isFetched() { 
     return username != null; 
    } 
} 

Si alguien encuentra una manera de hacer esto sin que la solución raro por favor agregue su solución :) porque éste funciona, pero me encantaría uno sin aspectj.

diseño de objetos Base que doenst requiere una verificación de campo personalizado

Una optimización sería la creación de una base de clase en lugar de una interfaz que utiliza realmente un campo booleano (Boolean carga) y los controles de que (por lo no necesita preocuparse por la comprobación manual)

public abstract class BaseObject { 
    private Boolean loaded; 
    public boolean isFetched() { 
     return loaded != null; 
    } 
    /** 
    * getLoaded will always return true (is read when saving the object) 
    */ 
    public Boolean getLoaded() { 
     return true; 
    } 

    /** 
    * setLoaded is called when loading from neo4j 
    */ 
    public void setLoaded(Boolean val) { 
     this.loaded = val; 
    } 
} 

Esto funciona porque al guardar el objeto "true" se devuelve para cargarlo. Cuando el aspecto mira al objeto que utiliza es Obtenido() que - cuando el objeto aún no se recupera devolverá nulo. Una vez que se recupera el objeto, se llama a setLoaded y la variable cargada se establece en verdadero.

¿Cómo evitar que jackson active la carga diferida?

(Como respuesta a la pregunta en el comentario, tenga en cuenta que aún no lo he probado porque no tenía este problema).

Con jackson sugiero usar un serializador personalizado (vea por ejemplo http://www.baeldung.com/jackson-custom-serialization). Esto le permite verificar la entidad antes de obtener los valores.Sólo tiene que hacer una comprobación de si se trata ya leídos y, o bien continuar con toda la serialización o simplemente utilizar el ID:

public class ItemSerializer extends JsonSerializer<BaseObject> { 
    @Override 
    public void serialize(BaseObject value, JsonGenerator jgen, SerializerProvider provider) 
     throws IOException, JsonProcessingException { 
     // serialize the whole object 
     if(value.isFetched()) { 
      super.serialize(value, jgen, provider); 
      return; 
     } 
     // only serialize the id 
     jgen.writeStartObject(); 
     jgen.writeNumberField("id", value.nodeId); 
     jgen.writeEndObject(); 
    } 
} 

configuración del resorte

Este es un ejemplo de configuración de Primavera utilizo - lo que necesita para ajustar los paquetes a su proyecto:

<?xml version="1.0" encoding="UTF-8" standalone="no"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
     xmlns:context="http://www.springframework.org/schema/context" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xmlns:neo4j="http://www.springframework.org/schema/data/neo4j" 
     xmlns:tx="http://www.springframework.org/schema/tx" 
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 
http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> 

    <context:annotation-config/> 
    <context:spring-configured/> 

    <neo4j:repositories base-package="my.dao"/> <!-- repositories = dao --> 

    <context:component-scan base-package="my.controller"> 
     <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <!-- that would be our services --> 
    </context:component-scan> 
    <tx:annotation-driven mode="aspectj" transaction-manager="neo4jTransactionManager"/>  
    <bean class="corinis.util.aspects.Neo4jFetchAspect" factory-method="aspectOf"/> 
</beans> 

AOP config

este es el /META-INF/aop.xml para que esto funcione:

<!DOCTYPE aspectj PUBLIC 
     "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"> 
    <aspectj> 
     <weaver> 
      <!-- only weave classes in our application-specific packages --> 
      <include within="my.model.*" /> 
     </weaver> 
     <aspects> 
      <!-- weave in just this aspect --> 
      <aspect name="my.util.aspects.Neo4jFetchAspect" /> 
     </aspects> 
    </aspectj> 
+0

¡Buena solución de búsqueda automática! Un problema que tengo con esto es que utilizamos algunos frameworks como Jackson que no quiero permitir la recuperación automática. Por supuesto, podría dejar los getters a Jackson e implementar este punto en los métodos * .lazyGet *(), pero eso sería casi lo mismo que escribir neo4jTemplate.fetch (*. Get *()) (en realidad escribimos nuestro propio contenedor eso previene doble búsqueda también, por lo que el efecto sería el mismo). ¿Cómo resolverías este enigma? –

+1

Agregué una posible solución en el texto de la pregunta; quizás la edite con un fragmento de código que funcione ya que no probé esto. – Niko

+0

¡gran sugerencia! –

Respuesta

11

encontrado la respuesta a todas las preguntas a mí mismo:

@Iterable: sí, iterables puede ser utilizado para sólo lectura

@load on access: por defecto no se carga nada. y la carga diferida automática no está disponible (por lo menos en lo que puedo reunir)

Para el resto: Cuando necesito una relación I o bien tienen que utilizar @Fetch o utilizar el método neo4jtemplate.fetch:

@NodeEntity 
public class User { 
    @GraphId Long nodeId; 
    @RelatedTo(type="user", direction = Direction.INCOMING) 
    private Iterable<Worker> worker; 
    @Fetch Unit currentUnit; 

    String name; 

} 

class GetService { 
    @Autowired private Neo4jTemplate template; 

    public void doSomethingFunction() { 
    User u = ....; 
    // worker is not avaiable here 

    template.fetch(u.worker); 
    // do something with the worker 
    } 
} 
+3

Me parece realmente extraño que la carga lenta automática no esté disponible. ¿No debería haber una forma de implementarlo sin llamar constantemente a template.fetch en tu código? – CorayThan

3

No es transparente, pero igual lazy fetching.

template.fetch(person.getDirectReports()); 

Y @Fetch hace la búsqueda ansiosa como ya se indicó en su respuesta.

+0

@ s-t-e-v-e ¿hay alguna forma de paginarlo? ya que estoy tratando de obtener 'Set ' que puede ser grande en número. – agpt

+0

@agpt SDN admite Pageable. No lo he probado y no tengo claro cómo/si tiene sentido en el contexto de la recuperación perezosa. Ver http://stackoverflow.com/questions/20564661/spring-data-neo4j-pageable –

0

Me gusta el enfoque de aspecto para evitar la limitación de la forma actual de datos de primavera para manejar la carga diferida.

@niko - He puesto el ejemplo de código en un proyecto básico experto y trató de conseguir que la solución a trabajar con poco éxito:

https://github.com/samuel-kerrien/neo4j-aspect-auto-fetching 

Por alguna razón el aspecto se está inicializando pero el consejo no lo hace parece ser ejecutado. Para reproducir el problema, simplemente ejecute la siguiente prueba JUnit:

playground.neo4j.domain.UserTest 
+0

Hola Samuel, te falta el aop.xml para decirle a apsectj dónde aplicar el aspecto.También su configuración primavera parece faltar ... – Niko

+0

@niko He configurado a través de la primavera anotaciones en https://github.com/samuel-kerrien/neo4j-aspect-auto-fetching/blob/master/src/main/java /playground/neo4j/config/Neo4jConfig.java probablemente estoy perdiendo algo esencial ... –

+0

no se puede reemplazar la aop.xml con muelle (para este caso) - funcionan por separado el uno del otro: esa es también la razón por la cual el aspecto es en las clases MODELO y no en los neo4j daos. Describí todo un poco más detallado en http://stackoverflow.com/a/37808762/150740 – Niko