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>
¡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? –
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
¡gran sugerencia! –