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”
- JPA内置方法就够了。
- JPA内置的方法是不够的,但与它的
Entity
定制的sql
是足够的。 -
前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 Side
和Client 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;