web-dev-qa-db-fra.com

Spring Data JPA. Comment obtenir uniquement une liste d'ID à partir de la méthode findAll ()

J'ai un modèle très compliqué. L'entité a beaucoup de relations, etc.

J'essaie d'utiliser Spring Data JPA et j'ai préparé un référentiel.

mais lorsque j'appelle une méthode, findAll () avec des spécifications pour l'objet a un problème de performances car les objets sont très gros. Je le sais parce que lorsque j'appelle une méthode comme celle-ci:

@Query(value = "select id, name from Customer ")
List<Object[]> myFindCustomerIds();

Je n'ai eu aucun problème de performance.

Mais quand j'invoque

List<Customer> findAll(); 

J'ai eu un gros problème avec les performances.

Le problème est que je dois invoquer la méthode findAll avec Spécifications pour le client, c'est pourquoi je ne peux pas utiliser la méthode qui renvoie une liste de tableaux d'objets.

Comment écrire une méthode pour trouver tous les clients avec des spécifications pour l'entité Client mais qui ne renvoie qu'un ID.

comme ça:

List<Long> findAll(Specification<Customer> spec);
  • Je ne peux pas utiliser dans ce cas la pagination.

Veuillez aider.

24
user6778654

J'ai résolu le problème.

(En conséquence, nous aurons un objet client clairsemé uniquement avec l'identifiant et le nom)

Définissez leur propre référentiel:

public interface SparseCustomerRepository {
    List<Customer> findAllWithNameOnly(Specification<Customer> spec);
}

Et une implémentation (rappelez-vous du suffixe - Impl par défaut)

@Service
public class SparseCustomerRepositoryImpl implements SparseCustomerRepository {
    private final EntityManager entityManager;

    @Autowired
    public SparseCustomerRepositoryImpl(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Override
    public List<Customer> findAllWithNameOnly(Specification<Customer> spec) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Tuple> tupleQuery = criteriaBuilder.createTupleQuery();
        Root<Customer> root = tupleQuery.from(Customer.class);
        tupleQuery.multiselect(getSelection(root, Customer_.id),
                getSelection(root, Customer_.name));
        if (spec != null) {
            tupleQuery.where(spec.toPredicate(root, tupleQuery, criteriaBuilder));
        }

        List<Tuple> CustomerNames = entityManager.createQuery(tupleQuery).getResultList();
        return createEntitiesFromTuples(CustomerNames);
    }

    private Selection<?> getSelection(Root<Customer> root,
            SingularAttribute<Customer, ?> attribute) {
        return root.get(attribute).alias(attribute.getName());
    }

    private List<Customer> createEntitiesFromTuples(List<Tuple> CustomerNames) {
        List<Customer> customers = new ArrayList<>();
        for (Tuple customer : CustomerNames) {
            Customer c = new Customer();
            c.setId(customer.get(Customer_.id.getName(), Long.class));
            c.setName(customer.get(Customer_.name.getName(), String.class));
            c.add(customer);
        }
        return customers;
    }
}
8
user6778654

Pourquoi ne pas utiliser l'annotation @Query?

@Query("select p.id from #{#entityName} p") List<Long> getAllIds();

Le seul inconvénient que je vois est lorsque l'attribut id change, mais comme il s'agit d'un nom très courant et peu susceptible de changer (id = clé primaire), cela devrait être correct.

28
eav

Ceci est maintenant supporté par Spring Data en utilisant Projections :

interface SparseCustomer {  

  String getId(); 

  String getName();  
}

Que dans votre référentiel Customer

List<SparseCustomer> findAll(Specification<Customer> spec);

MODIFIER:
Comme indiqué par Radouane ROUFID Projections with Specifications actuellement ne fonctionne pas parce que bug .

Mais vous pouvez utiliser la bibliothèque specification-avec-projection qui contourne cette déficience Spring Data Jpa.

18
Ondrej Bozek

Malheureusement Projections ne fonctionne pas avec spécifications . JpaSpecificationExecutor retourne uniquement une liste tapée avec la racine agrégée gérée par le référentiel (List<T> findAll(Specification<T> var1);)

Une solution de contournement réelle consiste à utiliser Tuple. Exemple :

    @Override
    public <D> D findOne(Projections<DOMAIN> projections, Specification<DOMAIN> specification, SingleTupleMapper<D> tupleMapper) {
        Tuple tuple = this.getTupleQuery(projections, specification).getSingleResult();
        return tupleMapper.map(Tuple);
    }

    @Override
    public <D extends Dto<ID>> List<D> findAll(Projections<DOMAIN> projections, Specification<DOMAIN> specification, TupleMapper<D> tupleMapper) {
        List<Tuple> tupleList = this.getTupleQuery(projections, specification).getResultList();
        return tupleMapper.map(tupleList);
    }

    private TypedQuery<Tuple> getTupleQuery(Projections<DOMAIN> projections, Specification<DOMAIN> specification) {

        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Tuple> query = cb.createTupleQuery();

        Root<DOMAIN> root = query.from((Class<DOMAIN>) domainClass);

        query.multiselect(projections.project(root));
        query.where(specification.toPredicate(root, query, cb));

        return entityManager.createQuery(query);
    }

Projections est une interface fonctionnelle pour la projection racine.

@FunctionalInterface
public interface Projections<D> {

    List<Selection<?>> project(Root<D> root);

}

SingleTupleMapper et TupleMapper sont utilisés pour mapper le résultat TupleQuery à l'objet que vous souhaitez renvoyer.

@FunctionalInterface
public interface SingleTupleMapper<D> {

    D map(Tuple tuple);
}

@FunctionalInterface
public interface TupleMapper<D> {

    List<D> map(List<Tuple> tuples);

}

Exemple d'utilisation:

        Projections<User> userProjections = (root) -> Arrays.asList(
                root.get(User_.uid).alias(User_.uid.getName()),
                root.get(User_.active).alias(User_.active.getName()),
                root.get(User_.userProvider).alias(User_.userProvider.getName()),
                root.join(User_.profile).get(Profile_.firstName).alias(Profile_.firstName.getName()),
                root.join(User_.profile).get(Profile_.lastName).alias(Profile_.lastName.getName()),
                root.join(User_.profile).get(Profile_.picture).alias(Profile_.picture.getName()),
                root.join(User_.profile).get(Profile_.gender).alias(Profile_.gender.getName())
        );

        Specification<User> userSpecification = UserSpecifications.withUid(userUid);

        SingleTupleMapper<BasicUserDto> singleMapper = Tuple -> {

            BasicUserDto basicUserDto = new BasicUserDto();

            basicUserDto.setUid(Tuple.get(User_.uid.getName(), String.class));
            basicUserDto.setActive(Tuple.get(User_.active.getName(), Boolean.class));
            basicUserDto.setUserProvider(Tuple.get(User_.userProvider.getName(), UserProvider.class));
            basicUserDto.setFirstName(Tuple.get(Profile_.firstName.getName(), String.class));
            basicUserDto.setLastName(Tuple.get(Profile_.lastName.getName(), String.class));
            basicUserDto.setPicture(Tuple.get(Profile_.picture.getName(), String.class));
            basicUserDto.setGender(Tuple.get(Profile_.gender.getName(), Gender.class));

            return basicUserDto;
        };

        BasicUserDto basicUser = findOne(userProjections, userSpecification, singleMapper);

J'espère que ça aide.

0
Radouane ROUFID