Pregunta

Tengo una configuración simple y encontré un problema desconcertante (al menos para mí):

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;
}

Por lo tanto, tiene unidades de usuarios-trabajador con una "CurrentUnit" que marca el usuario que permite saltar directamente a la "unidad actual". Cada usuario puede tener varios trabajadores, pero un trabajador solo se asigna a una unidad (una unidad puede tener múltiples trabajadores).

Lo que me preguntaba es cómo controlar el @Buscar Anotación en "User.Worker". De hecho, quiero que esto se lasee solo cuando sea necesario, porque la mayoría de las veces solo trabajo con "trabajador".

Fui a traves http://static.springsource.org/spring-neo/data-neo4j/docs/2.0.0.release/reference/html/ Y no está realmente claro para mí:

  • El trabajador es iterable porque solo debe leerse (relación entrante); en la documentación, esto se establece claramente, pero en los ejemplos se usa '' establecer '' la mayor parte del tiempo. ¿Por qué? o no importa ...
  • ¿Cómo hago que el trabajador solo cargue en el acceso? (carga lenta)
  • ¿Por qué necesito 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 resorte para que funcione con la carga perezosa?
  • ¿Hay alguna forma de cargar alguna relación (que no esté marcada como @Fetch) a través de una llamada adicional?

Por cómo lo veo, esta construcción carga toda la base de datos tan pronto como quiera un trabajador, incluso si no me importa el usuario la mayor parte del tiempo.

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

------- Actualizar -------

He estado trabajando con Neo4J bastante tiempo y encontré una solución para el problema anterior que no requiere llamar a buscar todo el tiempo (y por lo tanto no carga todo el gráfico). Solo 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() {}

}

Solo tiene que adaptar el classpath en el que se debe aplicar el aspecto: my.model.package..obtener())")

Aplico el aspecto a todos los métodos GET en mis clases de modelo. Esto requiere algunos prerreques:

  • Debe usar Getters en sus clases de modelos (el aspecto no funciona en atributos públicos, que no debe usar de todos modos)
  • Todas las clases de modelos están en el mismo paquete (por lo que debe adaptar un poco el código). Creo que podría adaptar el filtro
  • Se requiere un componente de tiempo de ejecución (un poco complicado cuando usas Tomcat), pero funciona :)
  • Todas las clases de modelos deben implementar la interfaz BaseObject que proporciona:

    interfaz pública baseBject {public boolean isfetched (); }

Esto evita la doble mochila. Simplemente reviso una subclase o atributo que sea obligatorio (es decir, el nombre o algo más excepto nodeid) para ver si realmente se obtiene. Neo4J creará un objeto pero solo llenará el nodoid y dejará todo lo demás intacto (para que todo lo demás sea nulo).

es 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 esa extraña solución, agregue su solución :) porque esta funciona, pero me encantaría uno sin aspecto.

Diseño de objetos base que doenst requiere una verificación de campo personalizada

Una optimización sería crear una clase base en lugar de una interfaz que realmente use un campo booleano (cargado booleano) y verifique eso (por lo que no necesita preocuparse por la verificació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 "verdadero" se devuelve para cargar. Cuando el aspecto mira el objeto, usa isfetched () que, cuando el objeto aún no está recuperado, volverá nulo. Una vez que se recupera el objeto, se llama cargada y la variable cargada se establece en True.

¿Cómo evitar que Jackson active la carga perezosa?

(Como respuesta a la pregunta en el comentario: tenga en cuenta que todavía no lo probé ya que no tenía este problema).

Con Jackson sugiero usar un serializador personalizado (ver es decir http://www.baeldung.com/jackson-custom-serialization ). Esto le permite verificar la entidad antes de obtener los valores. Simplemente haga un cheque si ya está obtenido y continúa con toda la serialización o simplemente usa la 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 de resorte

Esta es una configuración de resorte de muestra que uso: debe 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>

Configuración de AOP

Este es el /metainf/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>
¿Fue útil?

Solución

Encontré la respuesta a todas las preguntas yo mismo:

@Iterable: Sí, ITerable se puede usar para Readonly

@Load on Access: por valor predeterminado, nada se carga. y la carga perezosa automática no está disponible (al menos hasta donde puedo reunir)

Para el resto: cuando necesito una relación, tengo que usar @fetch o usar 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
  }  
}

Otros consejos

No transparente, pero aún así Pelzy Reting.

template.fetch(person.getDirectReports());

Y @fetch hace que el ansioso fuera como ya se dijo en su respuesta.

Me gusta el enfoque de aspecto para trabajar en torno a la limitación de la forma actual de datos de primavera de manejar la carga perezosa.

@Niko: he puesto su muestra de código en un proyecto Maven básico e intenté que esa solución funcione con poco éxito:

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

Por algunas razones, el aspecto se inicializa, pero el consejo no parece ser ejecutado. Para reproducir el problema, simplemente ejecute la siguiente prueba de Junit:

playground.neo4j.domain.UserTest
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top