web-dev-qa-db-fra.com

Référentiel Spring Data JPA: comment récupérer conditionnellement des entités enfants

Comment peut-on configurer leurs entités JPA pour ne pas récupérer les entités liées à moins qu'un certain paramètre d'exécution ne soit fourni.

Selon la documentation de Spring, 4.3.9. Configuration de Fetch- et LoadGraphs , vous devez utiliser le @EntityGraph annotation pour spécifier la politique de récupération pour les requêtes, mais cela ne me permet pas de décider au moment de l'exécution si je veux charger ces entités.

Je suis d'accord pour obtenir les entités enfants dans une requête distincte, mais pour ce faire, je devrais configurer mon référentiel ou mes entités pour ne récupérer aucun enfant. Malheureusement, je n'arrive pas à trouver de stratégies pour y parvenir. FetchPolicy est ignoré et EntityGraph n'est utile que pour spécifier les entités que je souhaite récupérer avec impatience.

Par exemple, supposons que Account est le parent et Contact est l'enfant, et qu'un compte peut avoir plusieurs contacts.

Je veux pouvoir faire ceci:

if(fetchPolicy.contains("contacts")){
  account.setContacts(contactRepository.findByAccountId(account.getAccountId());
}

Le problème est que les données de printemps récupèrent avec impatience les contacts de toute façon.

La classe Entité de compte ressemble à ceci:

@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

}

La classe AccountRepository ressemble à ceci:

public interface AccountRepository extends JpaRepository<Account, String>
{
    //@EntityGraph ... <-- has type= LOAD or FETCH, but neither can help me prevent retrieval
    Account findOne(String id);
}
24
cosbor11

L'extraction paresseuse devrait fonctionner correctement si aucune méthode d'objet résultant de getContacts () n'est appelée.

Si vous préférez plus de travail manuel et que vous voulez vraiment avoir le contrôle sur cela (peut-être plus de contextes selon le cas d'utilisation). Je vous suggère de supprimer les contacts de l'entité de compte et de mapper le compte dans les contacts à la place. Une façon de dire à hibernate d'ignorer ce champ consiste à le mapper à l'aide de l'annotation @Transient.

@Entity
@Table(name = "accounts")
public class Account
{
    protected String accountId;
    protected Collection<Contact> contacts;

    @Transient
    public Collection<Contact> getContacts()
    {
        return contacts;
    }

    //getters & setters

}

Ensuite, dans votre classe de service, vous pourriez faire quelque chose comme:

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;
}

J'espère que c'est ce que vous recherchez. Btw, le code n'est pas testé, vous devriez donc probablement vérifier à nouveau.

11
Rowanto

Vous pouvez utiliser @Transactional pour ça.

Pour cela, vous devez récupérer votre entité de compte Lazily.

@Transactional Les annotations doivent être placées autour de toutes les opérations inséparables.

Méthode d'écriture dans votre couche de service qui accepte un indicateur pour récupérer les contacts avec impatience.

@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 est un service qui peut effectuer plusieurs appels en une seule transaction sans fermer la connexion avec le point final.

Espérant que ceci puisse t'être utile. :)

Pour plus de détails reportez-vous à ce lien

10
Harshal Patil

Veuillez trouver un exemple qui fonctionne avec JPA 2.1.

Définissez le ou les attributs que vous souhaitez uniquement charger (avec la liste attributeNodes):

Votre entité avec les annotations du graphique d'entité:

@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;
    }
}

Votre interface personnalisée:

public interface AccountRepository extends JpaRepository<Account, String> {

    @EntityGraph("accountGraph")
    Account findOne(String id);
}

Seule la propriété "accountId" sera chargée avec impatience. Toutes les autres propriétés seront chargées paresseusement lors de l'accès.

6
André Blaszczyk

Les données de printemps n'ignorent pas fetch=FetchType.Lazy.

Mon problème était que j'utilisais dozer-mapping pour convertir mes entités en graphiques. De toute évidence, dozer appelle les getters et les setters pour mapper deux objets, j'ai donc dû ajouter une configuration de mappeur de champs personnalisé pour ignorer 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;
    }   
}
2
cosbor11

Si vous essayez d'envoyer le jeu de résultats de vos entités à un client, je vous recommande d'utiliser des objets de transfert de données (DTO) au lieu des entités. Vous pouvez créer directement un DTO dans le HQL/JPQL. Par exemple

"select new com.test.MyTableDto(my.id, my.name) from MyTable my"

et si tu veux passer l'enfant

"select new com.test.MyTableDto(my.id, my.name, my.child) from MyTable my"

De cette façon, vous avez un contrôle total sur ce qui est créé et transmis au client.

0
leventgo