springframework query org jparepository example data consultas spring spring-data-jpa specifications criteria-api

query - Spring Data JPA. Cómo obtener solo una lista de ID del método findAll()



jparepository spring boot (4)

¿Por qué no usar la anotación @Query ?

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

La única desventaja que veo es cuando cambia la id del atributo, pero como este es un nombre muy común y es poco probable que cambie (id = clave principal), debería estar bien.

Tengo un modelo muy complicado. La entidad tiene mucha relación y así sucesivamente.

Intento usar Spring Data JPA y preparé un repositorio.

pero cuando invoco un método findAll () con especificación para el objeto a, tenemos un problema de rendimiento porque los objetos son muy grandes. Lo sé porque cuando invoco un método como este:

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

No tuve ningún problema con el rendimiento.

Pero cuando invoco

List<Customer> findAll();

Tuve un gran problema con el rendimiento.

El problema es que necesito invocar el método findAll con Especificaciones para el Cliente, por eso no puedo usar el método que devuelve una lista de matrices de objetos.

Cómo escribir un método para encontrar a todos los clientes con especificaciones para la entidad Cliente pero que solo devuelve una ID.

Me gusta esto:

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

  • No puedo usar en este caso la paginación.

Por favor ayuda.


Desafortunadamente las Projections no funcionan con specifications . JpaSpecificationExecutor devuelve solo una Lista escrita con la raíz agregada administrada por el repositorio ( List<T> findAll(Specification<T> var1); )

Una solución real es utilizar Tuple. Ejemplo:

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

donde Projections es una interfaz funcional para la proyección de raíz.

@FunctionalInterface public interface Projections<D> { List<Selection<?>> project(Root<D> root); }

SingleTupleMapper y TupleMapper se utilizan para asignar el resultado de TupleQuery al objeto que desea devolver.

@FunctionalInterface public interface SingleTupleMapper<D> { D map(Tuple tuple); } @FunctionalInterface public interface TupleMapper<D> { List<D> map(List<Tuple> tuples); }

Ejemplo de uso:

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

Espero que ayude.


Esto ahora es soportado por Spring Data usando Projections :

interface SparseCustomer { String getId(); String getName(); }

Que en tu repositorio de Customer

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

EDITAR:
Según lo observado por Radouane, las proyecciones ROUFID con especificaciones actualmente no funcionan debido a un bug .

Pero puede usar la biblioteca de specification-with-projection que soluciona esta deficiencia de Spring Data Jpa.


Resolví el problema.

(Como resultado, tendremos un objeto de Cliente disperso solo con ID y nombre)

Definir su propio repositorio:

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

Y una implementación (recuerde acerca del sufijo - Impl como predeterminado)

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