Neo4j/Spring-Data中的懒/急加载/获取 [英] Lazy/Eager loading/fetching in Neo4j/Spring-Data

查看:79
本文介绍了Neo4j/Spring-Data中的懒/急加载/获取的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的设置很简单,遇到了一个令人费解的问题(至少对我而言):

我有三个彼此相关的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;
}

因此,您有一个带有"currentunit"的User-Worker-Unit,该标记在用户中允许直接跳转到"current unit".每个用户可以有多个工作人员,但是一个工作人员只能分配到一个单位(一个单位可以有多个工作人员).

我想知道的是如何控制"User.worker"上的 @Fetch 批注.实际上,我希望仅在需要时才使用此方法,因为在大多数情况下,我只与"Worker"一起工作.

我经历了 http ://static.springsource.org/spring-data/data-neo4j/docs/2.0.0.RELEASE/reference/html/对我来说还不是很清楚:

  • worker是可迭代的,因为它应该是只读的(传入关系)-在文档中对此进行了明确说明,但是在示例中大多数时候使用"Set".为什么?还是没关系...
  • 如何让工作人员仅加载访问权限? (延迟加载)
  • 为什么我还需要使用@Fetch注释简单的关系(worker.unit).有没有更好的办法?我有另一个具有许多这样简单关系的实体-我真的想避免仅仅因为我要一个对象的属性而不必加载整个图.
  • 我是否缺少弹簧配置,因此可以与延迟加载一起使用?
  • 是否可以通过额外的调用来加载任何关系(未标记为@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 { 公共布尔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的方法.

确实需要自定义字段检查的基础对象设计

一种优化方法是创建一个基类,而不是实际使用布尔字段(加载了布尔值)的接口并对其进行检查(因此您不必担心手动检查)

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.

如何防止杰克逊触发延迟加载?

(作为对评论中问题的回答-请注意,由于我没有这个问题,所以我还没有尝试过.)

对于杰克逊,我建议使用自定义序列化程序(请参见 http://www.baeldung .com/jackson-custom-serialization ).这使您可以在获取值之前检查实体.您只需检查它是否已被获取,然后进行整个序列化或仅使用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配置-您需要根据您的项目调整软件包:

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

解决方案

我自己找到了所有问题的答案:

@Iterable:是的,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
  }  
}

I have a simple setup and encountered a puzzling (at least for me) problem:

I have three pojos which are related to each other:

@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". 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". 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:

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

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.

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). 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..get())")

I apply the aspect to ALL get methods on my model classes. 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)
  • 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 :)
  • ALL model classes must implement the BaseObject interface which provides:

    public interface BaseObject { public boolean isFetched(); }

This prevents double-fetching. I just check for a subclass or attribute that is mandatory (i.e. the name or something else except nodeId) to see if it is actually fetched. Neo4j will create an object but only fill the nodeId and leave everything else untouched (so everything else is NULL).

i.e.

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

Base object design that doenst require a custom field check

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. When the aspect looks at the object it uses isFetched() which - when the object is not yet retrieved will return null. Once the object is retrieved setLoaded is called and the loaded variable set to 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 i.e. 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:

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:

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

this is the /META-INF/aop.xml for this to work:

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

@load on access: per default nothing is loaded. 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:

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

这篇关于Neo4j/Spring-Data中的懒/急加载/获取的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆