[java] JPA : 기본 쿼리 결과 세트를 POJO 클래스 콜렉션으로 변환하는 방법

프로젝트에서 JPA를 사용하고 있습니다.

5 개의 테이블에서 조인 작업을 수행 해야하는 쿼리에 왔습니다. 그래서 5 개의 필드를 반환하는 기본 쿼리를 만들었습니다.

이제 결과 객체를 동일한 5 개의 문자열이 포함 된 Java POJO 클래스로 변환하려고합니다.

JPA에서 결과를 POJO 객체 목록으로 직접 캐스트하는 방법이 있습니까 ??

나는 다음 해결책에왔다.

@NamedNativeQueries({
    @NamedNativeQuery(
        name = "nativeSQL",
        query = "SELECT * FROM Actors",
        resultClass = db.Actor.class),
    @NamedNativeQuery(
        name = "nativeSQL2",
        query = "SELECT COUNT(*) FROM Actors",
        resultClass = XXXXX) // <--------------- problem  
})  

이제 여기 resultClass에서 실제 JPA 엔터티 인 클래스를 제공해야합니까? 또는 동일한 열 이름을 포함하는 JAVA POJO 클래스로 변환 할 수 있습니까?



답변

JPA는 SqlResultSetMapping네이티브 쿼리의 모든 반환 내용을 엔터티에 매핑 할 수있는또는 커스텀 클래스.

EDIT JPA 1.0은 비 엔티티 클래스로의 매핑을 허용하지 않습니다. JPA 2.1에서만 ConstructorResult 가 추가되어 Java 클래스의 리턴 값을 맵핑합니다.

또한 OP를 계산하는 데 문제가 있으면 단일 결과 집합 매핑을 정의하기에 충분해야합니다 ColumnResult


답변

나는 이것에 대한 몇 가지 해결책을 발견했다.

매핑 된 엔터티 사용 (JPA 2.0)

JPA 2.0을 사용하면 기본 조회를 POJO에 맵핑 할 수 없으며 엔티티로만 수행 할 수 있습니다.

예를 들어 :

Query query = em.createNativeQuery("SELECT name,age FROM jedi_table", Jedi.class);
@SuppressWarnings("unchecked")
List<Jedi> items = (List<Jedi>) query.getResultList();

그러나이 경우, Jedi는 맵핑 된 엔티티 클래스 여야합니다.

여기서 확인되지 않은 경고를 피하기위한 대안은 명명 된 기본 쿼리를 사용하는 것입니다. 따라서 엔티티에서 기본 쿼리를 선언하면

@NamedNativeQuery(
 name="jedisQry",
 query = "SELECT name,age FROM jedis_table",
 resultClass = Jedi.class)

그런 다음 간단히 할 수 있습니다.

TypedQuery<Jedi> query = em.createNamedQuery("jedisQry", Jedi.class);
List<Jedi> items = query.getResultList();

이것은 더 안전하지만 여전히 매핑 된 엔터티를 사용하도록 제한되어 있습니다.

수동 매핑

내가 JPA 2.1이 도착하기 전에 약간의 실험을 한 솔루션은 약간의 리플렉션을 사용하여 POJO 생성자에 대한 매핑을 수행하고있었습니다.

public static <T> T map(Class<T> type, Object[] tuple){
   List<Class<?>> tupleTypes = new ArrayList<>();
   for(Object field : tuple){
      tupleTypes.add(field.getClass());
   }
   try {
      Constructor<T> ctor = type.getConstructor(tupleTypes.toArray(new Class<?>[tuple.length]));
      return ctor.newInstance(tuple);
   } catch (Exception e) {
      throw new RuntimeException(e);
   }
}

이 메소드는 기본적으로 튜플 배열 (네이티브 쿼리에 의해 리턴 됨)을 가져와 동일한 수의 필드와 동일한 유형의 생성자를 찾아 제공된 POJO 클래스에 맵핑합니다.

그런 다음 다음과 같은 편리한 방법을 사용할 수 있습니다.

public static <T> List<T> map(Class<T> type, List<Object[]> records){
   List<T> result = new LinkedList<>();
   for(Object[] record : records){
      result.add(map(type, record));
   }
   return result;
}

public static <T> List<T> getResultList(Query query, Class<T> type){
  @SuppressWarnings("unchecked")
  List<Object[]> records = query.getResultList();
  return map(type, records);
}

그리고 우리는이 기술을 다음과 같이 간단하게 사용할 수 있습니다 :

Query query = em.createNativeQuery("SELECT name,age FROM jedis_table");
List<Jedi> jedis = getResultList(query, Jedi.class);

@SqlResultSetMapping을 사용한 JPA 2.1

JPA 2.1이 출시되면 @SqlResultSetMapping 주석을 사용하여 문제를 해결할 수 있습니다.

엔티티 어딘가에 결과 세트 맵핑을 선언해야합니다.

@SqlResultSetMapping(name="JediResult", classes = {
    @ConstructorResult(targetClass = Jedi.class,
    columns = {@ColumnResult(name="name"), @ColumnResult(name="age")})
})

그리고 우리는 단순히 :

Query query = em.createNativeQuery("SELECT name,age FROM jedis_table", "JediResult");
@SuppressWarnings("unchecked")
List<Jedi> samples = query.getResultList();

물론이 경우 Jedi매핑 된 엔터티 일 필요는 없습니다. 일반적인 POJO 일 수 있습니다.

XML 매핑 사용

나는 @SqlResultSetMapping엔티티 에이 모든 것을 침범하는 것을 발견 한 사람들 중 하나이며 특히 엔티티 내의 명명 된 쿼리의 정의를 싫어하므로 META-INF/orm.xml파일 에서이 모든 것을 수행 합니다.

<named-native-query name="GetAllJedi" result-set-mapping="JediMapping">
    <query>SELECT name,age FROM jedi_table</query>
</named-native-query>

<sql-result-set-mapping name="JediMapping">
        <constructor-result target-class="org.answer.model.Jedi">
            <column name="name" class="java.lang.String"/>
            <column name="age" class="java.lang.Integer"/>
        </constructor-result>
    </sql-result-set-mapping>

그리고 그것들은 내가 아는 모든 솔루션입니다. 마지막 두 가지는 JPA 2.1을 사용할 수있는 이상적인 방법입니다.


답변

예, JPA 2.1을 사용하면 쉽습니다. 매우 유용한 주석이 있습니다. 그들은 당신의 인생을 단순화합니다.

먼저 고유 쿼리를 선언 한 다음 결과 세트 맵핑 (데이터베이스가 POJO에 리턴 한 데이터의 맵핑을 정의 함)을 맵핑하십시오. 참조 할 POJO 클래스를 작성하십시오 (간단하게 여기에 포함되지 않음). 마지막으로 : DAO에서 메소드를 작성하여 (예를 들어) 조회를 호출하십시오. 이것은 dropwizard (1.0.0) 앱에서 저에게 효과적이었습니다.

먼저 엔티티 클래스에서 기본 쿼리를 선언하십시오.

@NamedNativeQuery (
name = "domain.io.MyClass.myQuery",
query = "Select a.colA, a.colB from Table a",
resultSetMapping = "mappinMyNativeQuery")   // must be the same name as in the SqlResultSetMapping declaration

아래에 결과 집합 매핑 선언을 추가 할 수 있습니다.

@SqlResultSetMapping(
name = "mapppinNativeQuery",  // same as resultSetMapping above in NativeQuery
   classes = {
      @ConstructorResult(
          targetClass = domain.io.MyMapping.class,
          columns = {
               @ColumnResult( name = "colA", type = Long.class),
               @ColumnResult( name = "colB", type = String.class)
          }
      )
   }
)

나중에 DAO에서 쿼리를 다음과 같이 참조 할 수 있습니다.

public List<domain.io.MyMapping> findAll() {
        return (namedQuery("domain.io.MyClass.myQuery").list());
    }

그게 다야.


답변

를 사용 Spring-jpa하면 답변과이 질문에 대한 보충 자료입니다. 결함이 있으면이를 수정하십시오. 나는 Object[]실질적인 요구를 충족시키기 위해 “포조에 매핑 결과”를 달성하기 위해 주로 세 가지 방법을 사용했다 .

  1. 내장 된 JPA이면 충분합니다.
  2. JPA 내장 메소드로는 충분하지 않지만 사용자 정의 sqlEntity충분합니다.
  3. 전자 2는 실패했으며을 사용해야합니다 nativeQuery. 예제는 다음과 같습니다. 예상되는 포조 :

    public class Antistealingdto {
    
        private String secretKey;
    
        private Integer successRate;
    
        // GETTERs AND SETTERs
    
        public Antistealingdto(String secretKey, Integer successRate) {
            this.secretKey = secretKey;
            this.successRate = successRate;
        }
    }

방법 1 : pojo를 인터페이스로 변경하십시오.

public interface Antistealingdto {
    String getSecretKey();
    Integer getSuccessRate();
}

그리고 저장소 :

interface AntiStealingRepository extends CrudRepository<Antistealing, Long> {
    Antistealingdto findById(Long id);
}

방법 2 : 리포지토리 :

@Query("select new AntistealingDTO(secretKey, successRate) from Antistealing where ....")
Antistealing whatevernamehere(conditions);

참고 : POJO 생성자의 매개 변수 순서는 POJO 정의와 sql에서 동일해야합니다.

방법 3 : 사용 @SqlResultSetMapping하고 @NamedNativeQuery있는 Entity에드윈 Dalorzo의 대답의 예로서.

처음 두 가지 방법은 사용자 정의 변환기와 같은 많은 중간 처리기를 호출합니다. 예를 들어, AntiStealing정의 secretKey가 유지되기 전에 컨버터이를 암호화하는 삽입된다. 이로 인해 처음 2 개의 메소드가 변환 된 결과를 반환합니다 secretKey. 방법 3은 변환기를 극복하고 리턴 secretKey된 것은 저장된 것과 동일합니다 (암호화 된 것).


답변

비 엔티티 (Beans / POJO)에 결과를 지정하기 위해 랩핑 해제 프로 시저를 수행 할 수 있습니다. 절차는 다음과 같습니다.

List<JobDTO> dtoList = entityManager.createNativeQuery(sql)
        .setParameter("userId", userId)
        .unwrap(org.hibernate.Query.class).setResultTransformer(Transformers.aliasToBean(JobDTO.class)).list();

사용법은 JPA-Hibernate 구현에 사용됩니다.


답변

먼저 다음 주석을 선언하십시오.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeQueryResultEntity {
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeQueryResultColumn {
    int index();
}

그런 다음 POJO에 다음과 같이 주석을 답니다.

@NativeQueryResultEntity
public class ClassX {
    @NativeQueryResultColumn(index=0)
    private String a;

    @NativeQueryResultColumn(index=1)
    private String b;
}

그런 다음 주석 프로세서를 작성하십시오.

public class NativeQueryResultsMapper {

    private static Logger log = LoggerFactory.getLogger(NativeQueryResultsMapper.class);

    public static <T> List<T> map(List<Object[]> objectArrayList, Class<T> genericType) {
        List<T> ret = new ArrayList<T>();
        List<Field> mappingFields = getNativeQueryResultColumnAnnotatedFields(genericType);
        try {
            for (Object[] objectArr : objectArrayList) {
                T t = genericType.newInstance();
                for (int i = 0; i < objectArr.length; i++) {
                    BeanUtils.setProperty(t, mappingFields.get(i).getName(), objectArr[i]);
                }
                ret.add(t);
            }
        } catch (InstantiationException ie) {
            log.debug("Cannot instantiate: ", ie);
            ret.clear();
        } catch (IllegalAccessException iae) {
            log.debug("Illegal access: ", iae);
            ret.clear();
        } catch (InvocationTargetException ite) {
            log.debug("Cannot invoke method: ", ite);
            ret.clear();
        }
        return ret;
    }

    // Get ordered list of fields
    private static <T> List<Field> getNativeQueryResultColumnAnnotatedFields(Class<T> genericType) {
        Field[] fields = genericType.getDeclaredFields();
        List<Field> orderedFields = Arrays.asList(new Field[fields.length]);
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].isAnnotationPresent(NativeQueryResultColumn.class)) {
                NativeQueryResultColumn nqrc = fields[i].getAnnotation(NativeQueryResultColumn.class);
                orderedFields.set(nqrc.index(), fields[i]);
            }
        }
        return orderedFields;
    }
}

다음과 같이 위의 프레임 워크를 사용하십시오.

String sql = "select a,b from x order by a";
Query q = entityManager.createNativeQuery(sql);

List<ClassX> results = NativeQueryResultsMapper.map(q.getResultList(), ClassX.class);


답변

가장 쉬운 방법은 투영법 을 사용하는 것 입니다. 쿼리 결과를 인터페이스에 직접 매핑 할 수 있으며 SqlResultSetMapping을 사용하는 것보다 구현하기가 더 쉽습니다.

예는 다음과 같습니다.

@Repository
public interface PeopleRepository extends JpaRepository<People, Long> {

    @Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
        "FROM people p INNER JOIN dream_people dp " +
        "ON p.id = dp.people_id " +
        "WHERE p.user_id = :userId " +
        "GROUP BY dp.people_id " +
        "ORDER BY p.name", nativeQuery = true)
    List<PeopleDTO> findByPeopleAndCountByUserId(@Param("userId") Long userId);

    @Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
        "FROM people p INNER JOIN dream_people dp " +
        "ON p.id = dp.people_id " +
        "WHERE p.user_id = :userId " +
        "GROUP BY dp.people_id " +
        "ORDER BY p.name", nativeQuery = true)
    Page<PeopleDTO> findByPeopleAndCountByUserId(@Param("userId") Long userId, Pageable pageable);

}



// Interface to which result is projected
public interface PeopleDTO {

    String getName();

    Long getCount();

}

투영 된 인터페이스의 필드는이 엔티티의 필드와 일치해야합니다. 그렇지 않으면 필드 매핑이 중단 될 수 있습니다.

또한 SELECT table.column표기법 을 사용하는 경우 항상 예제와 같이 엔티티의 이름과 일치하는 별명을 정의하십시오.