Frage

Ich habe ein einfaches Setup und habe ein rätselhaftes Problem (zumindest für mich) gestoßen:

Ich habe drei Pojos, die miteinander verwandt sind:

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

Sie haben also Benutzer-Arbeiter-Einheit mit einem "CurrentUnit", das den Benutzer markiert, mit dem direkt zum "aktuellen Einheit" springen können. Jeder Benutzer kann mehrere Mitarbeiter haben, aber ein Arbeiter wird nur einer Einheit zugeordnet (eine Einheit kann mehrere Arbeitnehmer haben).

Ich habe mich gefragt, wie man das kontrollieren kann @Bringen Annotation zu "User.worker". Ich möchte tatsächlich, dass dies nur bei Bedarf laodiert wird, weil ich die meiste Zeit nur mit "Arbeitern" arbeite.

Ich ging durch http://static.springource.org/spring-data/data-neo4j/docs/2.0.0.release/reference/html/ Und es ist mir nicht wirklich klar:

  • Der Arbeiter ist iterbar, weil es nur gelesen werden sollte (eingehende Beziehung) - in der Dokumentation wird dies klärsklar angegeben, aber in den Beispielen "set" "wird die meiste Zeit verwendet. Wieso den? Oder spielt es nicht ...
  • Wie kann ich Worker nur den Zugriff laden? (faules Laden)
  • Warum muss ich auch die einfachen Beziehungen (Worker.unit) mit @Fetch kommentieren? Gibt es keinen besseren Weg? Ich habe eine andere Entität mit vielen solch einfachen Beziehungen - ich möchte wirklich vermeiden, das gesamte Diagramm laden zu müssen, nur weil ich die Eigenschaften eines Objekts möchte.
  • Fehlt mir eine Federkonfiguration, damit sie mit faulen Laden funktioniert?
  • Gibt es eine Möglichkeit, Beziehungen (die nicht als @Fetch) über einen zusätzlichen Anruf zu laden?

Aus dem, was ich es sehe, lädt dieses Konstrukt die gesamte Datenbank, sobald ich einen Arbeiter möchte, auch wenn mir der Benutzer die meiste Zeit egal ist.

Die einzige Problemumgehung, die ich gefunden habe, besteht darin, das Repository zu verwenden und die Entitäten bei Bedarf manuell zu laden.

------- Aktualisieren -------

Ich habe schon einige Zeit mit Neo4j zusammengearbeitet und eine Lösung für das obige Problem gefunden, bei dem nicht die ganze Zeit aufgerufen werden muss (und somit nicht das gesamte Diagramm lädt). Nur Nachteil: Es ist ein Laufzeitaspekt:

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

}

Sie müssen nur den Klassenpfad anpassen, auf dem der Aspekt angewendet werden sollte: my.model.package..erhalten())")

Ich wende den Aspekt an, um alle Methoden in meinen Modellkursen zu erhalten. Dies erfordert ein paar Voraussetzungen:

  • Sie müssen Getter in Ihren Modellklassen verwenden (der Aspekt funktioniert nicht auf öffentlichen Attributen - die Sie sowieso nicht verwenden sollten)
  • Alle Modellklassen befinden sich im selben Paket (Sie müssen den Code also ein wenig anpassen) - ich denke, Sie könnten den Filter anpassen
  • Aspektj als Laufzeitkomponente ist erforderlich (etwas schwierig, wenn Sie Tomcat verwenden) - aber es funktioniert :)
  • Alle Modellklassen müssen die BaseObject -Schnittstelle implementieren, die enthält:

    public interface baseObject {public boolean isfized (); }

Dies verhindert doppelte Abrechnung. Ich prüne nur nach einer Unterklasse oder einem Attribut, das obligatorisch ist (dh der Name oder etwas anderes außer Nodeid), um festzustellen, ob es tatsächlich abgerufen wird. Neo4j erstellt ein Objekt, füllt aber nur das Nodeid und lässt alles andere unberührt (also ist alles andere NULL).

dh

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

        String username = null;

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

Wenn jemand einen Weg findet, dies ohne diese seltsame Problemumgehung zu tun, fügen Sie bitte Ihre Lösung hinzu :) Weil dieser funktioniert, aber ich würde eine ohne Aspektjj lieben.

Basisobjektdesign, das Doenst eine benutzerdefinierte Feldprüfung benötigt

Eine Optimierung wäre, eine Basisklasse anstelle einer Schnittstelle zu erstellen, die tatsächlich ein boolesches Feld (boolean geladen) verwendet und dies überprüft (sodass Sie sich keine Sorgen um manuelle Überprüfung machen müssen)

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

Dies funktioniert, denn beim Speichern des Objekts "True" wird für das Laden zurückgegeben. Wenn der Aspekt das Objekt untersucht, verwendet er isfized (), das - wenn das Objekt noch nicht abgerufen wird, null zurückgibt. Sobald das Objekt abgerufen wurde, wird eingerichtet und die geladene Variable auf true eingestellt.

Wie kann Jackson daran hindern, das faule Laden auszulösen?

(Als Antwort auf die Frage im Kommentar - beachten Sie, dass ich es noch nicht ausprobiert habe, da ich dieses Problem nicht hatte).

Mit Jackson schlage ich vor, einen benutzerdefinierten Serializer zu verwenden (siehe IE http://www.baeldung.com/jackson-custom-serialization ). Auf diese Weise können Sie die Entität überprüfen, bevor Sie die Werte erhalten. Sie führen einfach eine Überprüfung durch, ob es bereits abgerufen ist, und machen entweder die gesamte Serialisierung oder verwenden einfach die 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();
    }
}

Federkonfiguration

Dies ist eine Beispielfederkonfiguration, die ich verwende - Sie müssen die Pakete an Ihr Projekt anpassen:

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

Dies ist der /meta-inf/aop.xml, damit dies funktioniert:

<!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>
War es hilfreich?

Lösung

Fand die Antwort auf alle Fragen selbst:

@Iterable: Ja, iterable kann für Readonly verwendet werden

@load on Access: Per Standard wird nichts geladen. und automatisches faule Laden ist nicht verfügbar (zumindest soweit ich sammeln kann)

Für den Rest: Wenn ich eine Beziehung brauche, muss ich entweder @Fetch oder die Neo4jtemplate.fetch -Methode verwenden:

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

Andere Tipps

Nicht transparent, aber immer noch faules Abrufen.

template.fetch(person.getDirectReports());

Und @Fetch macht das eifrige Abrufen, wie bereits in Ihrer Antwort angegeben.

Ich mag den Aspektansatz, um die Begrenzung der aktuellen Federdaten-Methode um faulen Laden zu bearbeiten.

@niko - Ich habe Ihr Code -Beispiel in ein grundlegendes Maven -Projekt eingebracht und versucht, diese Lösung mit wenig Erfolg zu machen:

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

Aus einigen Gründen ist der Aspekt initialisieren, aber der Rat scheint nicht hingerichtet zu werden. Um das Problem zu reproduzieren, führen Sie einfach den folgenden Junit -Test durch:

playground.neo4j.domain.UserTest
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top