JPA : How to convert a native query result set to POJO class collection

后端 未结 21 1500
孤街浪徒
孤街浪徒 2020-11-22 09:23

I am using JPA in my project.

I came to a query in which I need to make join operation on five tables. So I created a native query which returns five fields.

相关标签:
21条回答
  • 2020-11-22 09:27

    Old style using 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"));
                    }
                }
            }
        });
    }
    
    0 讨论(0)
  • 2020-11-22 09:27

    See example below for using a POJO as pseudo entity to retrieve result from native query without using complex SqlResultSetMapping. Just need two annotations, a bare @Enity and a dummy @Id in your POJO. @Id can be used on any field of your choice, an @Id field can have duplicate keys but not null values.

    Since @Enity does not map to any physical table, so this POJO is called a pseudo entity.

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

    You can download complete maven project here

    Native query is based on mysql sample 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);
        }
    }
    }
    
    0 讨论(0)
  • 2020-11-22 09:29

    Unwrap procedure can be performed to assign results to non-entity(which is Beans/POJO). The procedure is as following.

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

    The usage is for JPA-Hibernate implementation.

    0 讨论(0)
  • 2020-11-22 09:30

    First declare following annotations:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NativeQueryResultEntity {
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NativeQueryResultColumn {
        int index();
    }
    

    Then annotate your POJO as follows:

    @NativeQueryResultEntity
    public class ClassX {
        @NativeQueryResultColumn(index=0)
        private String a;
    
        @NativeQueryResultColumn(index=1)
        private String b;
    }
    

    Then write annotation processor:

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

    Use above framework as follows:

    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 讨论(0)
  • 2020-11-22 09:31

    If you use Spring-jpa, this is a supplement to the answers and this question. Please correct this if any flaws. I have mainly used three methods to achieve "mapping result Object[] to a pojo" based on what practical need I meet:

    1. JPA built in method is enough.
    2. JPA built in method is not enough, but a customized sql with its Entity are enough.
    3. The former 2 failed, and I have to use a nativeQuery. Here are the examples. The pojo expected:

      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: Change the pojo into an interface:

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

    And repository:

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

    Method 2: Repository:

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

    Note: parameter sequence of POJO constructor must be identical in both POJO definition and sql.

    Method 3: Use @SqlResultSetMapping and @NamedNativeQuery in Entity as the example in Edwin Dalorzo's answer.

    The first two methods would call many in-the-middle handlers, like customized converters. For example, AntiStealing defines a secretKey, before it is persisted, a converter is inserted to encrypt it. This would result in the first 2 methods returning a converted back secretKey which is not what I want. While the method 3 would overcome the converter, and returned secretKey would be the same as it is stored (an encrypted one).

    0 讨论(0)
  • 2020-11-22 09:31

    Not sure if this fits here, but I had similar question and found following simple solution/example for me:

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

    In my case I had to use SQL parts defined in Strings somewhere else, so I could not just use NamedNativeQuery.

    0 讨论(0)
提交回复
热议问题