私は簡単なセットアップをしていて、不可解な(少なくとも私にとっては)問題に遭遇しました:
互いに関連する3つの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があります。各ユーザーは複数のワーカーを持つことができますが、1つのワーカーは1つのユニットにのみ割り当てられます(1つのユニットは複数のワーカーを持つことができます)。
私が疑問に思っていたのは、「User.worker」の@Fetchアノテーションを制御する方法です。私はほとんどの場合「Worker」でしか作業しないので、実際には必要なときにだけこれをロードしたいと思っています。
http://static.springsource.org/spring-data/data-neo4j/docs/2.0.0.RELEASE/reference/html/を確認しましたが、はっきりしていません。
- ワーカーは読み取り専用(着信関係)である必要があるため、反復可能です。ドキュメントではこれが明確に示されていますが、例では「セット」がほとんどの場合に使用されます。なんで?またはそれは重要ではありません...
- ワーカーにアクセス時にのみロードさせるにはどうすればよいですか?(遅延読み込み)
- 単純な関係(worker.unit)でさえ@Fetchでアノテーションを付ける必要があるのはなぜですか。もっと良い方法はありませんか?このような単純な関係が多数ある別のエンティティがあります。1つのオブジェクトのプロパティが必要であるという理由だけで、グラフ全体をロードする必要はありません。
- 遅延読み込みで動作するように、スプリング構成が欠落していますか?
- 追加の呼び出しを介して関係(@Fetchとしてマークされていない)をロードする方法はありますか?
私の見方からすると、このコンストラクトは、ほとんどの場合ユーザーを気にしない場合でも、ワーカーが必要になるとすぐにデータベース全体をロードします。
私が見つけた唯一の回避策は、リポジトリを使用し、必要に応じてエンティティを手動でロードすることです。
- - - - アップデート - - - -
私はかなり前からneo4jを使用していて、フェッチを常に呼び出す必要がない(したがって、グラフ全体をロードしない)上記の問題の解決策を見つけました。唯一の欠点:それは実行時の側面です:
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インターフェイスを実装する必要があります。
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のないものが大好きです。
カスタムフィールドチェックを必要としないベースオブジェクトデザイン
最適化の1つは、ブールフィールド(ブールロード)を実際に使用してそれをチェックするインターフェイスの代わりに基本クラスを作成することです(手動チェックについて心配する必要はありません)。
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を参照)。これにより、値を取得する前にエンティティを確認できます。すでにフェッチされているかどうかをチェックし、シリアル化全体を続行するか、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>