首页 文章

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

提问于
浏览
134

我在我的项目中使用JPA .

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

现在我想将结果对象转换为包含相同五个字符串的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类?

11 回答

  • 81

    如果您使用的是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.
    }
    
  • -1

    请参阅下面的示例,将POJO用作伪实体,以便在不使用复杂的SqlResultSetMapping的情况下从本机查询中检索结果 . 在你的POJO中只需要两个注释,一个裸@Enity和一个虚拟@Id . @Id可用于您选择的任何字段,@ Id字段可以有重复键但不能为空值 .

    由于@Enity不映射到任何物理表,因此该POJO称为伪实体 .

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

    你可以下载完整的maven项目here

    本机查询基于mysql示例员工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);
        }
    }
    }
    
  • -1

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

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

    我找到了几个解决方案 .

    Using Mapped Entities (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();
    

    这更安全,但我们仍然限制使用映射实体 .

    Manual Mapping

    我尝试了一些解决方案(在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);
    

    JPA 2.1 with @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 .

    Using XML Mapping

    我是其中一个发现在我的实体中添加所有这些非常具有侵入性的东西之一,我特别不喜欢实体中命名查询的定义,所以或者我在 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,最后两个是理想的方式 .

  • 9

    首先声明以下注释:

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

    使用 DTO Design Pattern . 它用于 EJB 2.0 . 实体是容器管理的 . DTO Design Pattern 用于解决此问题 . 但是,当应用程序分别开发 Server SideClient Side 时,它现在可能会被使用 . DTOServer side 不希望将带注释的 Entity 传递/返回 Client Side 时使用 .

    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());
        }
    }
    
  • 3

    JPA提供SqlResultSetMapping,允许您将原始查询的任何返回映射到实体或自定义类 .

    EDIT JPA 1.0不允许映射到非实体类 . 仅在JPA 2.1中添加了ConstructorResult以将返回值映射到java类 .

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

  • 0

    如果你使用 Spring-jpa ,这是答案和这个问题的补充 . 如果有任何缺陷请更正 . 我主要使用三种方法根据我遇到的实际需要实现“映射结果 Object[] 到pojo”:

    • JPA内置方法就足够了 .

    • JPA内置方法是不够的,但定制 sql 及其 Entity 就足够了 .

    • 前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;
        }
    }
    

    Method 1 :将pojo更改为界面:

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

    和存储库:

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

    Method 2 :存储库:

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

    注意:POJO构造函数的参数序列在POJO定义和sql中必须相同 .

    Method 3 :在 Entity 中使用 @SqlResultSetMapping@NamedNativeQuery 作为Edwin Dalorzo的例子回答 .

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

  • 166

    是的,使用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());
        }
    

    而已 .

  • 7

    可以执行展开过程以将结果分配给非实体(即Beans / POJO) . 程序如下 .

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

    用法适用于JPA-Hibernate实现 .

  • 5

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

    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>>() {});
    

    我相信你可以找到其他数据库 .

    另外,仅供参考,JPA 2.0 native query results as map

相关问题