프로젝트에서 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[]
실질적인 요구를 충족시키기 위해 “포조에 매핑 결과”를 달성하기 위해 주로 세 가지 방법을 사용했다 .
- 내장 된 JPA이면 충분합니다.
- JPA 내장 메소드로는 충분하지 않지만 사용자 정의
sql
로Entity
충분합니다. -
전자 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
표기법 을 사용하는 경우 항상 예제와 같이 엔티티의 이름과 일치하는 별명을 정의하십시오.