1 2: select (table.*)/(all column) is OK
String sql = "select t_student.* from t_student";
//String sql = "select t_student.id,t_student.name,... from t_student"; //select all column
SQLQuery query = session.createSQLQuery(sql);
query.addEntity(Student.class);//or query.addEntity("alias", Student.class);
//query.list();[Student#..., Student#..., Student#...]
query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); //or other transformer
query.list(); //[{Student(or alias)=Student#...},{Student=Student#...}]
3: select some column(not all of), is Error
String sql = "select t_student.id,t_student.name.t_student.sex from t_student";
SQLQuery query = session.createSQLQuery(sql);
query.addEntity(Student.class);
query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
query.list(); //Exception:invalid column/no column
I want "3" to work ok, and let the result can be mapped to Student.class.
Like: Student[id=?, name=?, sex=?, (other field are null/default)]
I've no idea for this error, help me please!
You can go further and add
.setResultTransformer(Transformers.aliasToBean(YOUR_DTO.class));
and automatically map it to your custom dto object, see also Returning non-managed entities.
For example:
public List<MessageExtDto> getMessagesForProfile2(Long userProfileId) {
Query query = getSession().createSQLQuery(" "
+ " select a.*, b.* "
+ " from messageVO AS a "
+ " INNER JOIN ( SELECT max(id) AS id, count(*) AS count FROM messageVO GROUP BY messageConversation_id) as b ON a.id = b.id "
+ " where a.id > 0 "
+ " ")
.addScalar("id", new LongType())
.addScalar("message", new StringType())
......... your mappings
.setResultTransformer(Transformers.aliasToBean(MessageExtDto.class));
List<MessageExtDto> list = query.list();
return list;
}
I want "3" to work ok, and let the result can be mapped to Student.class
That's possible using
Query createNativeQuery(String sqlString, String resultSetMapping)
In the second argument you could tell the name of the result mapping. For example:
1) Let's consider a Student entity, the magic is going to be in the SqlResultSetMapping annotation:
import javax.persistence.Entity;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.Table;
#Entity
#Table(name = "student")
#SqlResultSetMapping(name = "STUDENT_MAPPING", classes = {#ConstructorResult(
targetClass = Student.class, columns = {
#ColumnResult(name = "name"),
#ColumnResult(name = "address")
})})
public class Student implements Serializable {
private String name;
private String address;
/* Constructor for the result mapping; the key is the order of the args*/
public Student(String aName, String anAddress) {
this.name = aName;
this.address = anAddress;
}
// the rest of the entity
}
2) Now you can execute a query which results will be mapped by STUDENT_MAPPING logic:
String query = "SELECT s FROM student s";
String mapping = "STUDENT_MAPPING";
Query query = myEntityManager.createNativeQuery(query, mapping);
#SuppressWarnings("unchecked")
List<Student> students = query.getResultList();
for (Student s : students) {
s.getName(); // ...
}
Note: I think it's not possible to avoid the unchecked warning.
There is only two ways.
You can use 1st or 2nd snippet. According to Hibernate documentation you must prefer 2nd.
You can get just a list of object arrays, like this:
String sql = "select name, sex from t_student";
SQLQuery query = session.createSQLQuery(sql);
query.addScalar("name", StringType.INSTANCE);
query.addScalar("sex", StringType.INSTANCE);
query.list();
I had same problem on HQL Query. I solved the problem by changing the transformer.
The problem caused the code written to transform as Map. But it is not suitable for Alias Bean. You can see the error code at below. The code written to cast result as map and put new field to the map.
Class : org.hibernate.property.access.internal.PropertyAccessMapImpl.SetterImpl
m
Method: set
#Override
#SuppressWarnings("unchecked")
public void set(Object target, Object value, SessionFactoryImplementor factory) {
( (Map) target ).put( propertyName, value );
}
I solved the problem to duplicate the transformer and change the code.
You can see the code in the project.
Link : https://github.com/robeio/robe/blob/DW1.0-migration/robe-hibernate/src/main/java/io/robe/hibernate/criteria/impl/hql/AliasToBeanResultTransformer.java
Class:
import java.lang.reflect.Field;
import java.util.Map;
import io.robe.hibernate.criteria.api.query.SearchQuery;
import org.hibernate.HibernateException;
import org.hibernate.transform.AliasedTupleSubsetResultTransformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AliasToBeanResultTransformer extends AliasedTupleSubsetResultTransformer {
private static final Logger LOGGER = LoggerFactory.getLogger(AliasToBeanResultTransformer.class);
private final Class resultClass;
// Holds fields of Transform Class as Map. Key is name of field.
private Map<String, Field> fieldMap;
public AliasToBeanResultTransformer(Class resultClass) {
if ( resultClass == null ) {
throw new IllegalArgumentException( "resultClass cannot be null" );
}
fieldMap = SearchQuery.CacheFields.getCachedFields(resultClass);
this.resultClass = resultClass;
}
#Override
public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
return false;
}
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
Object result;
try {
result = resultClass.newInstance();
for ( int i = 0; i < aliases.length; i++ ) {
String name = aliases[i];
Field field = fieldMap.get(name);
if(field == null) {
LOGGER.error(name + " field not found in " + resultClass.getName() + " class ! ");
continue;
}
field.set(result, tuple[i]);
}
}
catch ( InstantiationException e ) {
throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
} catch ( IllegalAccessException e ) {
throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
}
return result;
}
}
After created new Transformer You can use like below.
query.setResultTransformer(new AliasToBeanResultTransformer(YOUR_DTO.class));
You can mapped it automatically:
Your Model Student.java
public class Student {
private String name;
private String address;
}
Repository
String sql = "Select * from student";
Query query = em.createNativeQuery(sql, Student.class);
List ls = query.getResultList();
so it will automatically mapped the result with the Student class
Related
I'm using EclipseLink to run some Native SQL. I need to return the data into a POJO. I followed the instructions at EclipseLink Docs, but I receive the error Missing descriptor for [Class]
The query columns have been named to match the member variables of the POJO. Do I need to do some additional mapping?
POJO:
public class AnnouncementRecipientsFlattenedDTO {
private BigDecimal announcementId;
private String recipientAddress;
private String type;
public AnnouncementRecipientsFlattenedDTO() {
super();
}
public AnnouncementRecipientsFlattenedDTO(BigDecimal announcementId, String recipientAddress, String type) {
super();
this.announcementId = announcementId;
this.recipientAddress = recipientAddress;
this.type = type;
}
... Getters/Setters
Entity Manager call:
public List<AnnouncementRecipientsFlattenedDTO> getNormalizedRecipientsForAnnouncement(int announcementId) {
Query query = em.createNamedQuery(AnnouncementDeliveryLog.FIND_NORMALIZED_RECIPIENTS_FOR_ANNOUNCEMENT, AnnouncementRecipientsFlattenedDTO.class);
query.setParameter(1, announcementId);
return query.getResultList();
}
I found out you can put the results of a Native Query execution into a List of Arrays that hold Objects. Then one can iterate over the list and Array elements and build the desired Entity objects.
List<Object[]> rawResultList;
Query query =
em.createNamedQuery(AnnouncementDeliveryLog.FIND_NORMALIZED_RECIPIENTS_FOR_ANNOUNCEMENT);
rawResultList = query.getResultList();
for (Object[] resultElement : rawResultList) {
AnnouncementDeliveryLog adl = new AnnouncementDeliveryLog(getAnnouncementById(announcementId), (String)resultElement[1], (String)resultElement[2], "TO_SEND");
persistAnnouncementDeliveryLog(adl);
}
You can only use native SQL queries with a class if the class is mapped. You need to define the AnnouncementRecipientsFlattenedDTO class as an #Entity.
Otherwise just create the native query with only the SQL and get an array of the data back and construct your DTO yourself using the data.
Old question but may be following solution will help someone else.
Suppose you want to return a list of columns, data type and data length for a given table in Oracle. I have written below a native sample query for this:
private static final String TABLE_COLUMNS = "select utc.COLUMN_NAME, utc.DATA_TYPE, utc.DATA_LENGTH "
+ "from user_tab_columns utc "
+ "where utc.table_name = ? "
+ "order by utc.column_name asc";
Now the requirement is to construct a list of POJO from the result of above query.
Define TableColumn entity class as below:
#Entity
public class TableColumn implements Serializable {
#Id
#Column(name = "COLUMN_NAME")
private String columnName;
#Column(name = "DATA_TYPE")
private String dataType;
#Column(name = "DATA_LENGTH")
private int dataLength;
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public int getDataLength() {
return dataLength;
}
public void setDataLength(int dataLength) {
this.dataLength = dataLength;
}
public TableColumn(String columnName, String dataType, int dataLength) {
this.columnName = columnName;
this.dataType = dataType;
this.dataLength = dataLength;
}
public TableColumn(String columnName) {
this.columnName = columnName;
}
public TableColumn() {
}
#Override
public int hashCode() {
int hash = 0;
hash += (columnName != null ? columnName.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof TableColumn)) {
return false;
}
TableColumn other = (TableColumn) object;
if ((this.columnName == null && other.columnName != null) || (this.columnName != null && !this.columnName.equals(other.columnName))) {
return false;
}
return true;
}
#Override
public String toString() {
return getColumnName();
}
}
Now we are ready to construct a list of POJO. Use the sample code below to construct get your result as List of POJOs.
public List<TableColumn> findTableColumns(String table) {
List<TableColumn> listTables = new ArrayList<>();
EntityManager em = emf.createEntityManager();
Query q = em.createNativeQuery(TABLE_COLUMNS, TableColumn.class).setParameter(1, table);
listTables = q.getResultList();
em.close();
return listTables;
}
Also, don't forget to add in your POJO class in persistence.xml! It can be easy to overlook if you are used to your IDE managing that file for you.
Had the same kind of problem where I wanted to return a List of POJOs, and really just POJOs (call it DTO if you want) and not #Entity annotated Objects.
class PojoExample {
String name;
#Enumerated(EnumType.STRING)
SomeEnum type;
public PojoExample(String name, SomeEnum type) {
this.name = name;
this.type = type;
}
}
With the following Query:
String query = "SELECT b.name, a.newtype as type FROM tablea a, tableb b where a.tableb_id = b_id";
Query query = getEntityManager().createNativeQuery(query, "PojoExample");
#SuppressWarnings("unchecked")
List<PojoExample> data = query.getResultList();
Creates the PojoExample from the database without the need for an Entity annotation on PojoExample. You can find the method call in the Oracle Docs here.
edit:
As it turns out you have to use #SqlResultSetMapping for this to work, otherwise your query.getResultList() returns a List of Object.
#SqlResultSetMapping(name = "PojoExample",
classes = #ConstructorResult(columns = {
#ColumnResult(name = "name", type = String.class),
#ColumnResult(name = "type", type = String.class)
},
targetClass = PojoExample.class)
)
Just put this anywhere under your #Entity annotation (so in this example either in tablea or tableb because PojoExample has no #Entity annotation)
I am trying to insert into a mysql table using jpa + hibernate and #SQLInsert annotation. (I was trying a more elaborate insert query until I realized the basic one isn't working). The bean is below, what is happening in on entityManager.persist (or entityManager.merge), even though I set the 3 values on the bean, and log them hibernate complains that CKEY is NULL
the bean:
import java.io.Serializable;
import java.util.Calendar;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.hibernate.annotations.SQLInsert;
#Entity ( )
#Table ( name = "cachedb" )
#SQLInsert( sql="insert into cachedb ( ckey , cvalue , expiry ) VALUES ( ? , ? , ? )")
public class CacheDb implements Serializable
{
private static final long serialVersionUID = 1L;
#Id ( )
#Column ( name = "ckey" )
private String key;
#Column ( name = "cvalue" )
private String value;
#Column ( name = "expiry" )
private Calendar expiry;
#SuppressWarnings ( "unused" )
private CacheDb()
{
}
public CacheDb( final String _key , final String _value )
{
this.key = _key;
this.value = _value;
}
public CacheDb( final String _key , final String _value , final int expirtyMinutes )
{
this.key = _key;
this.value = _value;
final Calendar cal = Calendar.getInstance();
cal.add( Calendar.MINUTE , expirtyMinutes );
this.expiry = cal;
}
public Calendar getExpiry()
{
return this.expiry;
}
public void setExpiry( final Calendar _expiry )
{
this.expiry = _expiry;
}
public static long getSerialversionuid()
{
return serialVersionUID;
}
public void setKey( final String _key )
{
this.key = _key;
}
public String getKey()
{
return this.key;
}
public void setIKey( final String _key )
{
this.key = _key;
}
public String getValue()
{
return this.value;
}
public void setValue( final String _value )
{
this.value = _value;
}
#Override
public String toString()
{
return "CacheDb [key=" + this.key + ", value=" + this.value + ", expiry=" + this.expiry + "]";
}
}
some sample code I use to test inserts:
import java.util.List;
import javax.persistence.Query;
import com.database.jpa.EntityUtils;
public class TestInsert
{
public static void main(String[] args) throws Exception
{
javax.persistence.EntityManager em = null;
String key = "KEY.TEST.08082017";
try
{
em = EntityUtils.getEntityManagerWithOutTransaction( "RLENTYMGR" );
em.getTransaction().begin();
final Query q = em.createQuery("select p from CacheDb p where key = ?1" );
q.setParameter( 1 , key );
final List<CacheDb> resultsList = q.getResultList();
if (resultsList.size()==0)
{
CacheDb newRecord = new CacheDb();
newRecord.setKey( key ); // only required column varchar(100)
newRecord.setValue( "TESTB" ); //varchar(1000)
//newRecord.setExpiry(null); not needed default is null
em.persist( newRecord );
//newRecord = em.merge( newRecord );
}
em.getTransaction().commit();
}
catch(final Exception e)
{
e.printStackTrace();
if (em!=null)
{
em.getTransaction().rollback();
}
}
finally
{
if (em!=null) {em.close();}
}
}
}
the exception:
Caused by: java.sql.BatchUpdateException: Column 'CKEY' cannot be null
at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:2055)
at com.mysql.jdbc.PreparedStatement.executeBatch(PreparedStatement.java:1467)
at org.hibernate.engine.jdbc.batch.internal.BatchingBatch.performExecution(BatchingBatch.java:123)
It would seem that hibernate doesn't look at the order of columns you use in #SQLInsert.
It only uses its own order—which you have to find out first by letting Hibernate generate an insert statment for you and then mimicking it in your custom #SQLInsert.
As #user1889665 stated, hibernate uses it's own ordering of columns, as stated in the docs:
The parameter order is important and is defined by the order Hibernate handles properties. You can see the expected order by enabling debug logging, so Hibernate can print out the static SQL that is used to create, update, delete entities.
To see the expected sequence, remember to not include your custom SQL through annotations or mapping files as that will override the Hibernate generated static SQL.
Basically you need to do this:
Remove #SQLInsert
Enable debug logging
logging.level.org.hibernate=DEBUG
Insert entity normally
myCrudRepository.save(myEntity)
Check logs to see the insert statement generated
org.hibernate.SQL : insert into MY_TABLE (notMyFirstColumn, myLastColumn, myFirstColumn) values (?, ?, ?)
Use the order from insert statement printed in logs in #SQLInsert
#SQLInsert(sql = "INSERT INTO MY_TABLE(notMyFirstColumn, myLastColumn, myFirstColumn) values (?, ?, ?)")
I am interested in using Hibernate INNER JOINS that return an entity/model result.
At Hibernate Community Documentation, they write:
Or - assuming that the class Family has an appropriate constructor - as an actual typesafe Java object:
select new Family(mother, mate, offspr)
from DomesticCat as mother
join mother.mate as mate
left join mother.kittens as offspr
For the life of me, I have not been able to build that appropriate constructor. I want to query
Select new Participant(part, addr.adddressType)
from Participant part
INNER JOIN part.adddresses addr
Should I create a new Java class, let's say Participant_Address.java that reads like this:
Select new Participant_Address (part, addr.adddressType)
from Participant part
INNER JOIN part.adddresses addr
With constructor:
public Participant_Address(new Participant(...), String addressType)
Got this to work! Very happy..
Created a new class:
Placed in my Hibernate folder just for convenience/relevance:
package echomarket.hibernate;
public class ParticipantAddress implements java.io.Serializable {
private Participant part;
private String addressType;
public ParticipantAddress() {
}
public ParticipantAddress(Participant part, String addressType) {
this.part = part;
this.addressType = addressType;
}
public Participant getPart() {
return part;
}
public void setPart(Participant part) {
this.part = part;
}
public String getAddressType() {
return addressType;
}
public void setAddressType(String addressType) {
this.addressType = addressType;
}
}
Tested with:
package echomarket.hibernate;
import echomarket.hibernate.HibernateUtil;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.Transaction;
public class TestHib {
public static void main(String[] args) {
Session session = null;
Transaction tx = null;
List result = null;
String query = null;
try {
session = HibernateUtil.getSessionFactory().getCurrentSession();
tx = session.beginTransaction();
try {
query = "SELECT new echomarket.hibernate.ParticipantAddress(part, addr.addressType) "
+ " from Participant part "
+ " INNER JOIN part.addresses addr "
+ " WHERE addr.addressType = 'primary' AND part.participant_id = '603aec80-3e31-451d-9ada-bc5c9d75b569' GROUP BY part.participant_id, addr.addressType";
System.out.println(query);
result = session.createQuery(query)
.list();
tx.commit();
} catch (Exception e) {
System.out.println("Error result/commit in TestHib");
e.printStackTrace();
tx.rollback();
} finally {
tx = null;
session = null;
}
/// typically check that result is not null, and in my case that result.size() == 1
echomarket.hibernate.ParticipantAddress hold = (echomarket.hibernate.ParticipantAddress)result.get(0);
Participant pp = (Participant) hold.getPart(); /// Got my Participant record
System.out.println("wait"); /// put a break here so I could evaluate return on result, hold and pp
}
}
I really hope that this helps folks...
I have two classes: News and Comments with one-to-many association between them.
I am using Hibernate Criteria to fetch news from database. I would like my news to be ordered by the count of its comments.
session.createCriteria(News.class, "n");
criteria.createAlias("n.comments", "comments");
criteria.setProjection(Projections.projectionList()
.add(Projections.groupProperty("comments.id"))
.add(Projections.count("comments.id").as("numberOfComments")));
criteria.addOrder(Order.desc("numberOfComments"));
List<News> news = criteria.list();
With the following code I'm getting not the list of news but the list of objects with two Long's in each of them.
What should I do to get the list of sorted news objects?
I've found the answer to my question here:
Hibernate Criteria API - how to order by collection size?
I've added the new hibernate Order implementation:
public class SizeOrder extends Order {
protected String propertyName;
protected boolean ascending;
protected SizeOrder(String propertyName, boolean ascending) {
super(propertyName, ascending);
this.propertyName = propertyName;
this.ascending = ascending;
}
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
String role = criteriaQuery.getEntityName(criteria, propertyName) + '.' + criteriaQuery.getPropertyName(propertyName);
QueryableCollection cp = (QueryableCollection) criteriaQuery.getFactory().getCollectionPersister(role);
String[] fk = cp.getKeyColumnNames();
String[] pk = ((Loadable) cp.getOwnerEntityPersister())
.getIdentifierColumnNames();
return " (select count(*) from " + cp.getTableName() + " where "
+ new ConditionFragment()
.setTableAlias(
criteriaQuery.getSQLAlias(criteria, propertyName)
).setCondition(pk, fk)
.toFragmentString() + ") "
+ (ascending ? "asc" : "desc");
}
public static SizeOrder asc(String propertyName) {
return new SizeOrder(propertyName, true);
}
public static SizeOrder desc(String propertyName) {
return new SizeOrder(propertyName, false);
}
}
And then applied that to my criteria as
criteria.addOrder(SizeOrder.desc("n.comments"));
Now everything works fine,
thanks everyone a lot :)
I'm trying to execute a simple stored procedure with Spring/Hibernate using Annotations.
Here are my code snippets:
DAO class:
public class UserDAO extends HibernateDaoSupport {
public List selectUsers(final String eid){
return (List) getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws
HibernateException, SQLException
{
Query q = session.getNamedQuery("SP_APPL_USER");
System.out.println(q);
q.setString("eid", eid);
return q.list();
}
});
}
}
my entity class:
#Entity
#Table(name = "APPL_USER")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorFormula(value = "SUBSCRIBER_IND")
#DiscriminatorValue("N")
#NamedQuery(name = "req.all", query = "select n from Requestor n")
#org.hibernate.annotations.NamedNativeQuery(name = "SP_APPL_USER",
query = "call SP_APPL_USER(?, :eid)", callable = true, readOnly = true, resultClass = Requestor.class)
public class Requestor {
#Id
#Column(name = "EMPL_ID")
public String getEmpid() {
return empid;
}
public void setEmpid(String empid) {
this.empid = empid;
}
#Column(name = "EMPL_FRST_NM")
public String getFirstname() {
return firstname;
}
...
}
public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"applicationContext.xml");
APFUser user = (APFUser)ctx.getBean("apfUser");
List selectUsers = user.getUserDAO().selectUsers("EMP456");
System.out.println(selectUsers);
}
}
and the stored procedure:
create or replace PROCEDURE SP_APPL_USER (p_cursor out sys_refcursor, eid in varchar2)
as
empId varchar2(8);
fname varchar2(50);
lname varchar2(50);
begin
empId := null;
fname := null;
lname := null;
open p_cursor for
select l.EMPL_ID, l.EMPL_FRST_NM, l.EMPL_LST_NM
into empId, fname, lname
from APPL_USER l
where l.EMPL_ID = eid;
end;
If i enter invalid EID, its returning empty list which is OK.
But when record is there, following exception is thrown:
Exception in thread "main" org.springframework.jdbc.BadSqlGrammarException: Hibernate operation: could not execute query; bad SQL grammar [call SP_APPL_USER(?, ?)]; nested exception is java.sql.SQLException: Invalid column name
Do I need to modify the entity(Requestor.class) ?
How will the REFCURSOR be converted to the List?
The stored procedure is expected to return more than one record.
That's because of the bug in the hibernate.
I've modified the stored procedure to fetch all the columns and it worked well.