特定の実行パラメータが提供されない限り、関連するエンティティをフェッチしないようにJPAエンティティを構成するにはどうすればよいですか。
Springのドキュメント 4.3.9。Fetch-とLoadGraphsの設定 によると、@EntityGraph
アノテーションを使用して、クエリのフェッチポリシーを指定しますが、これらのエンティティをロードするかどうかを実行時に決定することはできません。
別のクエリで子エンティティを取得しても問題ありませんが、そのためには、リポジトリまたはエンティティを構成して子を取得しないようにする必要があります。残念ながら、これを行う方法に関する戦略を見つけることはできません。 FetchPolicy
は無視され、EntityGraph
は、どのエンティティを積極的に取得するかを指定する場合にのみ役立ちます。
たとえば、Account
が親で、Contact
が子であり、アカウントが多くの連絡先を持つことができるとします。
これをできるようにしたい:
if(fetchPolicy.contains("contacts")){
account.setContacts(contactRepository.findByAccountId(account.getAccountId());
}
問題は、とにかくスプリングデータが連絡先を熱心に取得することです。
Account Entityクラスは次のようになります:
@Entity
@Table(name = "accounts")
public class Account
{
protected String accountId;
protected Collection<Contact> contacts;
@OneToMany
//@OneToMany(fetch=FetchType.LAZY) --> doesn't work, Spring Repositories ignore this
@JoinColumn(name="account_id", referencedColumnName="account_id")
public Collection<Contact> getContacts()
{
return contacts;
}
//getters & setters
}
AccountRepositoryクラスは次のようになります:
public interface AccountRepository extends JpaRepository<Account, String>
{
//@EntityGraph ... <-- has type= LOAD or FETCH, but neither can help me prevent retrieval
Account findOne(String id);
}
GetContacts()から生じるオブジェクトのメソッドが呼び出されない場合、遅延フェッチは適切に機能するはずです。
より多くの手作業を好む場合で、実際にこれを制御したい場合(ユースケースに応じてより多くのコンテキストが必要な場合があります)。アカウントエンティティから連絡先を削除し、代わりに連絡先にアカウントをマッピングすることをお勧めします。 hibernateにそのフィールドを無視するように指示する1つの方法は、@ Transientアノテーションを使用してマッピングすることです。
@Entity
@Table(name = "accounts")
public class Account
{
protected String accountId;
protected Collection<Contact> contacts;
@Transient
public Collection<Contact> getContacts()
{
return contacts;
}
//getters & setters
}
次に、サービスクラスで次のようなことができます。
public Account getAccountById(int accountId, Set<String> fetchPolicy) {
Account account = accountRepository.findOne(accountId);
if(fetchPolicy.contains("contacts")){
account.setContacts(contactRepository.findByAccountId(account.getAccountId());
}
return account;
}
これがあなたが探しているものであることを願っています。ところで、コードはテストされていないので、おそらくもう一度確認する必要があります。
そのために@Transactional
を使用できます。
そのためには、アカウントエンティティを遅延的に取得する必要があります。
@Transactional
注釈は、分離できないすべての操作の周りに配置する必要があります。
連絡先を積極的に取得するために1つのフラグを受け入れるサービスレイヤーでメソッドを記述します。
@Transactional
public Account getAccount(String id, boolean fetchEagerly){
Account account = accountRepository.findOne(id);
//If you want to fetch contact then send fetchEagerly as true
if(fetchEagerly){
//Here fetching contacts eagerly
Object object = account.getContacts().size();
}
}
@Transactionalは、エンドポイントとの接続を閉じることなく、単一のトランザクションで複数の呼び出しを行うことができるサービスです。
これがあなたのお役に立てば幸いです。 :)
詳細については このリンクを参照
JPA 2.1で実行される例を見つけてください
(attributeNodesリストを使用して)ロードするだけの属性を設定します。
エンティティグラフアノテーションのあるエンティティ:
@Entity
@NamedEntityGraph(name = "accountGraph", attributeNodes = {
@NamedAttributeNode("accountId")})
@Table(name = "accounts")
public class Account {
protected String accountId;
protected Collection<Contact> contacts;
@OneToMany(fetch=FetchType.LAZY)
@JoinColumn(name="account_id", referencedColumnName="account_id")
public Collection<Contact> getContacts()
{
return contacts;
}
}
カスタムインターフェイス:
public interface AccountRepository extends JpaRepository<Account, String> {
@EntityGraph("accountGraph")
Account findOne(String id);
}
「accountId」プロパティのみが積極的にロードされます。他のすべてのプロパティは、アクセス時に遅延ロードされます。
Springデータはfetch=FetchType.Lazy
を無視しません。
私の問題は、dozer-mapping
を使用してエンティティをグラフに変換していたことです。明らかにdozer
はゲッターとセッターを呼び出して2つのオブジェクトをマップするため、カスタムフィールドマッパー構成を追加してPersistentCollectionsを無視する必要がありました...
GlobalCustomFieldMapper.Java:
public class GlobalCustomFieldMapper implements CustomFieldMapper
{
public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping)
{
if (!(sourceFieldValue instanceof PersistentCollection)) {
// Allow dozer to map as normal
return;
}
if (((PersistentCollectiosourceFieldValue).wasInitialized()) {
// Allow dozer to map as normal
return false;
}
// Set destination to null, and tell dozer that the field is mapped
destination = null;
return true;
}
}
エンティティの結果セットをクライアントに送信しようとしている場合は、エンティティの代わりにデータ転送オブジェクト(DTO)を使用することをお勧めします。 HQL/JPQL内でDTOを直接作成できます。例えば
"select new com.test.MyTableDto(my.id, my.name) from MyTable my"
そして、あなたが子供を渡したい場合
"select new com.test.MyTableDto(my.id, my.name, my.child) from MyTable my"
これにより、作成されてクライアントに渡されるものを完全に制御できます。