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);
Veuillez aider.
J'ai résolu le problème.
(En conséquence, nous aurons un objet client clairsemé uniquement avec l'identifiant et le nom)
public interface SparseCustomerRepository {
List<Customer> findAllWithNameOnly(Specification<Customer> spec);
}
@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;
}
}
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.
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.
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);
}
où 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);
}
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.