Domanda

Ho una semplice configurazione e ho riscontrato un problema sconcertante (almeno per me):

Ho tre pojos che sono correlati tra loro:

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

Quindi hai unità utente-lavoratore con una "Unit Unit" che segna l'utente che consente di saltare direttamente alla "unità corrente". Ogni utente può avere più lavoratori, ma un lavoratore viene assegnato solo a un'unità (un'unità può avere più lavoratori).

Quello che mi chiedevo è come controllare il @Andare a prendere Annotazione su "User.worker". In realtà voglio che questo venga lad solo quando necessario, perché la maggior parte delle volte lavoro solo con "lavoratore".

attraversai http://static.springsource.org/spring-data/data-neo4j/docs/2.0.0.release/reference/html/ E non è davvero chiaro per me:

  • Il lavoratore è iterabile perché dovrebbe essere lettura solo (relazione in arrivo) - nella documentazione questo è dichiarato clamoroso, ma negli esempi "set" viene utilizzato la maggior parte del tempo. Come mai? o non importa ...
  • Come faccio a far caricare il lavoratore solo sull'accesso? (caricamento pigro)
  • Perché devo annotare anche le semplici relazioni (worker.UNIT) con @fetch. Non c'è un modo migliore? Ho un'altra entità con molte relazioni così semplici: voglio davvero evitare di dover caricare l'intero grafico solo perché voglio le proprietà di un oggetto.
  • Mi manca una configurazione a molla in modo che funzioni con il caricamento pigro?
  • Esiste un modo per caricare relazioni (che non sono contrassegnate come @fetch) tramite una chiamata extra?

Da come lo vedo, questo costrutto carica l'intero database non appena voglio un lavoratore, anche se non mi interessa l'utente per la maggior parte del tempo.

L'unica soluzione alternativa che ho trovato è utilizzare il repository e caricare manualmente le entità quando necessario.

------- Aggiornare -------

Ho lavorato con Neo4j un po 'di tempo e ho trovato una soluzione per il problema di cui sopra che non richiede il recupero delle chiamate tutto il tempo (e quindi non carica l'intero grafico). Solo negativo: è un aspetto di runtime:

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

}

Devi solo adattare il percorso di classe su cui dovrebbe essere applicato l'aspetto: my.model.package..ottenere())")

Applico l'aspetto a tutti i metodi OTTIENI sulle mie classi di modelli. Ciò richiede alcuni prerequesiti:

  • È necessario utilizzare i getter nelle classi di modelli (l'aspetto non funziona sugli attributi pubblici - che non dovresti usare comunque)
  • Tutte le classi di modelli sono nello stesso pacchetto (quindi devi adattare un po 'il codice) - Immagino che potresti adattare il filtro
  • Aspectj come componente di runtime è richiesto (un po 'complicato quando usi tomcat) - ma funziona :)
  • Tutte le classi di modelli devono implementare l'interfaccia BaseObject che fornisce:

    public interface BaseObject {public boolean isFetched (); }

Questo impedisce il doppio successo. Controllo solo una sottoclasse o un attributo obbligatorio (cioè il nome o qualcos'altro tranne Nodeid) per vedere se è effettivamente recuperato. Neo4j creerà un oggetto ma riempirà solo il nodeid e lascerà tutto il resto intatto (quindi tutto il resto è nullo).

cioè

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

        String username = null;

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

Se qualcuno trova un modo per farlo senza quella strana soluzione alternativa, aggiungi la tua soluzione :) Perché questa funziona, ma mi piacerebbe uno senza aspetti.

Base Object Design That Doenst richiede un controllo del campo personalizzato

Un'ottimizzazione sarebbe quella di creare una classe base anziché un'interfaccia che utilizza effettivamente un campo booleano (caricato booleano) e controlla su quello (quindi non devi preoccuparti del controllo manuale)

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

Funziona perché quando si salva l'oggetto "vero" viene restituito per caricamento. Quando l'aspetto guarda l'oggetto, usa isfetched () che - quando l'oggetto non è ancora stato recuperato tornerà nullo. Una volta che l'oggetto viene recuperato, viene chiamato e la variabile caricata impostata su true.

Come impedire a Jackson di innescare il caricamento pigro?

(Come risposta alla domanda nel commento - Nota che non l'ho ancora provato poiché non avevo questo problema).

Con Jackson suggerisco di utilizzare un serializzatore personalizzato (vedi IE http://www.baeldung.com/jackson-custom-serialization ). Ciò consente di controllare l'entità prima di ottenere i valori. Fai semplicemente un controllo se è già recuperato e vai avanti con l'intera serializzazione o usi semplicemente l'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();
    }
}

Configurazione a molla

Questa è una configurazione a molla di esempio che utilizzo: è necessario regolare i pacchetti al progetto:

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

Questo è /meta-inf/aop.xml per questo funzionare:

<!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>
È stato utile?

Soluzione

Ho trovato la risposta a tutte le domande da solo:

@Iterable: sì, iTrable può essere usato per lettura

@load on Access: per impostazione predefinita non viene caricato nulla. e il caricamento automatico pigro non è disponibile (almeno per quanto posso raccogliere)

Per il resto: quando ho bisogno di una relazione, devo usare @fetch o usare il metodo 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
  }  
}

Altri suggerimenti

Non trasparente, ma comunque recupero pigro.

template.fetch(person.getDirectReports());

E @fetch fa il recupero desideroso come già indicato nella tua risposta.

Mi piace l'approccio dell'aspetto per aggirare la limitazione dell'attuale modo dei dati a molla per gestire il carico pigro.

@niko - Ho messo il tuo campione di codice in un progetto Maven di base e ho provato a far funzionare quella soluzione con scarso successo:

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

Per alcuni motivi l'aspetto sta inizializzando ma il consiglio non sembra essere eseguito. Per riprodurre il problema, esegui il seguente test JUnit:

playground.neo4j.domain.UserTest
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top