Ленивая/нетерпеливая загрузка/извлечение в Neo4j/Spring-Data

StackOverflow https://stackoverflow.com/questions/8852491

Вопрос

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

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

@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», который отмечает пользователя, позволяющий перейти непосредственно к «текущему блоку».Каждый Пользователь может иметь несколько работников, но один работник назначается только одному подразделению (одно подразделение может иметь несколько работников).

Меня интересует, как контролировать @Принести аннотация к «User.worker».На самом деле я хочу, чтобы это загружалось только при необходимости, потому что большую часть времени я работаю только с «Worker».

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

  • Worker является итеративным, поскольку его следует читать только (входящее отношение) - в документации это четко указано, но в примерах большую часть времени используется 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() {}

}

Вам просто нужно адаптировать путь к классам, к которому следует применить аспект:мой.модельный.пакет..получать())")

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

  • Вы ДОЛЖНЫ использовать геттеры в своих классах моделей (этот аспект не работает с общедоступными атрибутами, которые вам все равно не следует использовать).
  • все классы модели находятся в одном пакете (поэтому вам нужно немного адаптировать код) - думаю, вы могли бы адаптировать фильтр
  • Требуется аспектj в качестве компонента времени выполнения (немного сложно, если вы используете 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;
    }
}

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

Дизайн базового объекта, не требующий проверки настраиваемых полей.

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

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

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

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

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

С Джексоном я предлагаю использовать собственный сериализатор (см. 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, которую я использую — вам нужно настроить пакеты под ваш проект:

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

Конфигурация АОП

это /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>
Это было полезно?

Решение

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

@Итерабле:да, iterable можно использовать только для чтения

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

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

Другие советы

Не прозрачно, но все же ленивая выборка.

template.fetch(person.getDirectReports());

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

Мне нравится аспектный подход, позволяющий обойти ограничение текущего способа обработки отложенной загрузки данных Spring.

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

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

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

playground.neo4j.domain.UserTest
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top