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


使用DTO设计模式。它在EJB 2.0中使用。实体由容器管理。采用DTO设计模式来解决这一问题。 但是,当应用程序分别开发服务器端和客户端时,现在可能会使用它。DTO用于服务器端不想将带有注释的实体传递/返回给客户端。

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

java <——可能需要

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

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

EDIT JPA 1.0不允许映射到非实体类。只有在JPA 2.1中,才添加了一个ConstructorResult来映射java类的返回值。

同样,对于OP获取计数的问题,定义一个带有单个ColumnResult的结果集映射就足够了


我已经找到了一些解决方案。

使用映射实体(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();

但在这种情况下,绝地,必须是一个映射的实体职业。

避免此处未检查警告的另一种方法是使用命名本机查询。如果我们在实体中声明本机查询

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

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

当然,在这种情况下,绝地不需要是一个映射实体。它可以是一个普通的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,最后两个是理想的方法。


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

因为@Enity不映射到任何物理表,所以这个POJO被称为伪实体。

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

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

本机查询是基于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);
    }
}
}

首先声明以下注释:

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

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

是的,使用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

下面你可以添加resultset映射声明:

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

就是这样。


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

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

如果您使用Spring-jpa,这是对答案和这个问题的补充。如有任何瑕疵,请改正。根据我的实际需求,我主要使用了三种方法来实现“map result Object[] to a pojo”:

JPA内置方法就足够了。 JPA内置的方法是不够的,但一个自定义的sql与它的实体是足够的。 前2失败,我必须使用一个nativeQuery。 下面是一些例子。 pojo期待着: 公共类 private String; private Integer successRate; // getter和setter public Antistealingdto(String secretKey, Integer successRate) { 这一点。secretKey = secretKey; 这一点。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: 在Edwin Dalorzo的回答中,以实体中的@SqlResultSetMapping和@NamedNativeQuery为例。

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


既然其他人已经提到了所有可能的解决方案,我将分享我的变通方案。

在我使用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本机查询结果为映射


可以执行展开过程,将结果分配给非实体(即Beans/POJO)。操作步骤如下。

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

该用法用于JPA-Hibernate实现。


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

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

在我的情况下,我必须使用在其他地方的字符串中定义的SQL部分,所以我不能只使用NamedNativeQuery。


使用Hibernate:

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

使用Hibernate:

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

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

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


在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;}

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

   //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();

在上面的回答中,我尝试了很多事情。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;
    }

}

在这种情况下,使用“数据库视图”这样的实体也就是不可变实体是非常容易的。

正常的实体

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

如果查询不是太复杂,您可以这样做。在我的情况下,我需要使用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());
  }

也许有更干净的方法。


如果希望将自定义查询结果直接映射到实体,而不需要编写任何映射代码,可以尝试这种方式。根据我的经验,这是最方便的方法,但缺点是失去了hibernate ddl-auto的好处:

Disable hibernate validation by removing the hibernate.ddl-auto. If not doing this, hibernate can complain about missing table in database. Create a pojo with @Entity for the custom result set without table mapping, something like: @Getter @Setter @Entity public class MyCustomeResult implements Serializable { @Id private Long id; @Column(name = "name") private String name; } In repository, use the entity to map directly from query.getResultList() public List<MyCustomeResult> findByExampleCustomQuery(Long test) { String sql = "select id, name from examples where id =:test"; Query query = entityManager.createNativeQuery(sql, MyCustomeResult.class); return query.setParameter("test", test).getResultList(); }