【问题标题】:JPA : How to convert a native query result set to POJO class collectionJPA:如何将原生查询结果集转换为 POJO 类集合
【发布时间】:2012-10-12 07:56:26
【问题描述】:

我在我的项目中使用 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 类?

【问题讨论】:

标签: java jpa


【解决方案1】:

如果查询不太复杂,您可以这样做。在我的情况下,我需要使用 H2 FT_Search 结果查询来进行另一个查询。

var ftSearchQuery = "SELECT * FROM FT_SEARCH(\'something\', 0, 0)";
List<Object[]> results = query.getResultList();
List<Model> models = new ArrayList<>();
for (Object[] result : results) {
    var newQuery = "SELECT * FROM " + (String) result[0];
    models.addAll(entityManager.createNativeQuery(newQuery, Model.class).getResultList());
  }

可能有更清洁的方法来做到这一点。

【讨论】:

    【解决方案2】:

    在这种情况下,使用实体(又名不可变实体)之类的“数据库视图”非常容易。

    普通实体

    @Entity
    @Table(name = "people")
    data class Person(
      @Id
      val id: Long = -1,
      val firstName: String = "",
      val lastName: String? = null
    )
    

    查看类似实体

    @Entity
    @Immutable
    @Subselect("""
    select
        p.id,
        concat(p.first_name, ' ', p.last_name) as full_name
    from people p
    """)
    data class PersonMin(
      @Id
      val id: Long,
      val fullName: String,
    )
    

    在任何存储库中,我们都可以创建查询函数/方法,如下所示:

    @Query(value = "select p from PersonMin p")
    fun findPeopleMinimum(pageable: Pageable): Page<PersonMin>
    

    【讨论】:

      【解决方案3】:

      我尝试了上述答案中提到的很多事情。 SQLmapper 对放置它的位置非常困惑。只有非托管 POJO 是个问题。我尝试了各种方法,其中一种简单的方法就是照常工作。我正在使用 hibernate-jpa-2.1。

      List<TestInfo> testInfoList = factory.createNativeQuery(QueryConstants.RUNNING_TEST_INFO_QUERY)
                          .getResultList();
      

      唯一需要注意的是 POJO 具有与查询相同的成员变量名称(全部小写)。显然,我什至不需要像在 JPQL 中处理 TypedQueries 那样将目标类连同查询一起告诉。

      TestInfo.class

      @Setter
      @Getter
      @NoArgsConstructor
      @ToString
      public class TestInfo {
      
          private String emailid;
          private Long testcount;
      
          public TestInfo(String emailId, Long testCount) {
              super();
              this.emailid = emailId;
              this.testcount = testCount;
          }
      
      }
      

      【讨论】:

        【解决方案4】:

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

        就是这样。

        【讨论】:

        • 不错的答案,但我认为您在第一个 @ColumnResult 注释之后错过了一个括号。
        • 代码有错误,但容易改正。例如:“resulSetMapping =”应该是“resultSetMapping =”
        • 我看到了另一个苦涩的事实:NamedNativeQuery 和 SqlResultSetMapping 必须在 @Entity 类中
        【解决方案5】:

        我们已经通过以下方式解决了这个问题:

           //Add actual table name here in Query
            final String sqlQuery = "Select a.* from ACTORS a"
            // add your entity manager here 
            Query query = entityManager.createNativeQuery(sqlQuery,Actors.class);
            //List contains the mapped entity data.
            List<Actors> list = (List<Actors>) query.getResultList();
        

        【讨论】:

          【解决方案6】:

          在 hibernate 中,您可以使用此代码轻松映射您的本机查询。

          private List < Map < String, Object >> getNativeQueryResultInMap() {
          String mapQueryStr = "SELECT * FROM AB_SERVICE three ";
          Query query = em.createNativeQuery(mapQueryStr);
          NativeQueryImpl nativeQuery = (NativeQueryImpl) query;
          nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
          List < Map < String, Object >> result = query.getResultList();
          for (Map map: result) {
              System.out.println("after request  ::: " + map);
          }
          return result;}
          

          【讨论】:

          • 这是一个非常好的和有用的解决方案。这个对我有用。使用这个 List > 结果我可以做任何事情。我可以在 POJO 中设置值或转换为 JSON 或任何东西。
          【解决方案7】:

          最简单的方法是使用 projections。它可以将查询结果直接映射到接口,比使用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 表示法,请始终定义与实体名称匹配的别名,如示例所示。

          【讨论】:

          • 原生查询和预测不能很好地结合在一起。
          • 我无法让字段映射正常工作 - 大多数值一直返回为 null
          【解决方案8】:

          使用 ResultSet 的旧样式

          @Transactional(readOnly=true)
          public void accessUser() {
              EntityManager em = this.getEntityManager();
              org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
              session.doWork(new Work() {
                  @Override
                  public void execute(Connection con) throws SQLException {
                      try (PreparedStatement stmt = con.prepareStatement(
                              "SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u")) {
                          ResultSet rs = stmt.executeQuery();
                          ResultSetMetaData rsmd = rs.getMetaData();
                          for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                              System.out.print(rsmd.getColumnName(i) + " (" + rsmd.getColumnTypeName(i) + ") / ");
                          }
                          System.out.println("");
                          while (rs.next()) {
                              System.out.println("Found username " + rs.getString("USERNAME") + " name " + rs.getString("NAME") + " email " + rs.getString("EMAIL") + " passe " + rs.getString("PASSE") + " email " + rs.getInt("LOGIN_TYPE"));
                          }
                      }
                  }
              });
          }
          

          【讨论】:

            【解决方案9】:

            使用休眠:

            @Transactional(readOnly=true)
            public void accessUser() {
                EntityManager em = repo.getEntityManager();
                org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
                org.hibernate.SQLQuery q = (org.hibernate.SQLQuery) session.createSQLQuery("SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u")
                    .addScalar("username", StringType.INSTANCE).addScalar("name", StringType.INSTANCE)
                    .addScalar("email", StringType.INSTANCE).addScalar("passe", StringType.INSTANCE)
                    .addScalar("loginType", IntegerType.INSTANCE)
                    .setResultTransformer(Transformers.aliasToBean(User2DTO.class));
            
                List<User2DTO> userList = q.list();
            }
            

            【讨论】:

              【解决方案10】:

              使用休眠:

              @Transactional(readOnly=true)
              public void accessUser() {
              EntityManager em = repo.getEntityManager();
                  org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
                  org.hibernate.SQLQuery q = (org.hibernate.SQLQuery) session.createSQLQuery("SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u").addScalar("username", StringType.INSTANCE).addScalar("name", StringType.INSTANCE).addScalar("email", StringType.INSTANCE).addScalar("passe", StringType.INSTANCE).addScalar("loginType", IntegerType.INSTANCE)
                      .setResultTransformer(Transformers.aliasToBean(User2DTO.class));
              
                  List<User2DTO> userList = q.list();
              }
              

              【讨论】:

                【解决方案11】:

                不确定这是否适合这里,但我有类似的问题,并为我找到了以下简单的解决方案/示例:

                private EntityManager entityManager;
                ...
                    final String sql = " SELECT * FROM STORE "; // select from the table STORE
                    final Query sqlQuery = entityManager.createNativeQuery(sql, Store.class);
                
                    @SuppressWarnings("unchecked")
                    List<Store> results = (List<Store>) sqlQuery.getResultList();
                

                在我的例子中,我不得不在其他地方使用 Strings 中定义的 SQL 部分,所以我不能只使用 NamedNativeQuery。

                【讨论】:

                • 一旦我们返回实体。没有什么花哨。问题是当您尝试将结果映射到非托管 POJO 时。
                【解决方案12】:

                我已经找到了几个解决方案。

                使用映射实体 (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,最后两个是理想的方式。

                【讨论】:

                • 旁注:我刚刚使用了带有 JPA2.1 依赖项的 JPA 2.0 方法,但它失败了。所以可能这不是向下兼容的......
                • 您所说的“实体中的某处”是什么意思?我的 Pojo 不是 JPA 实体我不能在我的 POJO 中声明 @SqlResultSetMapping 吗?我对 JPA 2.1 解决方案感兴趣。请准确一点。
                • @Alboz @SqlResultSetMapping 必须放在一个实体中,因为 JPA 将从中读取元数据。您不能指望 JPA 检查您的 POJO。您放置映射的实体是无关紧要的,也许是与您的 POJO 结果更相关的实体。或者,映射可以用 XML 表示,以避免与完全不相关的实体耦合。
                • 构造函数result是否可以使用具有嵌套类的类?
                • 如果将 JPA 2.1 与 @SqlResultSetMapping 一起使用,可能值得注意的是,Jedi 类将需要一个全参数构造函数,而 @ColumnResult 注释可能需要将 type 属性添加到转换这可能不是隐含的(我需要为某些列添加type = ZonedDateTime.class)。
                【解决方案13】:

                可以执行解包过程以将结果分配给非实体(即 Beans/POJO)。流程如下。

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

                用于 JPA-Hibernate 实现。

                【讨论】:

                • 请注意JobDTO 应该有默认构造函数。或者您可以基于AliasToBeanResultTransformer 实现实现自己的转换器。
                • 为什么要设置参数?
                • org.hibernate.Query.class).setResultTransformer 显示已弃用。
                【解决方案14】:

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

                在我使用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

                【讨论】:

                  【解决方案15】:

                  如果您使用Spring-jpa,这是对答案和这个问题的补充。如有不足请指正。我主要根据自己的实际需求使用了三种方法来实现“将结果Object[]映射到一个pojo”:

                  1. JPA 内置方法就足够了。
                  2. JPA 内置方法是不够的,但自定义的sql 及其Entity 就足够了。
                  3. 前两个失败了,我必须使用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;
                        }
                    }
                    

                  方法一:将pojo改成接口:

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

                  和存储库:

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

                  方法二: 存储库:

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

                  注意:POJO构造函数的参数顺序在POJO定义和sql中必须一致。

                  方法三: 使用Entity 中的@SqlResultSetMapping@NamedNativeQuery 作为Edwin Dalorzo 回答中的示例。

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

                  【讨论】:

                  • 方法 1 实际上不需要 Spring 并且可以使用纯 Hibernate。
                  • @MartinVysny 是的,M1 是 JPQL。任何实现 JPQL 的项目都应该支持它。这样一来,说不定M2也被广泛支持了?
                  【解决方案16】:

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

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

                  【讨论】:

                    【解决方案17】:

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

                    【讨论】:

                      【解决方案18】:

                      请参阅下面的示例,了解使用 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 示例员工数据库 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);
                          }
                      }
                      }
                      

                      【讨论】:

                      • 既然你的list 据称是Employee 的列表,为什么你的for-each 循环会迭代Object 类型?如果您将 for-each 循环编写为 for(Employee emp : list),那么您会发现您的答案是错误的,并且您的列表内容不是员工,并且您取消的警告旨在提醒您注意这个潜在的错误。跨度>
                      • @SuppressWarnings("unchecked") 用于抑制List&lt;Employee&gt; list = (List&lt;Employee&gt;) query.getResultList(); 的警告将for (Object emp : list) 更改为for (Employee emp : list) 更好,但如果保留为Object emp 则不会出现错误,因为list 是List&lt;Employee&gt;。我更改了 git 项目中的代码,但不是在这里让您的评论与原始帖子相关
                      • 问题是您的查询没有返回雇员列表,而是返回一个对象数组。您的抑制警告隐藏了这一点。在您尝试将其中任何一个转换为员工的那一刻,您将收到一个错误,即转换异常。
                      • 查看Query query = em.createNativeQuery("select * ...", Employee.class); 和persistence.xml,本机查询确实返回了一个Employee 列表。我刚刚签出并运行没有问题的项目。如果您在本地设置 mysql 示例员工数据库,您应该也可以运行该项目
                      • 哦,我明白你的意思了。但在这种情况下,您的答案不能满足问题,因为这是关于使用常规 POJO 作为目标对象,而您的答案是使用 Employee,我假设它是一个实体。不是吗?
                      【解决方案19】:

                      首先声明以下注解:

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

                      【讨论】:

                      • BeanUtils 在哪个包中?
                      【解决方案20】:

                      JPA 提供了一个SqlResultSetMapping,它允许您将从本地查询返回的任何内容映射到实体或自定义类

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

                      此外,对于 OP 的计数问题,使用单个 ColumnResult 定义结果集映射就足够了

                      【讨论】:

                      • 感谢您的回复。在这里,我们将我们的结果映射到带有“@EntityResult”和“@FieldResult”注释的 java 实体类的实体。没关系。但在这里我需要更清楚。我们与结果映射的类是否必须是 JPA 实体类?或者我们可以使用一个简单的 POJO 类,它不是实体购买,它具有所有必需的变量作为结果集中的列。
                      • @GunjanShah:最好的了解方法是尝试一下 :) 另外,一个实体就是同一个 pojo,只是带有一些注释。只要你不试图坚持下去,它就会一直保持下去。
                      • 当我尝试这个时,我得到一个错误,该类不是一个已知的实体。我最终使用了这种方法stackoverflow.com/questions/5024533/…,而不是尝试使用本机查询。
                      • @EdwinDalorzo:这对 jpa 1.0 来说是正确的。在 jpa 2.1 中,他们添加了ConstructorResult 作为SqlResultSetMapping 的参数之一,允许使用在构造函数中设置的所有字段的pojo。我会更新答案。
                      • 我看到了另一个苦涩的事实: ConstructorResult 可以映射到 POJO .. 但是 ConstructorResult 本身必须在 Entity 类中,所以 Entity 你无法避免......因此更大的硬事实:你需要一些不关心主键的结果 - 你仍然必须在实体中有@Id ...荒谬吗?
                      【解决方案21】:

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

                      【讨论】:

                      • 感谢您的回答。这里我不需要 DTO 模式。我的要求是不要向客户隐藏注释细节。所以我不需要在我的应用程序中再创建一个 POJO。我的要求是将结果集转换为 qa pojo,它不是 JAVA 实体,而是具有与结果集列相同的字段的简单 POJO 类。
                      猜你喜欢
                      • 2012-07-15
                      • 1970-01-01
                      • 2016-09-15
                      • 2015-05-18
                      • 2018-05-08
                      • 1970-01-01
                      • 1970-01-01
                      • 2020-03-06
                      相关资源
                      最近更新 更多