简体   繁体   English

在Neo4j / Spring-Data中加载/获取Lazy / Eager

[英]Lazy/Eager loading/fetching in Neo4j/Spring-Data

I have a simple setup and encountered a puzzling (at least for me) problem: 我有一个简单的设置,遇到一个令人费解的(至少对我来说)问题:

I have three pojos which are related to each other: 我有三个相互关联的pojos:

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

So you have User-Worker-Unit with a "currentunit" which marks in user that allows to jump directly to the "current unit". 所以你有User-Worker-Unit有一个“currentunit”,它在用户中标记允许直接跳转到“当前单位”。 Each User can have multiple workers, but one worker is only assigned to one unit (one unit can have multiple workers). 每个用户可以有多个工作人员,但只有一个工作人员被分配到一个单元(一个单元可以有多个工作人员)。

What I was wondering is how to control the @Fetch annotation on "User.worker". 我想知道的是如何控制“User.worker”上的@Fetch注释。 I actually want this to be laoded only when needed, because most of the time I only work with "Worker". 我实际上希望这只在需要的时候加盖,因为大多数时候我只和“工人”一起工作。

I went through http://static.springsource.org/spring-data/data-neo4j/docs/2.0.0.RELEASE/reference/html/ and it isn't really clear to me: 我浏览了http://static.springsource.org/spring-data/data-neo4j/docs/2.0.0.RELEASE/reference/html/ ,这对我来说并不是很清楚:

  • worker is iterable because it should be read only (incoming relation) - in the documentation this is stated clarly, but in the examples ''Set'' is used most of the time. worker是可迭代的,因为它应该是只读的(传入关系) - 在文档中这是明确说明的,但是在示例中''Set''在大多数情况下被使用。 Why? 为什么? or doesn't it matter... 或者不重要......
  • How do I get worker to only load on access? 如何让工作人员只加载访问权限? (lazy loading) (懒加载)
  • Why do I need to annotate even the simple relations (worker.unit) with @Fetch. 为什么我需要用@Fetch注释简单的关系(worker.unit)。 Isn't there a better way? 有没有更好的方法? I have another entity with MANY such simple relations - I really want to avoid having to load the entire graph just because i want to the properties of one object. 我有另外一个与MANY这样简单关系的实体 - 我真的想避免因为我想要一个对象的属性而加载整个图形。
  • Am I missing a spring configuration so it works with lazy loading? 我错过了一个弹簧配置,所以它适用于延迟加载?
  • Is there any way to load any relationships (which are not marked as @Fetch) via an extra call? 有没有办法通过额外的呼叫加载任何关系(没有标记为@Fetch)?

From how I see it, this construct loads the whole database as soon as I want a Worker, even if I don't care about the User most of the time. 从我的看法来看,这个构造只要我想要一个Worker就加载整个数据库,即使我大部分时间都不关心用户。

The only workaround I found is to use repository and manually load the entities when needed. 我发现的唯一解决方法是使用存储库并在需要时手动加载实体。

------- Update ------- -------更新-------

I have been working with neo4j quite some time now and found a solution for the above problem that does not require calling fetch all the time (and thus does not load the whole graph). 我已经使用neo4j很长一段时间了,并找到了上述问题的解决方案,它不需要一直调用fetch(因此不会加载整个图形)。 Only downside: it is a runtime aspect: 唯一的缺点:它是运行时方面:

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

}

You just have to adapt the classpath on which the aspect should be applied: my.model.package. 您只需要调整应该应用方面的类路径:my.model.package。 .get ())") .get ())“)

I apply the aspect to ALL get methods on my model classes. 我将方面应用于我的模型类上的ALL get方法。 This requires a few prerequesites: 这需要一些先决条件:

  • You MUST use getters in your model classes (the aspect does not work on public attributes - which you shouldn't use anyways) 你必须在模型类中使用getter(方面不适用于公共属性 - 你不应该使用它们)
  • all model classes are in the same package (so you need to adapt the code a little) - I guess you could adapt the filter 所有模型类都在同一个包中(所以你需要调整一点代码) - 我想你可以调整过滤器
  • aspectj as a runtime component is required (a little tricky when you use tomcat) - but it works :) aspectj作为运行时组件是必需的(当你使用tomcat时有点棘手) - 但它的工作原理:)
  • ALL model classes must implement the BaseObject interface which provides: 所有模型类都必须实现BaseObject接口,该接口提供:

    public interface BaseObject { public boolean isFetched(); public interface BaseObject {public boolean isFetched(); } }

This prevents double-fetching. 这可以防止双重抓取。 I just check for a subclass or attribute that is mandatory (ie the name or something else except nodeId) to see if it is actually fetched. 我只检查必需的子类或属性(即除了nodeId之外的名称或其他内容)以查看它是否实际被提取。 Neo4j will create an object but only fill the nodeId and leave everything else untouched (so everything else is NULL). Neo4j将创建一个对象,但只填充nodeId并保持其他所有内容不变(因此其他所有内容都为NULL)。

ie

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

        String username = null;

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

If someone finds a way to do this without that weird workaround please add your solution :) because this one works, but I would love one without aspectj. 如果有人找到一种方法来做到这一点没有那种奇怪的解决方法,请添加你的解决方案:)因为这个工作,但我会喜欢没有aspectj的人。

Base object design that doenst require a custom field check doenst的基础对象设计需要自定义字段检查

One optimization would be to create a base-class instead of an interface that actually uses a Boolean field (Boolean loaded) and checks on that (so you dont need to worry about manual checking) 一个优化是创建一个基类而不是实际使用布尔字段的接口(布尔加载)并检查它(所以你不必担心手动检查)

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

This works because when saving the object "true" is returned for loaded. 这是有效的,因为保存对象时返回“true”以进行加载。 When the aspect looks at the object it uses isFetched() which - when the object is not yet retrieved will return null. 当方面查看它使用的对象isFetched()时 - 当尚未检索到对象时将返回null。 Once the object is retrieved setLoaded is called and the loaded variable set to true. 检索到对象后,将调用setLoaded并将加载的变量设置为true。

How to prevent jackson from triggering the lazy loading? 如何防止杰克逊触发延迟加载?

(As an answer to the question in the comment - note that I didnt try it out yet since I did not have this issue). (作为对评论中问题的回答 - 请注意,由于我没有这个问题,我还没有尝试过)。

With jackson I suggest to use a custom serializer (see ie http://www.baeldung.com/jackson-custom-serialization ). 有了杰克逊,我建议使用自定义序列化程序(参见http://www.baeldung.com/jackson-custom-serialization )。 This allows you to check the entity before getting the values. 这允许您在获取值之前检查实体。 You simply do a check if it is already fetched and either go on with the whole serialization or just use the id: 您只需检查它是否已被提取,并继续使用整个序列化或只使用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();
    }
}

Spring Configuration 弹簧配置

This is a sample Spring configuration I use - you need to adjust the packages to your project: 这是我使用的示例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 config AOP配置

this is the /META-INF/aop.xml for this to work: 这是/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>

Found the answer to all the questions myself: 自己找到了所有问题的答案:

@Iterable: yes, iterable can be used for readonly @Iterable:是的,iterable可以用于readonly

@load on access: per default nothing is loaded. @load on access:默认情况下,没有加载任何内容。 and automatic lazy loading is not available (at least as far as I can gather) 和自动延迟加载不可用(至少我可以收集)

For the rest: When I need a relationship I either have to use @Fetch or use the neo4jtemplate.fetch method: 其余的:当我需要一个关系时,我要么必须使用@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
  }  
}

Not transparent, but still lazy fetching . 不透明,但仍懒得取物

template.fetch(person.getDirectReports());

And @Fetch does the eager fetching as was already stated in your answer. 正如你的回答中已经说明的那样,@ Fetch做了热切的提取。

I like the aspect approach to work around the limitation of the current spring-data way to handle lazy loading. 我喜欢使用方面方法来解决当前spring-data方法处理延迟加载的限制。

@niko - I have put your code sample in a basic maven project and tried to get that solution to work with little success: @niko - 我已将您的代码示例放在一个基本的maven项目中,并尝试使该解决方案工作失败:

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

For some reasons the Aspect is initialising but the advice doesn't seem to get executed. 由于某些原因,Aspect正在初始化,但建议似乎没有得到执行。 To reproduce the issue, just run the following JUnit test: 要重现此问题,只需运行以下JUnit测试:

playground.neo4j.domain.UserTest

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM