Can i reference a non mapped table in JPA CriteriaBuilder? - java

I am trying to re write the following query using entity manager..
public abstract class HibernateEntitySelector<T> extends HibernateDAOSupport implements EntitySelector<T> {
#Autowired
public void init(SessionFactory factory) {
setSessionFactory(factory);
}
public String SELECT_IDS = " IN (SELECT RESULT_ID FROM QUERY_RESULTS)";
public List<T> getEntitiesByIds(){
DetachedCriteria criteria = DetachedCriteria.forClass(getEntityClass());
criteria.add(Restrictions.sqlRestriction(getPrimaryKeyField()+SELECT_IDS));
return (List<T>) this.getHibernateTemplate().findByCriteria(criteria);
}
Something like this..
public abstract class HibernateEntitySelector<T> implements EntitySelector<T> {
public String SELECT_IDS = " IN (SELECT RESULT_ID FROM QUERY_RESULTS)";
#PersistenceContext
protected EntityManager em;
public List<T> getEntitiesByIds(){
String s = "FROM " + getEntityClass().getSimpleName() + " ent WHERE ent."+getEntityId()+SELECT_IDS;
Query query = this.em.createNamedQuery(s);
return (List<T>)query.getResultList();
}
}
But this fails due to QUERY_RESULTS not being mapped. Is there a way to do this without using the createNativeQuery method and then having to map all the columns manually?

You were quite close, though you are using:
Query query = this.em.createNamedQuery(s);
Use the createNativeQuery instead and alter the query string alongside also:
String s = "SELECT ent.* FROM " + getEntityClass().getSimpleName() + " ent WHERE ent."+getEntityId() + SELECT_IDS;
Query query = this.em.createNativeQuery(s, getEntityClass());
Try it out.

Related

Using jdbcTemplate.query with parameters

I have 3 tables in Database Lecture--< LectureGroups >-- Groups.
And I want to get schedule for a certain group on a certain day. I try to do it in this way:
#Repository
public class Schedule {
private static final String GET_GROUP_DAY_SCHEDULE = "SELECT * FROM LECTURES " +
"INNER JOIN LECTUREGROUPS ON LECTURES.ID = LECTUREGROUPS.LECTUREID " +
"INNER JOIN GROUPS ON GROUPS.ID = LECTUREGROUPS.GROUPID " +
"WHERE GROUPID = :GROUPID AND DATE = :DATE";
#Autowired
private JdbcTemplate jdbcTemplate;
public List<Lecture> getGroupDayLectures(int groupId, LocalDateTime dateTime) {
MapSqlParameterSource parameters = new MapSqlParameterSource()
.addValue("groupid", groupId)
.addValue("date", dateTime);
return jdbcTemplate.query(GET_GROUP_DAY_SCHEDULE, new BeanPropertyRowMapper<>(Lecture.class), parameters);
}
}
But I get an exception in query raw
Caused by: org.postgresql.util.PSQLException: Can't infer the SQL type to use for an instance of org.springframework.jdbc.core.namedparam.MapSqlParameterSource. Use setObject() with an explicit Types value to specify the type to use.
How I can fix it?
I also used variant with
private static final String GET_GROUP_DAY_SCHEDULE = "SELECT * FROM LECTURES " +
"INNER JOIN LECTUREGROUPS ON LECTURES.ID = LECTUREGROUPS.LECTUREID " +
"INNER JOIN GROUPS ON GROUPS.ID = LECTUREGROUPS.GROUPID " +
"WHERE GROUPID = ? AND DATE = ?";
#Autowired
private JdbcTemplate jdbcTemplate;
public List<Lecture> getGroupDayLectures(int groupId, LocalDateTime dateTime) {
return jdbcTemplate.query(GET_GROUP_DAY_SCHEDULE, new Object[]{groupId, dateTime}, new BeanPropertyRowMapper<>(Lecture.class));
}
and it works but return only 1 Lecture in list (it must be 3)
There is a signature with parameters in the jdbcTemplate class:
public <T> List<T> query(String sql, RowMapper<T> rowMapper, #Nullable Object... args)
So it is very easy to use it in this way
private static final String GET_GROUP_DAY_SCHEDULE = "SELECT * FROM LECTURES " +
"INNER JOIN LECTUREGROUPS ON LECTURES.ID = LECTUREGROUPS.LECTUREID " +
"INNER JOIN GROUPS ON GROUPS.ID = LECTUREGROUPS.GROUPID " +
"WHERE GROUPID = ? AND DATE = ?";
#Autowired
private JdbcTemplate jdbcTemplate;
public List<Lecture> getGroupDayLectures(int groupId, LocalDate date) {
return jdbcTemplate.query(GET_GROUP_DAY_SCHEDULE, new BeanPropertyRowMapper<>(Lecture.class), groupId, date);
}

HIbernate JPA Query from Different Sources not Generating Proper Jackson JSON Response

I am retrieving data from multiple tables/POJO's.I want the data in JSON format.In Pojo classes I am using #JsonProperty.Still I am not getting result Json in desired format.
My result:
[["2017 Sprint 1","Android development",23/12/2016,16/01/2017]]
I want result in format {
"iteration": "2017 Sprint 1",
"project": "MDM - Core & Integration",
"isd": "23/12/2016",
"ied": "16/01/2017",
My main controller method:
#Controller
#RequestMapping("/json/retrospective")
public class MainControllerClass
{
#RequestMapping(value="{userid}", method = RequestMethod.GET,produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public List<Details> getInfoInJSON(#PathVariable int userid)
{
Configuration con = new Configuration();
con.configure("hibernate.cfg.xml");
SessionFactory SF = con.buildSessionFactory();
Session session= SF.openSession();
Query test=session.createQuery("select itr.iteration_name,prj.project_name,itr.isd,itr.ied from RetrospectiveInfo retro,IterationInfo itr,ProjectInfo prj where retro.retrospective_id ="+userid+" and retro.project_id = prj.project_id and retro.iteration_id = itr.iteration_id");
List<Details> details= test.list();
session.close();
SF.close();
return details;
}
}
Class details:
public class Details
{
#JsonProperty("iteration")
private String iteration;
#JsonProperty("project")
private String project;
#JsonProperty("isd")
private Date isd;
#JsonProperty("ied")
private Date ied;
getter/setters
I have got 3 Jackson jars annotation,databind and core of latest version 2.8 in buildpath.Why I am I getting such a result??What changes do I need to make in my code??Are any jars to be added??kindly help
The main issue is that your are constructing a Details class that is formed from a JPA query using different types check (Error: Cannot create TypedQuery for query with more than one return)
To resolve the issue, create a constructor for the required JSON attributes
package com.sof.type;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonPropertyOrder({ "iteration", "project", "isd", "ied"})
public class Details {
#JsonProperty
private String iteration;
#JsonProperty
private String project;
#JsonProperty
private String isd;
#JsonProperty
private String ied;
public Details(String iteration, String project, String isd, String ied) {
this.iteration = iteration;
this.project = project;
this.isd = isd;
this.ied = ied;
}
}
Then use it this way
#PersistenceContext
private EntityManager em;
or
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("com.sof");
EntityManager em = entityManagerFactory.createEntityManager();
with
List<Details> details = em.createQuery(
"SELECT NEW com.sof.type.Details(itr.iteration_name, prj.project_name, itr.isd, itr.ied) " +
"FROM RetrospectiveInfo retro, " +
" IterationInfo itr, " +
" ProjectInfo prj " +
"WHERE retro.retrospective_id = :userId " +
"AND retro.project_id = prj.project_id " +
"AND retro.iteration_id = itr.iteration_id", Details.class)
.setParameter("userId", userid)
.getResultList();

Are there any ways to map the result set at sql2o as generics?

I'm writing database client using sql2o library. There are a lot of data access objects in my project, so I supposed to have lot of similar functions for getting access to the tables. For example, for class Persons.
public List<Persons> getPersonsData(){
String sql = "SELECT * FROM " + PERSONS;
try(org.sql2o.Connection con = sql2o.open()) {
return con.createQuery(sql).executeAndFetch(Persons.class);
}
}
Are there any ways to optimize it? I was thinking about using generics, but as I know there no ways to get instance of generic class. Is it really impossible to create something like this?
public class Getter<T> {
public List<T> getGenericClass(){
String sql = "SELECT * FROM " + T.tableName;
try(org.sql2o.Connection con = sql2o.open()) {
return con.createQuery(sql).executeAndFetch(T.class);
}
}
}
We managed that creating a Store class
public class Store<T> {
private T value;
public void set(T object){
value = object;
}
public T get(){
return value;
}
}
and modify your GenericClass
public class GenericClass{
public <T> void storeGenericClass(Store<T> store, Class<T> clazz){
String sql = "SELECT field1, field2 FROM MYTABLE WHERE ID = 1";
try(org.sql2o.Connection con = sql2o.open()) {
store.set(con.createQuery(sql).executeAndFetchFirst(clazz));
}
}
public <T> void storeGenericClass(List<T> store, Class<T> clazz){
String sql = "SELECT field1, field2 FROM " +
clazz.getName().toUpperCase();
try(org.sql2o.Connection con = sql2o.open()) {
store.addAll(con.createQuery(sql).executeAndFetch(clazz));
}
}
}
Use the same className as tableName is not a best practice. But if you map each class with a table, you can do in this way
public class GenericClass{
...
public <T> void storeGenericClass(List<T> store, Class<T> clazz){
String sql = "SELECT field1, field2 FROM " +
clazz.getName().toUpperCase();
try(org.sql2o.Connection con = sql2o.open()) {
store.addAll(con.createQuery(sql).executeAndFetch(clazz));
}
}
...
}
To instantiate:
public class MyClass {
public String field1, field2;
}
...
GenericClass generic= new GenericClass(sql2o);
Store<MyClass> store = new Store<>();
generic.storeGenericClass(store, MyClass.class);
MyClass retrieved = store.get();
System.out.println("My Class fields are: "+retrieved.field1 + "-"+retrieved.field2);
...
I improved on #tonino code and removed the need for the Store class. Here is the code:
public <T> List<T> list(Class<T> clazz){
String sql = "SELECT * FROM " +
getTablename(clazz);
try(Connection con = sql2o.open()) {
return con.createQuery(sql).executeAndFetch(clazz);
}
}
public <T> T findById(Integer id, Class<T> clazz) {
String sql = "SELECT *" +
" FROM " + getTablename(clazz) +
" WHERE id=:id";
try(Connection con = sql2o.open()) {
return con.createQuery(sql)
.addParameter("id", id)
.executeAndFetchFirst(clazz);
}
}
private String getTablename(Class clazz) {
//This can be improved later with reflection
return clazz.getSimpleName().toUpperCase();
}

Hibernate Criteria order by count of child records

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 :)

Hibernate SQL Query result Mapping/Convert TO Object/Class/Bean

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

Categories