Question

J'ai une configuration simple et rencontré un problème déconcertant (au moins pour moi):

J'ai trois POJO qui sont liés les uns aux autres:

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

Vous avez donc l'utilisateur travailleur-unité avec un « currentunit », qui marque dans l'utilisateur qui permet de passer directement à la « unité actuelle ». Chaque utilisateur peut avoir plusieurs travailleurs, mais un travailleur est uniquement attribué à une unité (une unité peut avoir plusieurs travailleurs).

Ce que je me demandais comment contrôler la @Fetch annotation sur "User.worker". Je veux vraiment que cela soit laoded seulement en cas de besoin, parce que la plupart du temps je ne travaille avec « travailleur ».

Je suis passé par http : //static.springsource.org/spring-data/data-neo4j/docs/2.0.0.RELEASE/reference/html/ et il est pas vraiment clair pour moi:

  • travailleur itérables est parce qu'il doit être lu uniquement (relation entrante) - dans la documentation cela est indiqué clarly, mais dans les exemples « » Set « » est utilisé la plupart du temps. Pourquoi? ou ne pas d'importance ...
  • Comment puis-je obtenir travailleur à charge que sur l'accès? (Chargement paresseux)
  • Pourquoi dois-je annoter même les relations simples (worker.unit) avec @Fetch. N'y at-il un moyen de mieux? J'ai une autre entité avec beaucoup de ces relations simples - Je veux vraiment éviter d'avoir à charger le graphique entier juste parce que je veux les propriétés d'un objet
  • .
  • Suis-je manque une configuration de ressort, de sorte qu'il fonctionne avec le chargement paresseux?
  • Est-il possible de charger des relations (qui ne sont pas marqués comme @Fetch) par un appel supplémentaire?

De la façon dont je le vois, cette construction charge la base de données entière dès que je veux un travailleur, même si je ne me soucie pas de l'utilisateur la plupart du temps.

La seule solution que j'ai trouvé est d'utiliser du référentiel et charger manuellement les entités en cas de besoin.

------- ------- Mise à jour

Je travaille avec Neo4j un certain temps maintenant et a trouvé une solution pour le problème ci-dessus qui ne nécessitent pas d'appeler chercher tout le temps (et donc ne charge pas tout le graphique). Seul inconvénient: il est un aspect d'exécution:

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() {}

}

Il vous suffit d'adapter le classpath sur lequel l'aspect doit être appliqué. My.model.package .get ()) ")

J'applique l'aspect à toutes les méthodes get mes classes de modèle. Cela nécessite quelques Prérequis:

  • Vous devez utiliser getters dans vos classes de modèle (l'aspect ne fonctionne pas sur les attributs publics - que vous ne devriez pas utiliser de toute façon)
  • toutes les classes de modèles sont dans le même paquet (de sorte que vous devez adapter le code un peu) - Je suppose que vous pouvez adapter le filtre
  • aspectj en tant que composant d'exécution est nécessaire (un peu délicat lorsque vous utilisez tomcat) - mais cela fonctionne:)
  • TOUTES les classes de modèle doivent implémenter l'interface BaseObject qui fournit:

    {public interface BaseObject public boolean isFetched (); }

Cela évite à double extraction. Je viens de vérifier pour une sous-classe ou attribut est obligatoire (à savoir le nom ou quelque chose d'autre, sauf nodeId) pour voir si elle est en fait tiré par les cheveux. Neo4j va créer un objet mais seulement remplir le nodeId et tout le reste intact congé (si tout le reste est NULL).

i.e..

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

        String username = null;

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

Si quelqu'un trouve un moyen de le faire sans que solution de contournement bizarre s'il vous plaît ajouter votre solution :) parce que celui-ci fonctionne, mais j'aimerais un sans aspectj.

conception d'objets de base qui doenst nécessitent un contrôle de champ personnalisé

Une optimisation serait de créer un lieu de classe de base d'une interface qui utilise en fait un champ booléen (Boolean chargé) et les contrôles sur ce (si vous ne avez pas besoin de vous soucier de la vérification manuelle)

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

Cela fonctionne parce que lors de l'enregistrement de l'objet « true » est retourné pour charger. Lorsque l'aspect regarde l'objet qu'il utilise isFetched () qui- lorsque l'objet est pas encore récupéré retournera null. Une fois que l'objet est récupéré setLoaded est appelé et l'ensemble variable chargée à true.

Comment éviter jackson de déclencher le chargement paresseux?

(En réponse à la question dans le commentaire - Notez que je na pas essayer encore que je n'ai pas eu ce problème).

Avec jackson je suggère d'utiliser un sérialiseur personnalisé (voir par exemple http: //www.baeldung .com / jackson-custom-sérialisation ). Cela vous permet de vérifier l'entité avant d'obtenir les valeurs. Vous faites simplement un chèque si elle est déjà tiré par les cheveux et soit aller avec la sérialisation tout ou il suffit d'utiliser l'identifiant:

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

Configuration printemps

Ceci est un exemple de configuration Spring I utilisation - vous devez ajuster les paquets à votre projet:

<?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>

config AOP

est le /META-INF/aop.xml pour que cela fonctionne:

<!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>
Était-ce utile?

La solution

trouvé la réponse à toutes les questions moi-même:

@Iterable: oui, itérables peut être utilisé pour readonly

@load sur l'accès: par rien par défaut est chargé. et le chargement automatique paresseux ne sont pas disponibles (au moins autant que je sache)

Pour le reste: Quand je besoin d'une relation que j'ai soit d'utiliser @Fetch ou utiliser la méthode 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
  }  
}

Autres conseils

Non transparent, mais toujours paresseux aller chercher.

template.fetch(person.getDirectReports());

Et @Fetch fait le fetching désireux comme nous l'avons déjà dit dans votre réponse.

J'aime l'approche d'aspect au travail autour de la limitation de la façon dont ressort des données actuelles pour gérer le chargement paresseux.

@niko - J'ai mis votre exemple de code dans un projet Maven de base et essayé d'obtenir cette solution à travailler avec peu de succès:

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

Pour certaines raisons, l'aspect est en cours d'initialisation, mais les conseils ne semble pas été exécutée. Pour reproduire le problème, il suffit d'exécuter le test JUnit suivant:

playground.neo4j.domain.UserTest
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top