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.
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"));
}
}
}
});
}
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);
}
}
}
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.
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);
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:
sql
with its Entity
are enough.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).
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.