FAQ
SDN は Neo4j-OGM とどのように関係しますか ?
Neo4j-OGM (英語) はオブジェクトグラフマッピングライブラリであり、主に Spring Data Neo4j の以前のバージョンで、ノードと関連をドメインオブジェクトにマッピングするという重労働を行うバックエンドとして使用されます。現在の SDN は Neo4j-OGM を必要とせず、サポートしていません。SDN は、クラスのスキャンとメタモデルの構築にのみ Spring Data のマッピングコンテキストを使用します。
これにより、SDN が Spring エコシステムに固定されますが、CPU とメモリの使用量、特に Spring のマッピングコンテキストのすべての機能に関するフットプリントが小さいなど、いくつかの利点があります。
SDN+OGM ではなく SDN を使用する必要がある理由
SDN には、SDN+OGM にはないいくつかの機能があります。
リアクティブトランザクションを含む Springs リアクティブストーリーの完全サポート
例示による問い合わせの完全サポート
完全に不変のエンティティを完全にサポート
空間クエリを含む派生ファインダーメソッドのすべての修飾子とバリエーションのサポート
SDN は組み込み Neo4j をサポートしていますか ?
組み込み Neo4j には複数の側面があります。
SDN は埋め込みインスタンスと直接対話しますか ?
いいえ。組み込みデータベースは通常、org.neo4j.graphdb.GraphDatabaseService
のインスタンスによって表され、すぐに使用できるボルトコネクターはありません。
ただし、SDN は Neo4j のテストハーネスと非常によく連携できます。テストハーネスは、実際のデータベースのドロップイン置換として特に意図されています。Neo4j 3.5, 4.x, 5.x テストハーネスのサポートは、ドライバーの Spring Boot スターター [GitHub] (英語) を介して実装されます。対応するモジュール org.neo4j.driver:neo4j-java-driver-test-harness-spring-boot-autoconfigure
を参照してください。
どの Neo4j Java ドライバーをどのように使用できますか ?
SDN は Neo4j Java ドライバーに依存しています。各 SDN リリースでは、リリース時に利用可能な最新の Neo4j と互換性のある Neo4j Java ドライバーバージョンが使用されます。Neo4j Java Driver のパッチバージョンは通常、ドロップイン置き換えですが、SDN では、必要に応じてメソッドまたはインターフェースの変更の有無をチェックするため、マイナーバージョンであっても互換性があることを確認します。
任意の 4.x Neo4j Java ドライバーを任意の SDN 6.x バージョンで使用でき、任意の 5.x Neo4j ドライバーを任意の SDN 7.x バージョンで使用できます。
Spring Boot 付
最近では、Spring boot デプロイが Spring Data ベースのアプリケーションの デプロイである可能性が最も高くなります。Spring Boot 依存関係管理を使用して、次のようにドライバーのバージョンを変更してください。
<properties>
<neo4j-java-driver.version>5.4.0</neo4j-java-driver.version>
</properties>
または
neo4j-java-driver.version = 5.4.0
Spring Boot なし
Spring Boot を使用しない場合は、依存関係を手動で宣言するだけになります。Maven の場合は、次のように <dependencyManagement />
セクションを使用することをお勧めします。
<dependencyManagement> <dependency> <groupId>org.neo4j.driver</groupId> <artifactId>neo4j-java-driver</artifactId> <version>5.4.0</version> </dependency> </dependencyManagement>
Neo4j 4 は複数のデータベースをサポートしています - 使用するにはどうすればよいですか ?
データベース名を静的に構成することも、独自のデータベース名プロバイダーを実行することもできます。SDN はデータベースを作成しないことに注意してください。これは、移行ツール [GitHub] (英語) を使用して行うことも、もちろん事前に簡単なスクリプトを使用して行うこともできます。
静的に構成された
Spring Boot 構成で使用するデータベース名を次のように構成します (もちろん、同じプロパティが YML または環境ベースの構成に適用され、Spring Boot の規則が適用されます)。
spring.data.neo4j.database = yourDatabase
この構成を適用すると、SDN リポジトリのすべてのインスタンス (リアクティブと命令の両方) および ReactiveNeo4jTemplate
および Neo4jTemplate
によって生成されたすべてのクエリが、データベース yourDatabase
に対して実行されます。
動的に構成
Spring アプリケーションの型に応じて、Bean に型 Neo4jDatabaseNameProvider
または ReactiveDatabaseSelectionProvider
を指定します。
Bean は、たとえば Spring のセキュリティコンテキストを使用してテナントを取得できます。以下は、Spring Security で保護された命令型アプリケーションの実例です。
import org.neo4j.springframework.data.core.DatabaseSelection;
import org.neo4j.springframework.data.core.DatabaseSelectionProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
@Configuration
public class Neo4jConfig {
@Bean
DatabaseSelectionProvider databaseSelectionProvider() {
return () -> Optional.ofNullable(SecurityContextHolder.getContext()).map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated).map(Authentication::getPrincipal).map(User.class::cast)
.map(User::getUsername).map(DatabaseSelection::byName).orElseGet(DatabaseSelection::undecided);
}
}
あるデータベースから取得したエンティティを別のデータベースと混同しないように注意してください。データベース名は新しいトランザクションごとにリクエストされるため、呼び出しの間にデータベース名を変更すると、エンティティの数が予想よりも少なくなったり、多くなったりする可能性があります。さらに悪いことに、必然的に間違ったエンティティを間違ったデータベースに保存する可能性があります。 |
Spring Boot Neo4j ヘルスインジケーターはデフォルトのデータベースをターゲットにしていますが、これを変更するにはどうすればよいですか ?
Spring Boot には、命令型とリアクティブ型の両方の Neo4j ヘルスインジケーターが付属しています。どちらのバリアントも、アプリケーションコンテキスト内で org.neo4j.driver.Driver
の複数の Bean を検出でき、各インスタンスの全体的な健全性に貢献します。ただし、Neo4j ドライバーはサーバーに接続しますが、サーバー内の特定のデータベースには接続しません。Spring Boot は Spring Data Neo4j なしでドライバーを構成できます。また、どのデータベースが使用されるかという情報は Spring Data Neo4j に関連付けられているため、この情報は組み込みのヘルスインジケーターでは利用できません。
これは、多くの デプロイシナリオでは問題にならない可能性が高くなります。ただし、構成されたデータベースユーザーに少なくともデフォルトデータベースへのアクセス権がない場合、ヘルスチェックは失敗します。
これは、データベースの選択を認識しているカスタム Neo4j ヘルスコントリビューターによって軽減できます。
命令型の変形
import java.util.Optional;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Result;
import org.neo4j.driver.SessionConfig;
import org.neo4j.driver.summary.DatabaseInfo;
import org.neo4j.driver.summary.ResultSummary;
import org.neo4j.driver.summary.ServerInfo;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.data.neo4j.core.DatabaseSelection;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
import org.springframework.util.StringUtils;
public class DatabaseSelectionAwareNeo4jHealthIndicator extends AbstractHealthIndicator {
private final Driver driver;
private final DatabaseSelectionProvider databaseSelectionProvider;
public DatabaseSelectionAwareNeo4jHealthIndicator(
Driver driver, DatabaseSelectionProvider databaseSelectionProvider
) {
this.driver = driver;
this.databaseSelectionProvider = databaseSelectionProvider;
}
@Override
protected void doHealthCheck(Health.Builder builder) {
try {
SessionConfig sessionConfig = Optional
.ofNullable(databaseSelectionProvider.getDatabaseSelection())
.filter(databaseSelection -> databaseSelection != DatabaseSelection.undecided())
.map(DatabaseSelection::getValue)
.map(v -> SessionConfig.builder().withDatabase(v).build())
.orElseGet(SessionConfig::defaultConfig);
class Tuple {
String edition;
ResultSummary resultSummary;
Tuple(String edition, ResultSummary resultSummary) {
this.edition = edition;
this.resultSummary = resultSummary;
}
}
String query =
"CALL dbms.components() YIELD name, edition WHERE name = 'Neo4j Kernel' RETURN edition";
Tuple health = driver.session(sessionConfig)
.writeTransaction(tx -> {
Result result = tx.run(query);
String edition = result.single().get("edition").asString();
return new Tuple(edition, result.consume());
});
addHealthDetails(builder, health.edition, health.resultSummary);
} catch (Exception ex) {
builder.down().withException(ex);
}
}
static void addHealthDetails(Health.Builder builder, String edition, ResultSummary resultSummary) {
ServerInfo serverInfo = resultSummary.server();
builder.up()
.withDetail(
"server", serverInfo.version() + "@" + serverInfo.address())
.withDetail("edition", edition);
DatabaseInfo databaseInfo = resultSummary.database();
if (StringUtils.hasText(databaseInfo.name())) {
builder.withDetail("database", databaseInfo.name());
}
}
}
これは、利用可能なデータベースの選択を使用して、Boot が接続が正常かどうかを確認するために実行するのと同じクエリを実行します。これを適用するには、次の構成を使用します。
import java.util.Map;
import org.neo4j.driver.Driver;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
@Configuration(proxyBeanMethods = false)
public class Neo4jHealthConfig {
@Bean (1)
DatabaseSelectionAwareNeo4jHealthIndicator databaseSelectionAwareNeo4jHealthIndicator(
Driver driver, DatabaseSelectionProvider databaseSelectionProvider
) {
return new DatabaseSelectionAwareNeo4jHealthIndicator(driver, databaseSelectionProvider);
}
@Bean (2)
HealthContributor neo4jHealthIndicator(
Map<String, DatabaseSelectionAwareNeo4jHealthIndicator> customNeo4jHealthIndicators) {
return CompositeHealthContributor.fromMap(customNeo4jHealthIndicators);
}
@Bean (3)
InitializingBean healthContributorRegistryCleaner(
HealthContributorRegistry healthContributorRegistry,
Map<String, DatabaseSelectionAwareNeo4jHealthIndicator> customNeo4jHealthIndicators
) {
return () -> customNeo4jHealthIndicators.keySet()
.stream()
.map(HealthContributorNameFactory.INSTANCE)
.forEach(healthContributorRegistry::unregisterContributor);
}
}
1 | 複数のドライバーとデータベース選択プロバイダーがある場合は、組み合わせごとに 1 つのインジケーターを作成する必要があります。 |
2 | これにより、これらのインジケーターがすべて Neo4j にグループ化され、デフォルトの Neo4j ヘルスインジケーターが置き換えられます。 |
3 | これにより、個々の投稿者が正常性エンドポイントに直接表示されなくなります。 |
リアクティブバリアント
リアクティブバリアントは基本的に同じで、リアクティブ型と対応するリアクティブインフラストラクチャクラスを使用します。
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import org.neo4j.driver.Driver;
import org.neo4j.driver.SessionConfig;
import org.neo4j.driver.reactivestreams.RxResult;
import org.neo4j.driver.reactivestreams.RxSession;
import org.neo4j.driver.summary.DatabaseInfo;
import org.neo4j.driver.summary.ResultSummary;
import org.neo4j.driver.summary.ServerInfo;
import org.reactivestreams.Publisher;
import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.data.neo4j.core.DatabaseSelection;
import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider;
import org.springframework.util.StringUtils;
public final class DatabaseSelectionAwareNeo4jReactiveHealthIndicator
extends AbstractReactiveHealthIndicator {
private final Driver driver;
private final ReactiveDatabaseSelectionProvider databaseSelectionProvider;
public DatabaseSelectionAwareNeo4jReactiveHealthIndicator(
Driver driver,
ReactiveDatabaseSelectionProvider databaseSelectionProvider
) {
this.driver = driver;
this.databaseSelectionProvider = databaseSelectionProvider;
}
@Override
protected Mono<Health> doHealthCheck(Health.Builder builder) {
String query =
"CALL dbms.components() YIELD name, edition WHERE name = 'Neo4j Kernel' RETURN edition";
return databaseSelectionProvider.getDatabaseSelection()
.map(databaseSelection -> databaseSelection == DatabaseSelection.undecided() ?
SessionConfig.defaultConfig() :
SessionConfig.builder().withDatabase(databaseSelection.getValue()).build()
)
.flatMap(sessionConfig ->
Mono.usingWhen(
Mono.fromSupplier(() -> driver.rxSession(sessionConfig)),
s -> {
Publisher<Tuple2<String, ResultSummary>> f = s.readTransaction(tx -> {
RxResult result = tx.run(query);
return Mono.from(result.records())
.map((record) -> record.get("edition").asString())
.zipWhen((edition) -> Mono.from(result.consume()));
});
return Mono.fromDirect(f);
},
RxSession::close
)
).map((result) -> {
addHealthDetails(builder, result.getT1(), result.getT2());
return builder.build();
});
}
static void addHealthDetails(Health.Builder builder, String edition, ResultSummary resultSummary) {
ServerInfo serverInfo = resultSummary.server();
builder.up()
.withDetail(
"server", serverInfo.version() + "@" + serverInfo.address())
.withDetail("edition", edition);
DatabaseInfo databaseInfo = resultSummary.database();
if (StringUtils.hasText(databaseInfo.name())) {
builder.withDetail("database", databaseInfo.name());
}
}
}
そしてもちろん、設定のリアクティブ版です。Spring Boot は非リアクティブアクチュエーターエンドポイントでも使用できるように既存のリアクティブインジケーターをラップするため、2 つの異なるレジストリクリーナーが必要です。
import java.util.Map;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
import org.springframework.boot.actuate.health.HealthContributorNameFactory;
import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class Neo4jHealthConfig {
@Bean
ReactiveHealthContributor neo4jHealthIndicator(
Map<String, DatabaseSelectionAwareNeo4jReactiveHealthIndicator> customNeo4jHealthIndicators) {
return CompositeReactiveHealthContributor.fromMap(customNeo4jHealthIndicators);
}
@Bean
InitializingBean healthContributorRegistryCleaner(HealthContributorRegistry healthContributorRegistry,
Map<String, DatabaseSelectionAwareNeo4jReactiveHealthIndicator> customNeo4jHealthIndicators) {
return () -> customNeo4jHealthIndicators.keySet()
.stream()
.map(HealthContributorNameFactory.INSTANCE)
.forEach(healthContributorRegistry::unregisterContributor);
}
@Bean
InitializingBean reactiveHealthContributorRegistryCleaner(
ReactiveHealthContributorRegistry healthContributorRegistry,
Map<String, DatabaseSelectionAwareNeo4jReactiveHealthIndicator> customNeo4jHealthIndicators) {
return () -> customNeo4jHealthIndicators.keySet()
.stream()
.map(HealthContributorNameFactory.INSTANCE)
.forEach(healthContributorRegistry::unregisterContributor);
}
}
Neo4j 4.4+ はさまざまなユーザーの偽装をサポートしています - どのように使用できますか ?
ユーザーの偽装は、物理的に接続されている (または技術的な) 1 人のユーザーが多数のテナントになりすますことができる大規模なマルチテナント設定で特に興味深いものです。設定によっては、必要な物理ドライバーインスタンスの数が大幅に減少します。
この機能には、サーバー側に Neo4j Enterprise 4.4+ が必要で、クライアント側に 4.4+ ドライバー (org.neo4j.driver:neo4j-java-driver:4.4.0
以降) が必要です。
命令型バージョンとリアクティブバージョンの両方で、UserSelectionProvider
と ReactiveUserSelectionProvider
をそれぞれ提供する必要があります。同じインスタンスを、それぞれのリアクティブバリアントである Neo4Client
および Neo4jTransactionManager
に渡す必要があります。
Boot レスの命令およびリアクティブ構成では、問題の型の Bean を指定するだけです。
import org.springframework.data.neo4j.core.UserSelection;
import org.springframework.data.neo4j.core.UserSelectionProvider;
public class CustomConfig {
@Bean
public UserSelectionProvider getUserSelectionProvider() {
return () -> UserSelection.impersonate("someUser");
}
}
典型的な Spring Boot シナリオでは、Boot はその機能のない SDN バージョンもサポートしているため、この機能にはもう少し作業が必要です。ユーザー選択プロバイダー Bean の Bean を考慮すると、クライアントとトランザクションマネージャーを完全にカスタマイズする必要があります。
import org.neo4j.driver.Driver;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.data.neo4j.core.UserSelectionProvider;
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
public class CustomConfig {
@Bean
public Neo4jClient neo4jClient(
Driver driver,
DatabaseSelectionProvider databaseSelectionProvider,
UserSelectionProvider userSelectionProvider
) {
return Neo4jClient.with(driver)
.withDatabaseSelectionProvider(databaseSelectionProvider)
.withUserSelectionProvider(userSelectionProvider)
.build();
}
@Bean
public PlatformTransactionManager transactionManager(
Driver driver,
DatabaseSelectionProvider databaseSelectionProvider,
UserSelectionProvider userSelectionProvider
) {
return Neo4jTransactionManager
.with(driver)
.withDatabaseSelectionProvider(databaseSelectionProvider)
.withUserSelectionProvider(userSelectionProvider)
.build();
}
}
Spring Data Neo4j からの Neo4j クラスターインスタンスの使用
次の質問は、Neo4j AuraDB と Neo4j オンプレミスクラスターインスタンスに当てはまります。
トランザクションが Neo4j Causal Cluster でシームレスに動作するには、特定の構成が必要ですか ?
いいえ、そうではありません。SDN は、ユーザー側での構成を必要とせずに、Neo4j Causal Cluster ブックマークを内部的に使用します。同じスレッド内のトランザクション、または相互に続く同じリアクティブストリーム内のトランザクションは、予想どおり、以前に変更された値を読み取ることができます。
Neo4j クラスターに読み取り専用トランザクションを使用することは重要ですか ?
はい、そうです。Neo4j クラスターアーキテクチャは因果的クラスタリングアーキテクチャであり、プライマリサーバーとセカンダリサーバーを区別します。プライマリサーバーは、単一インスタンスまたはコアインスタンスのいずれかです。どちらも読み取りおよび書き込み操作に応答できます。書き込み操作は、コアインスタンスからクラスター内の読み取りレプリカ、より一般的にはフォロワーに伝播されます。これらのフォロワーはセカンダリサーバーです。セカンダリサーバーは書き込み操作に応答しません。
標準的な デプロイシナリオでは、クラスター内にいくつかのコアインスタンスと多数のリードレプリカが存在します。リーダーが圧倒されることがなく、クエリが読み取りレプリカにできるだけ伝播されるようにクラスターを拡張するには、操作またはクエリを読み取り専用としてマークすることが重要です。
Spring Data Neo4j も基礎となる Java ドライバーも Cypher 解析を行わず、両方のビルドブロックはデフォルトで書き込み操作を想定します。この決定は、すぐに使えるすべての操作をサポートするために行われました。スタック内の何かがデフォルトで読み取り専用であると想定されている場合、スタックは読み取りレプリカに書き込みクエリを送信して、その実行に失敗する可能性があります。
すべての findById 、findAllById 、findAll および定義済みの存在メソッドは、デフォルトで読み取り専用としてマークされます。 |
いくつかのオプションを以下に説明します。
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true)
interface PersonRepository extends Neo4jRepository<Person, Long> {
}
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.transaction.annotation.Transactional;
interface PersonRepository extends Neo4jRepository<Person, Long> {
@Transactional(readOnly = true)
Person findOneByName(String name); (1)
@Transactional(readOnly = true)
@Query("""
CALL apoc.search.nodeAll('{Person: "name",Movie: ["title","tagline"]}','contains','her')
YIELD node AS n RETURN n""")
Person findByCustomQuery(); (2)
}
1 | これがデフォルトで読み取り専用にならないのはなぜですか ? 上記の派生ファインダー (実際には読み取り専用であることがわかっています) では機能しますが、ユーザーがカスタム @Query を追加し、それを MERGE 構造 (もちろん書き込み操作) 経由で実装するケースをよく見かけます。 |
2 | カスタムプロシージャはあらゆる種類のことを行うことができますが、現時点では読み取り専用か書き込みかをここで確認する方法はありません。 |
import java.util.Optional;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.transaction.annotation.Transactional;
interface PersonRepository extends Neo4jRepository<Person, Long> {
}
interface MovieRepository extends Neo4jRepository<Movie, Long> {
List<Movie> findByLikedByPersonName(String name);
}
public class PersonService {
private final PersonRepository personRepository;
private final MovieRepository movieRepository;
public PersonService(PersonRepository personRepository,
MovieRepository movieRepository) {
this.personRepository = personRepository;
this.movieRepository = movieRepository;
}
@Transactional(readOnly = true)
public Optional<PersonDetails> getPerson(Long id) { (1)
return this.repository.findById(id)
.map(person -> {
var movies = this.movieRepository
.findByLikedByPersonName(person.getName());
return new PersonDetails(person, movies);
});
}
}
1 | ここでは、複数のリポジトリへの複数の呼び出しが 1 つの読み取り専用トランザクションにラップされています。 |
TransactionTemplate
を使用する import java.util.Collection;
import org.neo4j.driver.types.Node;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
public class PersonService {
private final TransactionTemplate readOnlyTx;
private final Neo4jClient neo4jClient;
public PersonService(PlatformTransactionManager transactionManager, Neo4jClient neo4jClient) {
this.readOnlyTx = new TransactionTemplate(transactionManager, (1)
new TransactionDefinition() {
@Override public boolean isReadOnly() {
return true;
}
}
);
this.neo4jClient = neo4jClient;
}
void internalOperation() { (2)
Collection<Node> nodes = this.readOnlyTx.execute(state -> {
return neo4jClient.query("MATCH (n) RETURN n").fetchAs(Node.class) (3)
.mappedBy((types, record) -> record.get(0).asNode())
.all();
});
}
}
1 | 必要な特性を備えた TransactionTemplate のインスタンスを作成します。もちろん、これはグローバル Bean にすることもできます。 |
2 | トランザクションテンプレートを使用する理由 1 つ目: 宣言型トランザクションは、アスペクトとプロキシで実装される性質のため、パッケージのプライベートメソッドやプライベートメソッドでは機能せず、内部メソッド呼び出し ( internalOperation を呼び出すこのサービスの別のメソッドを想像してください) でも機能しません。 |
3 | Neo4jClient は、SDN によって提供される固定ユーティリティです。アノテーションを付けることはできませんが、Spring と統合されます。自動マッピングやトランザクションを使用せずに、純粋なドライバーを使用して実行するすべての操作が可能になります。また、宣言的なトランザクションにも準拠します。 |
最新のブックマークを取得したり、トランザクションマネージャーをシードしたりできますか ?
ブックマーク管理で簡単に説明したように、ブックマークに関しては何も設定する必要はありません。ただし、SDN トランザクションシステムがデータベースから受信した最新のブックマークを取得すると便利な場合があります。これを行うには、BookmarkCapture
のような @Bean
を追加できます。
import java.util.Set;
import org.neo4j.driver.Bookmark;
import org.springframework.context.ApplicationListener;
public final class BookmarkCapture
implements ApplicationListener<Neo4jBookmarksUpdatedEvent> {
@Override
public void onApplicationEvent(Neo4jBookmarksUpdatedEvent event) {
// We make sure that this event is called only once,
// the thread safe application of those bookmarks is up to your system.
Set<Bookmark> latestBookmarks = event.getBookmarks();
}
}
トランザクションシステムをシードするには、次のようなカスタマイズされたトランザクションマネージャーが必要です。
import java.util.Set;
import java.util.function.Supplier;
import org.neo4j.driver.Bookmark;
import org.neo4j.driver.Driver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
public class BookmarkSeedingConfig {
@Bean
public PlatformTransactionManager transactionManager(
Driver driver, DatabaseSelectionProvider databaseNameProvider) { (1)
Supplier<Set<Bookmark>> bookmarkSupplier = () -> { (2)
Bookmark a = null;
Bookmark b = null;
return Set.of(a, b);
};
Neo4jBookmarkManager bookmarkManager =
Neo4jBookmarkManager.create(bookmarkSupplier); (3)
return new Neo4jTransactionManager(
driver, databaseNameProvider, bookmarkManager); (4)
}
}
1 | Spring に注入させます |
2 | このサプライヤーには、システムに取り込みたい最新のブックマークを保持するものであれば何でもかまいません。 |
3 | それを使用してブックマークマネージャーを作成します |
4 | カスタマイズされたトランザクションマネージャーに渡します |
アプリケーションがこのデータにアクセスしたり、このデータを提供したりする必要がない限り、上記のことを行う必要はありません。疑わしい場合は、どちらも行わないでください。 |
ブックマーク管理を無効にできますか ?
ブックマーク管理を効果的に無効にする Noop ブックマークマネージャーを提供します。
このブックマークマネージャーは自己責任で使用してください。すべてのブックマークを削除し、ブックマークをまったく提供しないことにより、事実上ブックマーク管理が無効になります。クラスターでは、古い読み取りが発生するリスクが高くなります。単一のインスタンスでは、ほとんどの場合、何の違いも生じません。 |
+ クラスターでは、古いデータを上書きする危険がなく、古い読み取りを許容できる場合に限り、これは実用的なアプローチとなります。
次の構成では、関連するクラスから取得されるブックマークマネージャーの "noop" バリアントが作成されます。
import org.neo4j.driver.Driver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
@Configuration
public class BookmarksDisabledConfig {
@Bean
public Neo4jBookmarkManager neo4jBookmarkManager() {
return Neo4jBookmarkManager.noop();
}
}
Neo4jTransactionManager/Neo4jClient
と ReactiveNeo4jTransactionManager/ReactiveNeo4jClient
のペアを個別に構成することもできますが、特定のデータベース選択ニーズに合わせてすでに構成している場合にのみこれを行うことをお勧めします。
Neo4j 固有のアノテーションを使用する必要がありますか ?
いいえ。次の同等の Spring Data アノテーションを自由に使用できます。
SDN 固有のアノテーション | Spring Data 共通アノテーション | 目的 | 相違 |
---|---|---|---|
|
| アノテーション付きの属性を一意の ID としてマークします。 | 特定のアノテーションには追加機能はありません。 |
|
| クラスを永続エンティティとしてマークします。 |
|
How do I use assigned ids?
Just use @Id
without @GeneratedValue
and fill your id attribute via a constructor parameter or a setter or wither.
See this blog post (英語) for some general remarks about finding good ids.
How do I use externally generated ids?
We provide the interface org.springframework.data.neo4j.core.schema.IdGenerator
.
Implement it in any way you want and configure your implementation like this:
@Node
public class ThingWithGeneratedId {
@Id @GeneratedValue(TestSequenceGenerator.class)
private String theId;
}
If you pass in the name of a class to @GeneratedValue
, this class must have a no-args default constructor.
You can however use a string as well:
@Node
public class ThingWithIdGeneratedByBean {
@Id @GeneratedValue(generatorRef = "idGeneratingBean")
private String theId;
}
With that, idGeneratingBean
refers to a bean in the Spring context.
This might be useful for sequence generating.
Setters are not required on non-final fields for the id. |
Do I have to create repositories for each domain class?
No.
Have a look at the SDN building blocks and find the Neo4jTemplate
or the ReactiveNeo4jTemplate
.
Those templates know your domain and provide all necessary basic CRUD methods for retrieving, writing and counting entities.
This is our canonical movie example with the imperative template:
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collections;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.core.Neo4jTemplate;
import org.springframework.data.neo4j.documentation.domain.MovieEntity;
import org.springframework.data.neo4j.documentation.domain.PersonEntity;
import org.springframework.data.neo4j.documentation.domain.Roles;
@DataNeo4jTest
public class TemplateExampleTest {
@Test
void shouldSaveAndReadEntities(@Autowired Neo4jTemplate neo4jTemplate) {
MovieEntity movie = new MovieEntity("The Love Bug",
"A movie that follows the adventures of Herbie, Herbie's driver, "
+ "Jim Douglas (Dean Jones), and Jim's love interest, " + "Carole Bennett (Michele Lee)");
Roles roles1 = new Roles(new PersonEntity(1931, "Dean Jones"), Collections.singletonList("Didi"));
Roles roles2 = new Roles(new PersonEntity(1942, "Michele Lee"), Collections.singletonList("Michi"));
movie.getActorsAndRoles().add(roles1);
movie.getActorsAndRoles().add(roles2);
MovieEntity result = neo4jTemplate.save(movie);
assertThat(result.getActorsAndRoles()).allSatisfy(relationship -> assertThat(relationship.getId()).isNotNull());
Optional<PersonEntity> person = neo4jTemplate.findById("Dean Jones", PersonEntity.class);
assertThat(person).map(PersonEntity::getBorn).hasValue(1931);
assertThat(neo4jTemplate.count(PersonEntity.class)).isEqualTo(2L);
}
}
And here is the reactive version, omitting the setup for brevity:
import reactor.test.StepVerifier;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate;
import org.springframework.data.neo4j.documentation.domain.MovieEntity;
import org.springframework.data.neo4j.documentation.domain.PersonEntity;
import org.springframework.data.neo4j.documentation.domain.Roles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@Testcontainers
@DataNeo4jTest
class ReactiveTemplateExampleTest {
@Container private static Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>("neo4j:5");
@DynamicPropertySource
static void neo4jProperties(DynamicPropertyRegistry registry) {
registry.add("org.neo4j.driver.uri", neo4jContainer::getBoltUrl);
registry.add("org.neo4j.driver.authentication.username", () -> "neo4j");
registry.add("org.neo4j.driver.authentication.password", neo4jContainer::getAdminPassword);
}
@Test
void shouldSaveAndReadEntities(@Autowired ReactiveNeo4jTemplate neo4jTemplate) {
MovieEntity movie = new MovieEntity("The Love Bug",
"A movie that follows the adventures of Herbie, Herbie's driver, Jim Douglas (Dean Jones), and Jim's love interest, Carole Bennett (Michele Lee)");
Roles role1 = new Roles(new PersonEntity(1931, "Dean Jones"), Collections.singletonList("Didi"));
Roles role2 = new Roles(new PersonEntity(1942, "Michele Lee"), Collections.singletonList("Michi"));
movie.getActorsAndRoles().add(role1);
movie.getActorsAndRoles().add(role2);
StepVerifier.create(neo4jTemplate.save(movie)).expectNextCount(1L).verifyComplete();
StepVerifier.create(neo4jTemplate.findById("Dean Jones", PersonEntity.class).map(PersonEntity::getBorn))
.expectNext(1931).verifyComplete();
StepVerifier.create(neo4jTemplate.count(PersonEntity.class)).expectNext(2L).verifyComplete();
}
}
Please note that both examples use @DataNeo4jTest
from Spring Boot.
How do I use custom queries with repository methods returning Page<T>
or Slice<T>
?
While you don’t have to provide anything else apart a Pageable
as a parameter on derived finder methods
that return a Page<T>
or a Slice<T>
, you must prepare your custom query to handle the pageable.
Pages and Slices gives you an overview about what’s needed.
import org.springframework.data.domain.Pageable;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
public interface MyPersonRepository extends Neo4jRepository<Person, Long> {
Page<Person> findByName(String name, Pageable pageable); (1)
@Query(""
+ "MATCH (n:Person) WHERE n.name = $name RETURN n "
+ "ORDER BY n.name ASC SKIP $skip LIMIT $limit"
)
Slice<Person> findSliceByName(String name, Pageable pageable); (2)
@Query(
value = ""
+ "MATCH (n:Person) WHERE n.name = $name RETURN n "
+ "ORDER BY n.name ASC SKIP $skip LIMIT $limit",
countQuery = ""
+ "MATCH (n:Person) WHERE n.name = $name RETURN count(n)"
)
Page<Person> findPageByName(String name, Pageable pageable); (3)
}
1 | A derived finder method that creates a query for you.
It handles the Pageable for you.
You should use a sorted pageable. |
2 | This method uses @Query to define a custom query. It returns a Slice<Person> .
A slice does not know about the total number of pages, so the custom query
doesn’t need a dedicated count query. SDN will notify you that it estimates the next slice.
The Cypher template must spot both $skip and $limit Cypher parameter.
If you omit them, SDN will issue a warning. The will probably not match your expectations.
Also, the Pageable should be unsorted and you should provide a stable order.
We won’t use the sorting information from the pageable. |
3 | This method returns a page. A page knows about the exact number of total pages. Therefore, you must specify an additional count query. All other restrictions from the second method apply. |
Can I map named paths?
A series of connected nodes and relationships is called a "path" in Neo4j. Cypher allows paths to be named using an identifier, as exemplified by:
p = (a)-[*3..5]->(b)
or as in the infamous Movie graph, that includes the following path (in that case, one of the shortest path between two actors):
MATCH p=shortestPath((bacon:Person {name:"Kevin Bacon"})-[*]-(meg:Person {name:"Meg Ryan"}))
RETURN p
Which looks like this:
We find 3 nodes labeled Vertex
and 2 nodes labeled Movie
. Both can be mapped with a custom query.
Assume there’s a node entity for both Vertex
and Movie
as well as Actor
taking care of the relationship:
@Node
public final class Person {
@Id @GeneratedValue
private final Long id;
private final String name;
private Integer born;
@Relationship("REVIEWED")
private List<Movie> reviewed = new ArrayList<>();
}
@RelationshipProperties
public final class Actor {
@RelationshipId
private final Long id;
@TargetNode
private final Person person;
private final List<String> roles;
}
@Node
public final class Movie {
@Id
private final String title;
@Property("tagline")
private final String description;
@Relationship(value = "ACTED_IN", direction = Direction.INCOMING)
private final List<Actor> actors;
}
When using a query as shown in The "Bacon" distance for a domain class of type Vertex
like this
interface PeopleRepository extends Neo4jRepository<Person, Long> {
@Query(""
+ "MATCH p=shortestPath((bacon:Person {name: $person1})-[*]-(meg:Person {name: $person2}))\n"
+ "RETURN p"
)
List<Person> findAllOnShortestPathBetween(@Param("person1") String person1, @Param("person2") String person2);
}
it will retrieve all people from the path and map them.
If there are relationship types on the path like REVIEWED
that are also present on the domain, these
will be filled accordingly from the path.
Take special care when you use nodes hydrated from a path based query to save data. If not all relationships are hydrated, data will be lost. |
The other way round works as well. The same query can be used with the Movie
entity.
It then will only populate movies.
The following listing shows how todo this as well as how the query can be enriched with additional data
not found on the path. That data is used to correctly populate the missing relationships (in that case, all the actors)
interface MovieRepository extends Neo4jRepository<Movie, String> {
@Query(""
+ "MATCH p=shortestPath(\n"
+ "(bacon:Person {name: $person1})-[*]-(meg:Person {name: $person2}))\n"
+ "WITH p, [n IN nodes(p) WHERE n:Movie] AS x\n"
+ "UNWIND x AS m\n"
+ "MATCH (m) <-[r:DIRECTED]-(d:Person)\n"
+ "RETURN p, collect(r), collect(d)"
)
List<Movie> findAllOnShortestPathBetween(@Param("person1") String person1, @Param("person2") String person2);
}
The query returns the path plus all relationships and related nodes collected so that the movie entities are fully hydrated.
The path mapping works for single paths as well for multiple records of paths (which are returned by the allShortestPath
function.)
Named paths can be used efficiently to populate and return more than just a root node, see appendix/custom-queries.adoc#custom-query.paths. |
Is @Query
the only way to use custom queries?
No, @Query
is not the only way to run custom queries.
The annotation is comfortable in situations in which your custom query fills your domain completely.
Please remember that SDN assumes your mapped domain model to be the truth.
That means if you use a custom query via @Query
that only fills a model partially, you are in danger of using the same
object to write the data back which will eventually erase or overwrite data you didn’t consider in your query.
So, please use repositories and declarative methods with @Query
in all cases where the result is shaped like your domain
model or you are sure you don’t use a partially mapped model for write commands.
What are the alternatives?
-
Projections might be already enough to shape your view on the graph: They can be used to define the depth of fetching properties and related entities in an explicit way: By modelling them.
-
If your goal is to make only the conditions of your queries dynamic, then have a look at the
QuerydslPredicateExecutor
but especially our own variant of it, theCypherdslConditionExecutor
. Both mixins allow adding conditions to the full queries we create for you. Thus, you will have the domain fully populated together with custom conditions. Of course, your conditions must work with what we generate. Find the names of the root node, the related nodes and more here. -
Use the Cypher-DSL (英語) via the
CypherdslStatementExecutor
or theReactiveCypherdslStatementExecutor
. The Cypher-DSL is predestined to create dynamic queries. In the end, it’s what SDN uses under the hood anyway. The corresponding mixins work both with the domain type of repository itself and with projections (something that the mixins for adding conditions don’t).
If you think that you can solve your problem with a partially dynamic query or a full dynamic query together with a projection, please jump back now to the chapter about Spring Data Neo4j Mixins.
Otherwise, please read up on two things: custom repository fragments the levels of abstractions we offer in SDN.
Why speaking about custom repository fragments now?
-
You might have more complex situation in which more than one dynamic query is required, but the queries still belong conceptually in a repository and not in the service layer
-
Your custom queries return a graph shaped result that fits not quite to your domain model and therefore the custom query should be accompanied by a custom mapping as well
-
You have the need for interacting with the driver, i.e. for bulk loads that should not go through object mapping.
Assume the following repository declaration that basically aggregates one base repository plus 3 fragments:
import org.springframework.data.neo4j.repository.Neo4jRepository;
public interface MovieRepository extends Neo4jRepository<MovieEntity, String>,
DomainResults,
NonDomainResults,
LowlevelInteractions {
}
The repository contains Movies as shown in the getting started section.
The additional interface from which the repository extends (DomainResults
, NonDomainResults
and LowlevelInteractions
)
are the fragments that addresses all the concerns above.
Using complex, dynamic custom queries but still returning domain types
The fragment DomainResults
declares one additional method findMoviesAlongShortestPath
:
interface DomainResults {
@Transactional(readOnly = true)
List<MovieEntity> findMoviesAlongShortestPath(PersonEntity from, PersonEntity to);
}
This method is annotated with @Transactional(readOnly = true)
to indicate that readers can answer it.
It cannot be derived by SDN but would need a custom query.
This custom query is provided by the one implementation of that interface.
The implementation has the same name with the suffix Impl
:
import static org.neo4j.cypherdsl.core.Cypher.anyNode;
import static org.neo4j.cypherdsl.core.Cypher.listWith;
import static org.neo4j.cypherdsl.core.Cypher.name;
import static org.neo4j.cypherdsl.core.Cypher.node;
import static org.neo4j.cypherdsl.core.Cypher.parameter;
import static org.neo4j.cypherdsl.core.Cypher.shortestPath;
import org.neo4j.cypherdsl.core.Cypher;
class DomainResultsImpl implements DomainResults {
private final Neo4jTemplate neo4jTemplate; (1)
DomainResultsImpl(Neo4jTemplate neo4jTemplate) {
this.neo4jTemplate = neo4jTemplate;
}
@Override
public List<MovieEntity> findMoviesAlongShortestPath(PersonEntity from, PersonEntity to) {
var p1 = node("Person").withProperties("name", parameter("person1"));
var p2 = node("Person").withProperties("name", parameter("person2"));
var shortestPath = shortestPath("p").definedBy(
p1.relationshipBetween(p2).unbounded()
);
var p = shortestPath.getRequiredSymbolicName();
var statement = Cypher.match(shortestPath)
.with(p, listWith(name("n"))
.in(Cypher.nodes(shortestPath))
.where(anyNode().named("n").hasLabels("Movie")).returning().as("mn")
)
.unwind(name("mn")).as("m")
.with(p, name("m"))
.match(node("Person").named("d")
.relationshipTo(anyNode("m"), "DIRECTED").named("r")
)
.returning(p, Cypher.collect(name("r")), Cypher.collect(name("d")))
.build();
Map<String, Object> parameters = new HashMap<>();
parameters.put("person1", from.getName());
parameters.put("person2", to.getName());
return neo4jTemplate.findAll(statement, parameters, MovieEntity.class); (2)
}
}
1 | The Neo4jTemplate is injected by the runtime through the constructor of DomainResultsImpl . No need for @Autowired . |
2 | The Cypher-DSL is used to build a complex statement (pretty much the same as shown in path mapping.) The statement can be passed directly to the template. |
The template has overloads for String-based queries as well, so you could write down the query as String as well. The important takeaway here is:
-
The template "knows" your domain objects and maps them accordingly
-
@Query
is not the only option to define custom queries -
They can be generated in various ways
-
The
@Transactional
annotation is respected
Using custom queries and custom mappings
Often times a custom query indicates custom results.
Should all of those results be mapped as @Node
? Of course not! Many times those objects represents read commands
and are not meant to be used as write commands.
It is also not unlikely that SDN cannot or want not map everything that is possible with Cypher.
It does however offer several hooks to run your own mapping: On the Neo4jClient
.
The benefit of using the SDN Neo4jClient
over the driver:
-
The
Neo4jClient
is integrated with Springs transaction management -
It has a fluent API for binding parameters
-
It has a fluent API exposing both the records and the Neo4j type system so that you can access everything in your result to execute the mapping
Declaring the fragment is exactly the same as before:
interface NonDomainResults {
class Result { (1)
public final String name;
public final String typeOfRelation;
Result(String name, String typeOfRelation) {
this.name = name;
this.typeOfRelation = typeOfRelation;
}
}
@Transactional(readOnly = true)
Collection<Result> findRelationsToMovie(MovieEntity movie); (2)
}
1 | This is a made up non-domain result. A real world query result would probably look more complex. |
2 | The method this fragment adds. Again, the method is annotated with Spring’s @Transactional |
Without an implementation for that fragment, startup would fail, so here it is:
class NonDomainResultsImpl implements NonDomainResults {
private final Neo4jClient neo4jClient; (1)
NonDomainResultsImpl(Neo4jClient neo4jClient) {
this.neo4jClient = neo4jClient;
}
@Override
public Collection<Result> findRelationsToMovie(MovieEntity movie) {
return this.neo4jClient
.query(""
+ "MATCH (people:Person)-[relatedTo]-(:Movie {title: $title}) "
+ "RETURN people.name AS name, "
+ " Type(relatedTo) as typeOfRelation"
) (2)
.bind(movie.getTitle()).to("title") (3)
.fetchAs(Result.class) (4)
.mappedBy((typeSystem, record) -> new Result(record.get("name").asString(),
record.get("typeOfRelation").asString())) (5)
.all(); (6)
}
}
1 | Here we use the Neo4jClient , as provided by the infrastructure. |
2 | The client takes only in Strings, but the Cypher-DSL can still be used when rendering into a String |
3 | Bind one single value to a named parameter. There’s also an overload to bind a whole map of parameters |
4 | This is the type of the result you want |
5 | And finally, the mappedBy method, exposing one Record for each entry in the result plus the drivers type system if needed.
This is the API in which you hook in for your custom mappings |
The whole query runs in the context of a Spring transaction, in this case, a read-only one.
Low level interactions
Sometimes you might want to do bulk loadings from a repository or delete whole subgraphs or interact in very specific ways with the Neo4j Java-Driver. This is possible as well. The following example shows how:
interface LowlevelInteractions {
int deleteGraph();
}
class LowlevelInteractionsImpl implements LowlevelInteractions {
private final Driver driver; (1)
LowlevelInteractionsImpl(Driver driver) {
this.driver = driver;
}
@Override
public int deleteGraph() {
try (Session session = driver.session()) {
SummaryCounters counters = session
.executeWrite(tx -> tx.run("MATCH (n) DETACH DELETE n").consume()) (2)
.counters();
return counters.nodesDeleted() + counters.relationshipsDeleted();
}
}
}
1 | Work with the driver directly. As with all the examples: There is no need for @Autowired magic. All the fragments
are actually testable on their own. |
2 | The use case is made up. Here we use a driver managed transaction deleting the whole graph and return the number of deleted nodes and relationships |
This interaction does of course not run in a Spring transaction, as the driver does not know about Spring.
Putting it all together, this test succeeds:
@Test
void customRepositoryFragmentsShouldWork(
@Autowired PersonRepository people,
@Autowired MovieRepository movies
) {
PersonEntity meg = people.findById("Meg Ryan").get();
PersonEntity kevin = people.findById("Kevin Bacon").get();
List<MovieEntity> moviesBetweenMegAndKevin = movies.
findMoviesAlongShortestPath(meg, kevin);
assertThat(moviesBetweenMegAndKevin).isNotEmpty();
Collection<NonDomainResults.Result> relatedPeople = movies
.findRelationsToMovie(moviesBetweenMegAndKevin.get(0));
assertThat(relatedPeople).isNotEmpty();
assertThat(movies.deleteGraph()).isGreaterThan(0);
assertThat(movies.findAll()).isEmpty();
assertThat(people.findAll()).isEmpty();
}
As a final word: All three interfaces and implementations are picked up by Spring Data Neo4j automatically. There is no need for further configuration. Also, the same overall repository could have been created with only one additional fragment (the interface defining all three methods) and one implementation. The implementation would than have had all three abstractions injected (template, client and driver).
All of this applies of course to reactive repositories as well.
They would work with the ReactiveNeo4jTemplate
and ReactiveNeo4jClient
and the reactive session provided by the driver.
If you have recurring methods for all repositories, you could swap out the default repository implementation.
How do I use custom Spring Data Neo4j base repositories?
Basically the same ways as the shared Spring Data Commons documentation shows for Spring Data JPA in Customize the Base Repository. Only that in our case you would extend from
public class MyRepositoryImpl<T, ID> extends SimpleNeo4jRepository<T, ID> {
MyRepositoryImpl(
Neo4jOperations neo4jOperations,
Neo4jEntityInformation<T, ID> entityInformation
) {
super(neo4jOperations, entityInformation); (1)
}
@Override
public List<T> findAll() {
throw new UnsupportedOperationException("This implementation does not support `findAll`");
}
}
1 | This signature is required by the base class. Take the Neo4jOperations (the actual specification of the Neo4jTemplate )
and the entity information and store them on an attribute if needed. |
In this example we forbid the use of the findAll
method.
You could add methods taking in a fetch depth and run custom queries based on that depth.
One way to do this is shown in DomainResults fragment.
To enable this base repository for all declared repositories enable Neo4j repositories with: @EnableNeo4jRepositories(repositoryBaseClass = MyRepositoryImpl.class)
.
How do I audit entities?
All Spring Data annotations are supported. Those are
-
org.springframework.data.annotation.CreatedBy
org.springframework.data.annotation.CreatedDate
org.springframework.data.annotation.LastModifiedBy
org.springframework.data.annotation.LastModifiedDate
監査は、Spring Data Commons のより大きなコンテキストで監査を使用する方法の概要を示します。次のリストは、Spring Data Neo4j によって提供されるすべての構成オプションを示しています。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.domain.AuditorAware;
@Configuration
@EnableNeo4jAuditing(
modifyOnCreate = false, (1)
auditorAwareRef = "auditorProvider", (2)
dateTimeProviderRef = "fixedDateTimeProvider" (3)
)
class AuditingConfig {
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.of("A user");
}
@Bean
public DateTimeProvider fixedDateTimeProvider() {
return () -> Optional.of(AuditingITBase.DEFAULT_CREATION_AND_MODIFICATION_DATE);
}
}
1 | 作成時に変更データも書き込む場合は true に設定します |
2 | この属性を使用して、監査を提供する Bean の名前を指定します。(つまりユーザー名) |
3 | この属性を使用して、現在の日付を提供する Bean の名前を指定します。この場合、上記の構成はテストの一部であるため、固定の日付が使用されます。 |
リアクティブバージョンは、監査者認識 Bean の型が ReactiveAuditorAware
であることを除けば基本的に同じであるため、監査者の取得はリアクティブフローの一部です。
これらの監査メカニズムに加えて、BeforeBindCallback<T>
または ReactiveBeforeBindCallback<T>
を実装する Bean をいくつでもコンテキストに追加できます。これらの Bean は Spring Data Neo4j によって取得され、エンティティが永続化される直前に (Ordered
を実装している場合、または @Order
のアノテーションが付けられている場合) 順番に呼び出されます。
エンティティを変更することも、完全に新しいエンティティを返すこともできます。次の例では、エンティティが永続化される前に 1 つの属性を変更する 1 つのコールバックをコンテキストに追加します。
import java.util.UUID;
import java.util.stream.StreamSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.core.mapping.callback.AfterConvertCallback;
import org.springframework.data.neo4j.core.mapping.callback.BeforeBindCallback;
@Configuration
class CallbacksConfig {
@Bean
BeforeBindCallback<ThingWithAssignedId> nameChanger() {
return entity -> {
ThingWithAssignedId updatedThing = new ThingWithAssignedId(
entity.getTheId(), entity.getName() + " (Edited)");
return updatedThing;
};
}
@Bean
AfterConvertCallback<ThingWithAssignedId> randomValueAssigner() {
return (entity, definition, source) -> {
entity.setRandomValue(UUID.randomUUID().toString());
return entity;
};
}
}
追加の構成は必要ありません。
「例で検索」を使用するにはどうすればよいですか ?
「例による検索」は SDN の新機能です。エンティティをインスタンス化するか、既存のものを使用します。このインスタンスを使用して org.springframework.data.domain.Example
を作成します。リポジトリが org.springframework.data.neo4j.repository.Neo4jRepository
または org.springframework.data.neo4j.repository.ReactiveNeo4jRepository
を継承している場合は、findByExample に示すように、例を取り入れて利用可能な findBy
メソッドをすぐに使用できます。
Example<MovieEntity> movieExample = Example.of(new MovieEntity("The Matrix", null));
Flux<MovieEntity> movies = this.movieRepository.findAll(movieExample);
movieExample = Example.of(
new MovieEntity("Matrix", null),
ExampleMatcher
.matchingAny()
.withMatcher(
"title",
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING)
)
);
movies = this.movieRepository.findAll(movieExample);
個々のプロパティを無効にすることもできます。これにより、適切な NOT
操作が追加され、=
が <>
に変わります。すべてのスカラーデータ型とすべての文字列演算子がサポートされています。
Example<MovieEntity> movieExample = Example.of(
new MovieEntity("Matrix", null),
ExampleMatcher
.matchingAny()
.withMatcher(
"title",
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING)
)
.withTransformer("title", Neo4jPropertyValueTransformers.notMatching())
);
Flux<MovieEntity> allMoviesThatNotContainMatrix = this.movieRepository.findAll(movieExample);
Spring Data Neo4j を使用するには Spring Boot が必要ですか ?
いいえ、そうではありません。Spring Boot を介した Spring の多くの側面の自動構成は多くの手動作業を軽減し、新しい Spring プロジェクトをセットアップする場合に推奨されるアプローチですが、これを使用する必要はありません。
上記のソリューションには次の依存関係が必要です。
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>7.5.1</version>
</dependency>
Gradle セットアップのコーディネートも同じです。
静的または動的に別のデータベースを選択するには、Neo4j 4 は複数のデータベースをサポートしています - 使用するにはどうすればよいですか ? に従って、型 DatabaseSelectionProvider
の Bean を追加します。リアクティブシナリオの場合は、ReactiveDatabaseSelectionProvider
を提供します。
Spring Boot を使用せずに Spring コンテキスト内で Spring Data Neo4j を使用する
必要な Bean の導入をサポートするために、2 つの抽象構成クラスが提供されています。つまり、命令型データベースアクセス用の org.springframework.data.neo4j.config.AbstractNeo4jConfig
とリアクティブバージョン用の org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig
です。これらはそれぞれ @EnableNeo4jRepositories
および @EnableReactiveNeo4jRepositories
で使用することを目的としています。使用例については、命令型データベースアクセスのための Spring Data Neo4j インフラストラクチャの有効化およびリアクティブデータベースアクセスのための Spring Data Neo4j インフラストラクチャの有効化を参照してください。どちらのクラスでも、ドライバーを作成する必要がある driver()
をオーバーライドする必要があります。
Neo4j クライアントの命令型バージョン、テンプレートおよび命令型リポジトリのサポートを取得するには、ここに示すようなものを使用します。
import org.neo4j.driver.Driver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.data.neo4j.config.AbstractNeo4jConfig;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
@Configuration
@EnableNeo4jRepositories
@EnableTransactionManagement
class MyConfiguration extends AbstractNeo4jConfig {
@Override @Bean
public Driver driver() { (1)
return GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "secret"));
}
@Override
protected Collection<String> getMappingBasePackages() {
return Collections.singletonList(Person.class.getPackage().getName());
}
@Override @Bean (2)
protected DatabaseSelectionProvider databaseSelectionProvider() {
return DatabaseSelectionProvider.createStaticDatabaseSelectionProvider("yourDatabase");
}
}
1 | ドライバー Bean が必要です。 |
2 | これは、yourDatabase という名前のデータベースを静的に選択します。これはオプションです。 |
次のリストは、リアクティブな Neo4j クライアントとテンプレートを提供し、リアクティブなトランザクション管理を有効にし、Neo4j 関連のリポジトリを検出します。
import org.neo4j.driver.Driver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig;
import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableReactiveNeo4jRepositories
@EnableTransactionManagement
class MyConfiguration extends AbstractReactiveNeo4jConfig {
@Bean
@Override
public Driver driver() {
return GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "secret"));
}
@Override
protected Collection<String> getMappingBasePackages() {
return Collections.singletonList(Person.class.getPackage().getName());
}
}
CDI 2.0 環境での Spring Data Neo4j の使用
便宜上、Neo4jCdiExtension
による CDI 拡張機能を提供します。互換性のある CDI 2.0 コンテナーで実行すると、Java のサービスローダー SPI [Oracle] (英語) を通じて自動的に登録およびロードされます。
アプリケーションに導入する必要があるのは、Neo4j Java Driver を生成するアノテーション付き型だけです。
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
public class Neo4jConfig {
@Produces @ApplicationScoped
public Driver driver() { (1)
return GraphDatabase
.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "secret"));
}
public void close(@Disposes Driver driver) {
driver.close();
}
@Produces @Singleton
public DatabaseSelectionProvider getDatabaseSelectionProvider() { (2)
return DatabaseSelectionProvider.createStaticDatabaseSelectionProvider("yourDatabase");
}
}
1 | 命令型データベースアクセスのための Spring Data Neo4j インフラストラクチャの有効化のプレーン Spring と同じですが、対応する CDI インフラストラクチャのアノテーションが付けられます。 |
2 | これはオプションです。ただし、カスタムデータベース選択プロバイダーを実行する場合は、この Bean を修飾してはなりません。 |
たとえば Weld (英語) が提供するような SE コンテナーで実行している場合は、次のように拡張機能を有効にできます。
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
import org.springframework.data.neo4j.config.Neo4jCdiExtension;
public class SomeClass {
void someMethod() {
try (SeContainer container = SeContainerInitializer.newInstance()
.disableDiscovery()
.addExtensions(Neo4jCdiExtension.class)
.addBeanClasses(YourDriverFactory.class)
.addPackages(Package.getPackage("your.domain.package"))
.initialize()
) {
SomeRepository someRepository = container.select(SomeRepository.class).get();
}
}
}