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