neo4j/spring-dataでの怠zy/熱心な読み込み/フェッチ
-
28-10-2019 - |
質問
私は簡単なセットアップがあり、不可解な(少なくとも私にとっては)問題に遭遇しました:
私はお互いに関連する3つのポジョーを持っています:
@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」を備えたユーザーワーカーユニットがあります。各ユーザーには複数の労働者を持つことができますが、1人のワーカーは1つのユニットにのみ割り当てられます(1つのユニットには複数の労働者がいることがあります)。
私が疑問に思っていたのは、どのように制御するかです @フェッチ 「user.worker」の注釈。ほとんどの場合、「労働者」とのみ仕事をしているので、実際にこれを必要なときにのみlaodedにしたいのです。
私は経験しました http://static.springsource.org/spring-data/data-neo4j/docs/2.0.0.release/reference/html/ そして、それは私には本当に明らかではありません:
- 労働者は、読み取り専用(着信関係)を読む必要があるため反復可能です。ドキュメントでは、これはclarlyと述べられていますが、例では「セット」はほとんどの場合使用されています。なんで?またはそれは重要ではありません...
- 労働者にアクセスのみをロードさせるにはどうすればよいですか? (怠zyloading)
- 単純な関係(worker.unit)を@fetchで注釈する必要があるのはなぜですか。より良い方法はありませんか?私はそのような単純な関係の多くを持つ別のエンティティを持っています - 私は本当に1つのオブジェクトのプロパティに必要なという理由だけでグラフ全体をロードする必要を避けたいです。
- スプリングの構成が欠けているので、怠zyなロードで動作しますか?
- 追加の電話で関係(@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。。得る())")
モデルクラスですべてのメソッドを取得するすべてのアスペクトを適用します。これには、いくつかの前提条件が必要です。
- モデルクラスでゲッターを使用する必要があります(アスペクトはパブリック属性では機能しません - とにかく使用すべきではありません)
- すべてのモデルクラスは同じパッケージにあります(したがって、コードを少し適応させる必要があります) - フィルターを適応させることができると思います
- ランタイムコンポーネントとして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;
}
}
誰かがその奇妙な回避策なしにこれを行う方法を見つけた場合は、ソリューションを追加してください:)
カスタムフィールドチェックが必要なベースオブジェクトデザイン
最適化の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が返されます。オブジェクトが取得されると、セットロードが呼び出され、ロードされた変数がtrueに設定されます。
ジャクソンが怠zyな負荷を引き起こすのを防ぐ方法は?
(コメントの質問に対する答えとして - この問題がなかったので、まだ試していないことに注意してください)。
ジャクソンと一緒にカスタムシリアナーを使用することをお勧めします(つまり、IEを参照してください 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();
}
}
スプリング構成
これは私が使用するサンプルスプリング構成です - プロジェクトにパッケージを調整する必要があります。
<?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
これが/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:はい、繰り返し可能ですreadonlyに使用できます
@load on Access:デフォルトごとにロードされていません。そして、自動怠zyなロードは利用できません(少なくとも私が収集できる限り)
残りの場合:関係が必要な場合は、@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
}
}
他のヒント
透明ではありませんが、それでも 怠zyなフェッチ.
template.fetch(person.getDirectReports());
そして、@fetchは、あなたの答えにすでに述べられているように、熱心なフェッチを行います。
私は、怠zyなロードを処理するための現在のスプリングデータの制限を回避するためのアスペクトアプローチが好きです。
@niko-私はあなたのコードサンプルを基本的なMavenプロジェクトに入れて、そのソリューションをほとんど成功せずに動作させようとしました。
https://github.com/samuel-kerrien/neo4j-aspect-auto-fetching
何らかの理由で、側面は初期化されていますが、アドバイスは実行されないようです。問題を再現するには、次のJunitテストを実行するだけです。
playground.neo4j.domain.UserTest