JPA:如何将本机查询结果集转换为POJO类集合

我在我的项目中使用JPA。

我来到一个查询,我需要在五个表上进行连接操作。 所以我创build了一个返回五个字段的本地查询。

现在我想将结果对象转换为包含相同的五个string的Java POJO类。

在JPA中有没有办法直接把结果转换成POJO对象列表?

我来到以下解决scheme..

@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 ,允许您将来自您的本机查询的任何回报映射到实体中 或自定义类

编辑 JPA 1.0不允许映射到非实体类。 只有在JPA 2.1中, ConstructorResult已被添加到映射返回值java类。

此外,对于OP的计数问题,应该足以使用一个ColumnResult定义结果集映射

我find了一些解决scheme。

使用映射实体(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到来之前)的一个解决scheme是使用一点思考来映射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); } } 

这个方法基本上需要一个元组数组(通过本地查询返回),并通过查找具有相同数量的字段和相同types的构造函数,将其映射到提供的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); 

JPA 2.1与@SqlResultSetMapping

随着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"/> <column name="age"/> </constructor-result> </sql-result-set-mapping> 

而这些都是我所知道的解决scheme。 如果我们可以使用JPA 2.1,最后两个是理想的方法。

首先声明以下注释:

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

是的,使用JPA 2.1很容易。 你有非常有用的注释。 他们简化你的生活。

首先声明你的本地查询,然后声明你的结果集映射。 写你的POJO类来引用(为简洁起见,这里不包括)。 最后但并非最不重要的:在DAO中创build一个方法(例如)来调用查询。 这适用于我在一个dropwizard(1.0.0)应用程序。

首先在一个实体类中声明一个本地查询:

 @NamedNativeQuery ( name = "domain.io.MyClass.myQuery" query = "Select a.colA, a.colB from Table a" resulSetMapping = "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[]到pojo”

  1. JPA内置方法就够了。
  2. JPA内置的方法是不够的,但与它的Entity定制的sql是足够的。
  3. 前2失败,我必须使用nativeQuery 。 这里是例子。 pojo预计:

     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 :在Entity使用@NamedNativeQuery@NamedNativeQuery作为Edwin Dalorzo的答案的例子。

前两种方法会调用许多中间处理程序,如定制转换器。 例如, AntiStealing定义了一个AntiStealing ,在它被保存之前,插入一个转换器来encryption它。 这将导致前2个方法返回一个转换后的secretKey ,这不是我想要的。 虽然方法3将克服转换器,并且返回的secretKey将与存储(encryption的)相同。

如果你正在使用Spring,你可以使用org.springframework.jdbc.core.RowMapper

这里是一个例子:

 public List query(String objectType, String namedQuery) { String rowMapper = objectType + "RowMapper"; // then by reflection you can instantiate and use. The RowMapper classes need to follow the naming specific convention to follow such implementation. } 

由于其他人已经提到了所有可能的解决scheme,我正在分享我的解决方法。

在我与Postgres 9.4情况下,当与Jackson

 //Convert it to named native query. List<String> list = em.createNativeQuery("select cast(array_to_json(array_agg(row_to_json(a))) as text) from myschema.actors a") .getResultList(); List<ActorProxy> map = new ObjectMapper().readValue(list.get(0), new TypeReference<List<ActorProxy>>() {}); 

我相信你可以find相同的其他数据库。

也FYI, JPA 2.0原生查询结果作为地图

使用DTO Design Pattern 。 它被用在EJB 2.0 。 实体是容器pipe理的。 DTO Design Pattern用于解决这个问题。 但是,当应用程序是分别开发Server SideClient Side时,现在可能会使用它。 DTO用于Server side不想传递/返回具有注释到Client Side Entity

DTO例子:

PersonEntity.java

 @Entity public class PersonEntity { @Id private String id; private String address; public PersonEntity(){ } public PersonEntity(String id, String address) { this.id = id; this.address = address; } //getter and setter } 

PersonDTO.java

 public class PersonDTO { private String id; private String address; public PersonDTO() { } public PersonDTO(String id, String address) { this.id = id; this.address = address; } //getter and setter } 

DTOBuilder.java

 public class DTOBuilder() { public static PersonDTO buildPersonDTO(PersonEntity person) { return new PersonDTO(person.getId(). person.getAddress()); } } 

EntityBuilder.java < – 它需要

 public class EntityBuilder() { public static PersonEntity buildPersonEntity(PersonDTO person) { return new PersonEntity(person.getId(). person.getAddress()); } } 

请参阅下面的示例,使用POJO作为伪实体从本机查询中检索结果,而不使用复杂的SqlResultSetMapping。 只需要两个注释,一个纯粹的@Enity和一个虚拟@Id在你的POJO。 @Id可以用于你select的任何字段,一个@Id字段可以有重复键但不是空值。

由于@Enity没有映射到任何物理表,所以这个POJO被称为伪实体。

环境:eclipselink 2.5.0-RC1,jpa-2.1.0,mysql-connector-java-5.1.14

你可以在这里下载完整的maven项目

本机查询基于mysql示例employees db http://dev.mysql.com/doc/employee/en/employees-installation.html

persistence.xml中

 <?xml version="1.0" encoding="UTF-8"?><persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="jpa-mysql" transaction-type="RESOURCE_LOCAL"> <class>org.moonwave.jpa.model.pojo.Employee</class> <properties> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/employees" /> <property name="javax.persistence.jdbc.user" value="user" /> <property name="javax.persistence.jdbc.password" value="***" /> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> </properties> </persistence-unit> 

Employee.java

 package org.moonwave.jpa.model.pojo; @Entity public class Employee { @Id protected Long empNo; protected String firstName; protected String lastName; protected String title; public Long getEmpNo() { return empNo; } public void setEmpNo(Long empNo) { this.empNo = empNo; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("empNo: ").append(empNo); sb.append(", firstName: ").append(firstName); sb.append(", lastName: ").append(lastName); sb.append(", title: ").append(title); return sb.toString(); } } 

EmployeeNativeQuery.java

 public class EmployeeNativeQuery { private EntityManager em; private EntityManagerFactory emf; public void setUp() throws Exception { emf=Persistence.createEntityManagerFactory("jpa-mysql"); em=emf.createEntityManager(); } public void tearDown()throws Exception { em.close(); emf.close(); } @SuppressWarnings("unchecked") public void query() { Query query = em.createNativeQuery("select e.emp_no as empNo, e.first_name as firstName, e.last_name as lastName," + "t.title from employees e join titles t on e.emp_no = t.emp_no", Employee.class); query.setMaxResults(30); List<Employee> list = (List<Employee>) query.getResultList(); int i = 0; for (Object emp : list) { System.out.println(++i + ": " + emp.toString()); } } public static void main( String[] args ) { EmployeeNativeQuery test = new EmployeeNativeQuery(); try { test.setUp(); test.query(); test.tearDown(); } catch (Exception e) { System.out.println(e); } } } 

将SQL查询转换为POJO类集合的简单方法,

 Query query = getCurrentSession().createSQLQuery(sqlQuery).addEntity(Actors.class); List<Actors> list = (List<Actors>) query.list(); return list;