복붙노트

[SPRING] Spring 데이터 JPA. findAll () 메소드에서 ID 목록 만 얻는 방법

SPRING

Spring 데이터 JPA. findAll () 메소드에서 ID 목록 만 얻는 방법

나는 매우 복잡한 모델을 가지고있다. 엔티티는 많은 관계를 가지고 있습니다.

Spring Data JPA를 사용하려고 시도하고 저장소를 준비했습니다.

하지만 개체를 ​​매우 큰 때문에 성능 문제가있는 개체에 대한 사양 가진 findAll () 메서드를 호출 할 때. 나는 내가 다음과 같은 메소드를 호출 할 때 이것을 알기 때문에 :

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

성능에 문제가 없었습니다.

하지만 내가 호출 할 때

List<Customer> findAll(); 

성능에 큰 문제가있었습니다.

문제는 내가 고객을위한 사양으로 findAll 메소드를 호출해야하는데, 이는 왜 객체 배열 목록을 반환하는 메소드를 사용할 수 없는지입니다.

고객 개체에 대한 사양을 가진 모든 고객을 찾는 방법을 작성하는 방법이지만 ID 만 반환합니다.

이렇게 :

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

도와주세요.

해결법

  1. ==============================

    1.@Query 어노테이션을 사용하지 않는 이유는 무엇입니까?

    @Query 어노테이션을 사용하지 않는 이유는 무엇입니까?

    @Query ( "# {# entityName} p에서 p.id 선택") List getAllIds ();

    내가 볼 수있는 유일한 단점은 속성 ID가 변경 될 때이지만 매우 일반적인 이름이고 변경할 가능성이 없으므로 (id = 기본 키) 확인해야합니다.

  2. ==============================

    2.이것은 이제 Projections를 사용하는 Spring 데이터에 의해 지원됩니다 :

    이것은 이제 Projections를 사용하는 Spring 데이터에 의해 지원됩니다 :

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

    고객 저장소보다

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

    편집하다: Redouane ROUND에서 지적한 바와 같이 현재 사양과 함께 ROUND Projections는 버그로 인해 작동하지 않습니다.

    그러나이 Spring Data Jpa 결함을 해결할 수있는 spec-with-projection 라이브러리를 사용할 수 있습니다.

  3. ==============================

    3.나는 그 문제를 해결했다.

    나는 그 문제를 해결했다.

    (결과적으로 우리는 ID와 이름만을 가진 희소 고객 객체를 가질 것입니다)

    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;
        }
    }
    
  4. ==============================

    4.불행히도 투영법은 사양과 호환되지 않습니다. JpaSpecificationExecutor는 리포지토리 (List findAll (Specification var1);)가 관리하는 집계 루트로 형식화 된 List 만 반환합니다.

    불행히도 투영법은 사양과 호환되지 않습니다. JpaSpecificationExecutor는 리포지토리 (List findAll (Specification var1);)가 관리하는 집계 루트로 형식화 된 List 만 반환합니다.

    실제 해결 방법은 Tuple을 사용하는 것입니다. 예 :

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

    여기서 투영은 루트 투영을위한 기능적 인터페이스입니다.

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

    SingleTupleMapper 및 TupleMapper는 TupleQuery 결과를 반환 할 객체에 매핑하는 데 사용됩니다.

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

    도움이되기를 바랍니다.

  5. from https://stackoverflow.com/questions/30331767/spring-data-jpa-how-to-get-only-a-list-of-ids-from-findall-method by cc-by-sa and MIT license