Lazy/Eager загрузка/выборка в Neo4j/Spring -Data

У меня простая настройка и столкнулся с проблемой (по крайней мере для меня):

У меня есть три поля, которые связаны друг с другом:

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

Итак, у вас есть User-Worker-Unit с "currentunit", который помещает в пользователя, который позволяет перейти непосредственно к "текущему блоку". Каждый пользователь может иметь несколько сотрудников, но один рабочий назначается только одному блоку (одна единица может иметь несколько сотрудников).

Мне было интересно, как управлять аннотацией @Fetch на "User.worker". Я на самом деле хочу, чтобы это было исправлено только в случае необходимости, потому что большую часть времени я работаю только с "Рабочим".

Я прошел http://static.springsource.org/spring-data/data-neo4j/docs/2.0.0.RELEASE/reference/html/, и мне это не совсем понятно:

  • рабочий итерабельен, потому что он должен быть только для чтения (входящее отношение). В документации это указано четко, но в примерах "Set" используется большую часть времени. Зачем? или это не имеет значения...
  • Как заставить рабочего загружать только доступ? (ленивая загрузка)
  • Зачем мне нужно аннотировать даже простые отношения (worker.unit) с помощью @Fetch. Разве нет лучшего способа? У меня есть другая сущность с МНОГИМИ таких простых отношений - я действительно хочу избежать необходимости загружать весь график только потому, что хочу свойства одного объекта.
  • Мне не хватает конфигурации spring, поэтому она работает с ленивой загрузкой?
  • Есть ли способ загрузить любые отношения (которые не помечены как @Fetch) через дополнительный вызов?

Из того, как я вижу это, эта конструкция загружает всю базу данных, как только я хочу Рабочего, даже если я не забочусь о пользователе большую часть времени.

Единственным обходным решением, которое я нашел, является использование репозитория и ручная загрузка объектов при необходимости.

------- Обновить -------

Я работаю с neo4j довольно давно и нашел решение для вышеупомянутой проблемы, которая не требует вызова fetch все время (и, следовательно, не загружает весь график). Единственный недостаток: это аспект выполнения:

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

}

Вам просто нужно адаптировать путь к классам, на котором должен применяться аспект: my.model.package..get()) ")

Я применяю этот аспект для ВСЕХ методов get на моих классах моделей. Для этого требуется несколько предварительных условий:

  • Вы ДОЛЖНЫ использовать геттеры в ваших классах моделей (аспект не работает с общедоступными атрибутами, которые вы не должны использовать в любом случае).
  • все классы моделей находятся в одном пакете (так что вам нужно немного адаптировать код) - думаю, вы могли бы адаптировать фильтр
  • aspectj как компонент времени выполнения (немного сложнее при использовании tomcat), но он работает:)
  • Все классы модели должны реализовывать интерфейс BaseObject, который обеспечивает:

    Открытый интерфейс BaseObject {   public boolean isFetched(); }

Это предотвращает двойную выборку. Я просто проверяю наличие подкласса или атрибута, который является обязательным (например, имя или что-то еще, кроме nodeId), чтобы узнать, действительно ли он получен. Neo4j создаст объект, но только заполнит nodeId и оставит все остальное нетронутым (так что все остальное будет NULL).

то есть.

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

        String username = null;

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

Если кто-то найдет способ сделать это без этого странного обходного пути, добавьте свое решение:), потому что этот работает, но я бы обожал его без aspectj.

Конструкция базового объекта, для которой doenst требуется специальная проверка поля

Одной из оптимизаций было бы создание базового класса вместо интерфейса, который фактически использует логическое поле (Boolean loaded) и проверяет его (поэтому вам не нужно беспокоиться о ручной проверке)

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

Это работает, потому что при сохранении объекта "true" возвращается для загрузки. Когда аспект смотрит на объект, который он использует isFetched(), который - когда объект еще не восстановлен, возвращает null. После восстановления объекта вызывается setLoaded, а загруженная переменная установлена ​​в true.

Как предотвратить джексон от запуска ленивой загрузки?

(В качестве ответа на вопрос в комментарии - заметьте, что я еще не пробовал это, так как у меня не было этой проблемы).

С помощью jackson я предлагаю использовать собственный сериализатор (см., например, http://www.baeldung.com/jackson-custom-serialization). Это позволяет проверить объект перед получением значений. Вы просто делаете чек, если он уже загружен, либо продолжайте всю сериализацию, либо просто используйте идентификатор:

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

Spring Конфигурация

Это пример конфигурации spring, которую я использую - вам нужно настроить пакеты в свой проект:

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

это/META-INF/aop.xml для этого:

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

Ответы

Ответ 1

Нашел ответ на все вопросы:

@Iterable: yes, iterable может использоваться для readonly

@load при доступе: по умолчанию ничто не загружается. и автоматическая ленивая загрузка недоступна (по крайней мере, насколько я могу собрать)

В остальном: Когда мне нужно отношение, я либо должен использовать @Fetch, либо использовать метод 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
  }  
}

Ответ 2

Не прозрачный, но ленивый выбор.

template.fetch(person.getDirectReports());

И @Fetch делает горячую выборку, как уже было сказано в вашем ответе.

Ответ 3

Мне нравится аспектный подход, чтобы обойти ограничение текущего метода spring -data для обработки ленивой загрузки.

@niko - Я поместил ваш образец кода в базовый проект maven и попытался получить это решение для работы с небольшим успехом:

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

По каким-то причинам Аспект инициализируется, но совет, похоже, не выполняется. Чтобы воспроизвести проблему, просто запустите следующий тест JUnit:

playground.neo4j.domain.UserTest