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

Gun*_*hah 156 java jpa

我在我的项目中使用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  
})  
Run Code Online (Sandbox Code Playgroud)

现在在resultClass中,我们是否需要提供一个实际的JPA实体类?或者我们可以将它转换为包含相同列名的任何JAVA POJO类吗?

Edw*_*rzo 190

我找到了几个解决方案.

使用映射实体(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();
Run Code Online (Sandbox Code Playgroud)

但在这种情况下,Jedi必须是映射的实体类.

在此处避免未经检查的警告的替代方法是使用命名的本机查询.因此,如果我们在实体中声明本机查询

@NamedNativeQuery(
 name="jedisQry", 
 query = "SELECT name,age FROM jedis_table", 
 resultClass = Jedi.class)
Run Code Online (Sandbox Code Playgroud)

然后,我们可以简单地做:

TypedQuery<Jedi> query = em.createNamedQuery("jedisQry", Jedi.class);
List<Jedi> items = query.getResultList();
Run Code Online (Sandbox Code Playgroud)

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

手动映射

我尝试了一些解决方案(在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);
   }
}
Run Code Online (Sandbox Code Playgroud)

此方法基本上采用元组数组(由本机查询返回),并通过查找具有相同字段数和相同类型的构造函数将其映射到提供的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);
}
Run Code Online (Sandbox Code Playgroud)

我们可以简单地使用以下技术:

Query query = em.createNativeQuery("SELECT name,age FROM jedis_table");
List<Jedi> jedis = getResultList(query, Jedi.class);
Run Code Online (Sandbox Code Playgroud)

带有@SqlResultSetMapping的JPA 2.1

随着JPA 2.1的到来,我们可以使用@SqlResultSetMapping注释来解决问题.

我们需要在实体的某处声明一个结果集映射:

@SqlResultSetMapping(name="JediResult", classes = {
    @ConstructorResult(targetClass = Jedi.class, 
    columns = {@ColumnResult(name="name"), @ColumnResult(name="age")})
})
Run Code Online (Sandbox Code Playgroud)

然后我们简单地做:

Query query = em.createNativeQuery("SELECT name,age FROM jedis_table", "JediResult");
@SuppressWarnings("unchecked")
List<Jedi> samples = query.getResultList();
Run Code Online (Sandbox Code Playgroud)

当然,在这种情况下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>
Run Code Online (Sandbox Code Playgroud)

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

  • 如果将JPA 2.1与`@ SqlResultSetMapping一起使用',可能值得注意的是``Jedi`类将需要一个全arg构造函数,并且`@ ColumnResult`注释可能需要将`type`属性添加到可能不隐含的转换中(我需要为某些列添加`type = ZonedDateTime.class`. (4认同)
  • 如果我能给你更多的功劳,我愿意. (3认同)
  • “实体中的某个地方”是什么意思?我的 Pojo 不是 JPA 实体,我不能在 POJO 中声明 @SqlResultSetMapping 吗?我对 JPA 2.1 解决方案感兴趣。请说得更精确一点。 (2认同)
  • @Alboz必须将`@ SqlResultSetMapping`放在一个实体中,因为这就是JPA将从中读取元数据的内容.您不能指望JPA检查您的POJO.放置映射的实体无关紧要,也许与您的POJO结果更相关.或者,映射可以用XML表示,以避免与完全不相关的实体耦合. (2认同)

Den*_*kiy 92

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

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

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

  • 谢谢回复。在这里,我们将结果与带有“@EntityResult”和“@FieldResult”注释的 java 实体类映射到实体。没关系。但在这里我需要更清楚。是否要求我们与结果映射的类必须是 JPA 实体类?或者我们可以使用一个简单的 POJO 类,它不是实体购买,它具有所有必需的变量作为结果集中的列。 (2认同)
  • 当我尝试这个时,我得到一个错误,该类不是已知实体。我最终使用了这种方法 http://stackoverflow.com/questions/5024533/how-to-map-join-query-to-non-entity-class-in-jpa?rq=1 而不是尝试使用本机询问。 (2认同)
  • @EdwinDalorzo:这对于jpa 1.0来说是正确的.在jpa 2.1中,他们添加了`ConstructorResult`作为`SqlResultSetMapping`的参数之一,允许使用pojo和构造函数中设置的所有字段.我会更新答案. (2认同)
  • 我看到另一个痛苦的事实:ConstructorResult可以映射到POJO ..但是ConstructorResult本身必须在Entity类中,所以实体你无法避免...因此更大的事实:你需要一些结果而不关心到主键 - 你还必须在实体中拥有@Id ......太荒谬吧? (2认同)

小智 11

是的,使用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
Run Code Online (Sandbox Code Playgroud)

在下面,您可以添加结果集映射声明:

@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)
          }
      )
   } 
)
Run Code Online (Sandbox Code Playgroud)

稍后在DAO中,您可以将查询称为

public List<domain.io.MyMapping> findAll() {
        return (namedQuery("domain.io.MyClass.myQuery").list());
    }
Run Code Online (Sandbox Code Playgroud)

而已.

  • 我看到另一个痛苦的事实:NamedNativeQuery和SqlResultSetMapping必须在@Entity类中 (3认同)

Tii*_*ina 8

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

  1. JPA内置方法就足够了.
  2. JPA内置方法是不够的,但sql用它定制Entity就足够了.
  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;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

方法1:将pojo更改为接口:

public interface Antistealingdto {
    String getSecretKey();
    Integer getSuccessRate();
}
Run Code Online (Sandbox Code Playgroud)

和存储库:

interface AntiStealingRepository extends CrudRepository<Antistealing, Long> {
    Antistealingdto findById(Long id);
}
Run Code Online (Sandbox Code Playgroud)

方法2:存储库:

@Query("select new AntistealingDTO(secretKey, successRate) from Antistealing where ....")
Antistealing whatevernamehere(conditions);
Run Code Online (Sandbox Code Playgroud)

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

方法3:使用@SqlResultSetMapping@NamedNativeQueryEntity在埃德温Dalorzo的答案的例子.

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


Tha*_*thu 7

最简单的方法是使用 so投影。可以将查询结果直接映射到接口上,比使用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();

}
Run Code Online (Sandbox Code Playgroud)

来自投影接口的字段必须与该实体中的字段匹配。否则字段映射可能会中断。

此外,如果您使用SELECT table.column表示法,请始终定义与实体名称匹配的别名,如示例所示。

  • 本机查询和预测不能很好地结合在一起。 (2认同)

zaw*_*tut 6

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

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

该用法用于JPA-Hibernate实现。

  • org.hibernate.Query.class).setResultTransformer 显示已弃用。 (3认同)

小智 5

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

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;}
Run Code Online (Sandbox Code Playgroud)