我有一个简单的设置,遇到了一个令人困惑的问题(至少对我来说是这样):
我有三个与彼此相关的 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”标记在用户中,允许直接跳转到“current unit”。每个用户可以有多个工作者,但一个工作者只分配给一个单元(一个单元可以有多个工作者)。
我想知道如何在“User.worker”上控制@Fetch注释。实际上,我希望只有在需要时才加载,因为大部分时间我只与“Worker”一起使用。
我研究了http://static.springsource.org/spring-data/data-neo4j/docs/2.0.0.RELEASE/reference/html/,但对我来说并不是很清楚:
- 工作者是可迭代的,因为它应该是只读的(入站关系)——在文档中这一点已经明确说明了,但在示例中大多数情况下都使用“Set”。为什么?或者它无所谓......
- 如何使工作者仅在访问时加载?(延迟加载)
- 为什么我需要用@Fetch甚至注释简单的关系(worker.unit)。没有更好的方法吗?我还有另一个实体有许多这样简单的关系——我真的想避免仅因为要获取一个对象的属性而加载整个图。
- 我是否缺少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方法。这需要一些先决条件:
- 您必须在模型类中使用getter方法(该切面不适用于公共属性,反正您也不应该使用它们)
- 所有模型类位于同一个包中(因此您需要稍微调整代码) - 我想您可以调整过滤器
- 需要运行时组件aspectj(当您使用Tomcat时有点棘手) - 但是它可行 :)
- 所有模型类都必须实现提供以下内容的BaseObject接口:
public interface 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的解决方法。
基本对象设计,不需要自定义字段检查
一个优化方法是创建一个基类而不是一个接口,实际上使用一个布尔字段(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被调用并将loaded变量设置为true。
如何防止jackson触发延迟加载?
(作为对评论中问题的答复 - 请注意我尚未尝试过,因为我没有遇到此问题)。
使用jackson建议使用自定义序列化程序(参见例如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配置
这是我使用的示例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>