web-dev-qa-db-fra.com

Spring Data JPA et NamedEntityGraphs

actuellement, je ne peux récupérer que les données dont j'ai besoin. La méthode findAll () doit extraire des données en fonction de l'endroit où elles sont appelées. Je ne veux pas finir par écrire des méthodes différentes pour chaque graphe d'entités. De plus, j'éviterais d'appeler entitymanagers et de former moi-même les requêtes (répétitives). En gros, je souhaite utiliser la méthode findAll de génération, mais avec le graphe d'entités de mon choix. Une chance?

@Entity
@Table(name="complaints")
@NamedEntityGraphs({
    @NamedEntityGraph(name="allJoinsButMessages", attributeNodes = {
            @NamedAttributeNode("customer"),
            @NamedAttributeNode("handling_employee"),
            @NamedAttributeNode("genre")
    }),
    @NamedEntityGraph(name="allJoins", attributeNodes = {
            @NamedAttributeNode("customer"),
            @NamedAttributeNode("handling_employee"),
            @NamedAttributeNode("genre"),
            @NamedAttributeNode("complaintMessages")
    }),
    @NamedEntityGraph(name="noJoins", attributeNodes = {

    })
})
public class Complaint implements Serializable{
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    private long id;

    private Timestamp date;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer")
    private User customer;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "handling_employee")
    private User handling_employee;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="genre")
    private Genre genre;

    private boolean closed;

    @OneToMany(mappedBy = "complaint", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<ComplaintMessage> complaintMessages = new ArrayList<ComplaintMessage>();

//getters and setters
}

Et mon JPARepository

@Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{

    List<Complaint> findByClosed(boolean closed);

    @EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
    @Override
    List<Complaint> findAll(Sort sort);
}
41
dendimiiii

Nous avons rencontré un problème similaire et élaboré plusieurs solutions prospectives, mais il ne semble pas y avoir de solution élégante à ce qui semble être un problème commun.

1) Préfixes. Data jpa offre plusieurs préfixes (find, get, ...) pour un nom de méthode. Une possibilité consiste à utiliser différents préfixes avec différents graphes nommés. C’est un travail minime, mais cache au développeur le sens de la méthode et risque fort de causer des problèmes non évidents avec le chargement d’entités incorrect.

@Repository
@Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
    @EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
    User findByUserID(int id);

    @EntityGraph(value = "User.membershipYears", type = EntityGraphType.LOAD)
    User readByUserId(int id);
}

2) CustomRepository. Une autre solution possible consiste à créer des méthodes de requête personnalisées et à injecter EntityManager. Cette solution vous offre l'interface la plus propre pour votre référentiel, car vous pouvez attribuer à vos méthodes quelque chose de significatif, mais il est très complexe d'ajouter à votre code pour fournir la solution ET vous saisissez manuellement le gestionnaire d'entités au lieu d'utiliser Spring magic.

interface UserRepositoryCustom {
    public User findUserWithMembershipYearsById(int id);
}

class UserRepositoryImpl implements UserRepositoryCustom {
    @PersistenceContext
    private EntityManager em;
    @Override
    public User findUserWithMembershipYearsById(int id) {
        User result = null;
        List<User> users = em.createQuery("SELECT u FROM users AS u WHERE u.id = :id", User.class)
                .setParameter("id", id)
                .setHint("javax.persistence.fetchgraph", em.getEntityGraph("User.membershipYears"))
                .getResultList();
        if(users.size() >= 0) {
            result = users.get(0);
        }
        return result;
    }
}

@Repository
@Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
    @EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
    User findByUserID(int id);
}

3) JPQL. Il s’agit essentiellement de renoncer aux graphes d’entité nommés et d’utiliser JPQL pour gérer vos jointures à votre place. Non-idéal à mon avis.

@Repository
@Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
    @EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
    User findByUserID(int id);

    @Query("SELECT u FROM users WHERE u.id=:id JOIN??????????????????????????")
    User findUserWithTags(@Param("id") final int id);
}

Nous avons opté pour l’option 1, car c’est l’implémentation la plus simple, mais cela signifie que lorsque nous utilisons nos référentiels, nous devons examiner les méthodes de récupération pour nous assurer que nous utilisons celle avec le graphe d’entités correct. Bonne chance.

Sources:

Je n'ai pas assez de réputation pour publier toutes mes sources. Désolé :(

39
Chris Spencer

Nous avons eu le même problème et avons construit une extension Spring Data JPA pour le résoudre:

https://github.com/Cosium/spring-data-jpa-entity-graph

Cette extension permet de passer EntityGraph nommé ou construit dynamiquement comme argument de toute méthode de référentiel.

Avec cette extension, vous auriez cette méthode immédiatement disponible:

List<Complaint> findAll(Sort sort, EntityGraph entityGraph);

Et pouvoir l'appeler avec un EntityGraph sélectionné au moment de l'exécution.

14

Utilisation @EntityGraph ensemble avec @Query

@Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{

   @EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
   @Query("SELECT c FROM Complaint ORDER BY ..")
   @Override
   List<Complaint> findAllJoinsButMessages();

   @EntityGraph(value = "allJoins" , type=EntityGraphType.FETCH)
   @Query("SELECT c FROM Complaint ORDER BY ..")
   @Override
   List<Complaint> findAllJoin();

   ...

}

11
GKislin

Pouvez-vous essayer de créer le nom EntiyGraph avec enfant que vous demanderez et donner le même nom à la méthode find all. Ex:

@EntityGraph(value = "fetch.Profile.Address.record", type = EntityGraphType.LOAD)
 Employee getProfileAddressRecordById(long id);

Pour votre cas:

@NamedEntityGraph(name="all.Customer.handling_employee.genre", attributeNodes = {
        @NamedAttributeNode("customer"),
        @NamedAttributeNode("handling_employee"),
        @NamedAttributeNode("genre")
})

nom de la méthode dans le référentiel

@EntityGraph(value = "all.Customer.handling_employee.genre" , type=EntityGraphType.FETCH)
 findAllCustomerHandlingEmployeeGenre

De cette façon, vous pouvez suivre différentes méthodes findAll.

1
smile