I'm fetching only selected attributes from a table using HQL, maintaining a list of non-entity class objects.
For Eg. My entity class:
#Entity
#Table(name="STUDENT")
public class Student {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="NAME", columnDefinition="TEXT", length="60", nullable = false)
private String name;
#ManyToOne
#JoinColumn(name = "Dept_id", nullable = false)
private Department department;
// Other fields...
// Getter-Setters
}
and non-persistent DTO class is having only fewer class members (say, name only):
public class StudentDTO {
private String name;
// Getter-Setter for name
}
Now using
public List<StudentDTO> getStudents(Long deptId) {
List<StudentDTO> students;
Query query = session.createQuery("select student.name " +
"from Student as student " +
"where Dept_id =?").setResultTransformer(new AliasToBeanResultTransformer(StudentDTO.class));
query.setString(0, Long.toString(deptId));
students = CommonUtil.castList(StudentDTO.class, query.list());
return students;
}
where castList converts any collection into ArrayList.
public static <T> List<T> castList(Class<? extends T> clazz, Collection<?> c) {
List<T> resultList = new ArrayList<T>(c.size());
for(Object o: c)
resultList.add(clazz.cast(o));
return resultList;
}
throws org.hibernate.PropertyNotFoundException: Could not find setter for 0 on class ../StudentDTO
Referring Hibernate exception PropertyNotFoundException when using Transformer, I changed my query to "select student.id as id,...", having Long id inside StudentDTO but that throw same exception, saying setter not found for 1.
Getter/Setters are given for each property. Please suggest changes!
As you are using AliasToBeanResultTransformer, you have to declare proper alias names in your query. The class name itself says that it will transform the results into your resultClass using the alias names.
The transformTuple method of AliasToBeanResultTransformer class uses the alias names to find the setter methods of your resultClass (StudentDto):
for (int i = 0; i < aliases.length; i++) {
String alias = aliases[i];
if(alias != null) {
setters[i] = propertyAccessor.getSetter(resultClass, alias);
}
}
In order to make AliasToBeanResultTransformer work properly you need to use proper alias names in your query. If the property name in your StudentDto class is name, you should use select student.name as name. Using select student.name as n will again throw exception. The alias names that you are using in the query should be same as the property names in your DTO class.
just write each column name as below :
Select column_name as property_name , ...., from Class_name
eg: select student.name as name from Student
set alias for each field in select clause, these field is same as filed name in your persistence class which you set as transformer class in your query, for example:
public mypersistenceclass()
{
// define constructor
public mypersistenceclass(){}
private String username;
// define setter and getter
}
public userclass()
{
...
Query query = session.createQuery("select Users.username as username ...")
.setResultTransformer(new AliasToBeanResultTransformer(mypersistenceclass.class));
...
}
Related
I have the following ContactDTO java POJO that is a trimmed down version of my Contact entity that contains over 100 fields/columns
ContactDTO:
public class ContactDTO {
#JsonProperty
private Integer contactId;
#JsonProperty
private String userName;
#JsonProperty
private String firstName;
//getters and setters...
}
I am trying to return this in a hibernate query as follows, note that this query works as expected when I run it manually in MYSQL Workbench:
#Override
public ContactDTO getContactDTObyId(String client, Integer id) throws ATSException {
EntityManager entityManager = null;
try {
entityManager = entityManagement.createEntityManager(client);
String queryString = "select contact_id as contactId, username as userName, first_name as firstName from " + client + ".contact where "+" contact_id = " + id + "";
Query query = entityManager.createNativeQuery(queryString, ContactDTO.class);
return (ContactDTO) query.getSingleResult();
} catch (Exception e) {
log.error("An error is thrown in getContactDTObyId");
} finally {
entityManagement.closeEntityManager(client, entityManager);
}
}
The above is giving me the following error at the getSingleResult() line:
org.hibernate.MappingException: Unknown entity: ContactDTO
How can I return this DTO object using Hibernate?
I am aware that this is not an Entity in the way that my Contact Entity maps to the Contact database table, but I thought I could still return it using Hibernate by populating the fields.
Specifying a class as the result of a native query only works with entity classes. To specify an DTO (and unmanaged class in the spec), you need to specify a constructor on your DTO and declare a SqlResultMapping
Assuming you declare a three argument constructor on ContactDTO you should add:
#SqlResultSetMapping(name = "contactDTOResult",
classes = {
#ConstructorResult(targetClass = ContactDTO.class,
columns = {
#ColumnResult(name = "contactId"),
#ColumnResult(name = "userName"),
#ColumnResult(name = "firstName")})
})
And in your native query use the result set mapping name contactDTOResult:
Query query = entityManager.createNativeQuery(queryString, "contactDTOResult");
There are two tables PersonEntity and cityentity. PersonEntity in the database is linked to cityentity by the external key fk_cityid. I need to select all the records (names) of the PersonEntity table with the given CityId. Join is used everywhere for this, but in this case I don't need data from the cityentity table, only the name field of the PersonEntity table. Here is a description of the classes:
#Entity
public class PersonEntity {
private Long id;
private String name;
private CityEntity cityId;
}
#Entity
public class CityEntity {
private Long id;
private String name;
}
Here is the HQL query:
#Repository
public interface PersonEntityRepository extends JpaRepository<PersonEntity, Long> {
#Query("select p.name FROM PersonEntity p where (p.name = :name or :name is null) " +
"and (p.cityId = :cityId or :cityId is null)")
List<PersonEntity> findByNameAndCity (
#Param("name") String name,
#Param("cityId") CityEntity cityId);
}
tried by id:
#Query("select p.name FROM PersonEntity p where (p.name = :name or :name is null) " +
"and (p.cityId.id = :cityId or :cityId is null)")
List<PersonEntity> findByNameAndCity (
#Param("name") String name,
#Param("cityId") Long cityId);
In both cases, the error is: "the data type could not be determined".
options for calling the function:
servisPerson.findByNameAndCity (null, cityId);
or
servisPerson.findByNameAndCity (name, null);
In fact, there are more than two parameters. I only show two for simplification.
servisPerson.findByNameAndCity (name, age, ..., cityId);
Person entity should look something like this
#Entity
public class PersonEntity {
private Long id;
private String name;
#OneToOne
#JoinColumn(name = "city_id")
private CityEntity city;
}
Than you can write your query like this
List<PersonEntity> findByNameAndCityOrNameIsNullOrCityIsNull(String name, CityEntity city);
Spring Data JPA is so smart to generate the query for you under the hood.
BTW, you attempted to write JPQL, not HQL.
EDIT (reaction on comment about long method names):
I would suggest to avoid creating JPQL query, because is is more error prone. If you don't like lengthy method name, you can wrap it into shorter default method within repository:
#Repository
public interface PersonEntityRepository extends JpaRepository<PersonEntity, Long> {
List<PersonEntity> findByNameAndCityOrNameIsNullOrCityIsNull(String name, CityEntity city);
default List<PersonEntity> shortName(String name, CityEntity city) {
return findByNameAndCityOrNameIsNullOrCityIsNull(name, city);
}
}
I have an Entity called Student
#Entity
#Table(name = "students")
public class Student implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STUDENT_ID")
private Integer studentId;
#Column(name = "STUDENT_NAME", nullable = false, length = 100)
private String studentName;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "student", cascade = CascadeType.ALL)
private List<Note> studentNotes;
// Some other instance variables that are not relevant to this question
/* Getters and Setters */
}
and an entity called as Note
#Entity
#Table(name = "notes")
public class Note implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "NOTE_ID")
private Integer noteId;
#Column(name = "NOTE_CONTENT")
private String noteText;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "STUDENT_ID")
private Student student;
/* Getters and Setters */
}
As you can see the relationship dictates that a Student can have multiple number of notes.
For displaying some information about the student on a particular page I need only the studentName, count of notes and all the notes.
I created a StudentDTO for that and it looks something like this:
public class StudentDTO {
private Long count;
private String name;
private List<Note> notes;
/* Getters and setters */
}
And I am using the following code to map the Student and Notes returned from the DB to the StudentDTO
private static void testDTO() {
Session session = getSessionFactory().openSession();
String queryString = "SELECT count(n) as count, s.studentName as name, s.studentNotes as notes " +
"from Student s join s.studentNotes n where s.id = 3";
Query query = session.createQuery(queryString);
List<StudentDTO> list = query.setResultTransformer(Transformers.aliasToBean(StudentDTO.class)).list();
for (StudentDTO u : list) {
System.out.println(u.getName());
System.out.println(u.getCount());
System.out.println(u.getNotes().size());
}
}
The above code fails when there are notes fetched in the query but if I remove the notes and get only name and count it works fine.
When notes is included in the query, this is the error that is fired by Hibernate:
select
count(studentnot2_.NOTE_ID) as col_0_0_,
. as col_3_0_,
studentnot3_.NOTE_ID as NOTE_ID1_2_,
studentnot3_.NOTE_CONTENT as NOTE_CON2_2_,
studentnot3_.STUDENT_ID as STUDENT_3_2_
from
students studentx0_
inner join
notes studentnot2_
on studentx0_.STUDENT_ID=studentnot2_.STUDENT_ID
inner join
notes studentnot3_
on studentx0_.STUDENT_ID=studentnot3_.STUDENT_ID
where
studentx0_.STUDENT_ID=3;
And this is the error message that I get:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'as col_3_0_, studentnot3_.NOTE_ID as NOTE_ID1_2_, studentnot3_.NOTE_CONTENT as N' at line 1
Now I can see where the query is wrong but it is generated by Hibernate, not something that I have control on. Is there something that I need to change in my queryString to acheive the result that I need.
I do not want to manually map the results to my DTO, is there a way that I can directly map my studentNotes in Student.java to notes in StudentDTO.java
Looks like this query is wrong. The better way is to get just the student. You can always get collection of notes from a student.
Session session = getSessionFactory().openSession();
String queryString = from Student s where s.studentId = 3;
Query query = session.createQuery(queryString);
Student student = query.getSingleResult();
sysout(student.getNotes().size())
Also, I never retrieved collection this way in SELECT clause; so, not sure but do you really need
join s.studentNotes
in your query? Not sure if my answer is helpful.
Your query is wrong as you would need two joins to also select the count of notes, but that's not even necessary, as you could determine the count by just using the size of the notes collection.
I created Blaze-Persistence Entity Views for exactly that use case. You essentially define DTOs for JPA entities as interfaces and apply them on a query. It supports mapping nested DTOs, collection etc., essentially everything you'd expect and on top of that, it will improve your query performance as it will generate queries fetching just the data that you actually require for the DTOs.
The entity views for your example could look like this
#EntityView(Student.class)
interface StudentDTO {
#Mapping("studentName")
String getName();
#Mapping("studentNotes")
List<NoteDTO> getNotes();
default int getCount() { return getNotes().size(); }
}
#EntityView(Note.class)
interface NoteDTO {
// attributes of Note that you need
#IdMapping Integer getId();
String getNoteText();
}
Querying could look like this
StudentDTO student = entityViewManager.find(entityManager, StudentDTO.class, studentId);
I need to implement a query in Spring Data like this :-
Select User.name, sum(Activity.minutes)
From User, Activity, ActivityStatus
Where User.id = ActivityStatus.userId
And Activity.id = ActivityStatus.activityId
AND ActivityStatus = "COMPLETED"
GROUP BY user.name;
So i need to join 3 tables, therefore I have to use #Query with nativeQuery = true ( correct me if I'm wrong here )
And so my Repository method looks like this :-
#Query(value = "Select User.name, sum(Activity.minutes) as total_minutes
From User, Activity, ActivityStatus
Where User.id = ActivityStatus.userId
And Activity.id = ActivityStatus.activityId
AND ActivityStatus = "COMPLETED"
AND User.Type = ?1
GROUP BY user.name;",
nativeQuery = true
)
List<MyObj> getTotalActivityMinutesByUserType(String userType);
MyObj class looks like this :-
public class MyObj {
String name;
long total_minutes;
// getter and setter methods
public MyObj(String name, long total_minutes) {
this.name = name;
this.total_minutes = total_minutes;
}
}
My Test Method :-
#Test
public void TotalActivityTest() throws Exception {
List<MyObj> objA = myRepository.getTotalActivityMinutesByUser("TEST");
}
and i get the following exception :-
org.springframework.core.convert.ConversionFailedException: Failed to
convert from type [java.lang.Object[]] to type
[com.mycomp.MyObj] for value '{TEST, 5.0}'; nested
exception is
org.springframework.core.convert.ConverterNotFoundException: No
converter found capable of converting from type [java.lang.String] to
type [com.mycomp.dto.MyObj]
I need a way to return the result as MyObj. ( Or at least a way to cast it to MyObj) Is this possible?
EDIT:
Building from #Cepr0's answer My Entity class looks like this :-
#Entity
public class ActivityStatus extends Base {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
private Activity activity;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
private User user;
}
I am not sure how the JOIN query in JPQL should look like...
Just use Projection and JPQL query:
public interface NameAndDuration {
String getName();
Long getDuaration();
}
#Query("select u.name as name, sum(a.minutes) as duration from User u join u.activityStatus st join st.activity a where st.status = "COMPLETED" and u.type = ?1 group by u.name")
List<NameAndDuration> getNameAndDurationByUserType(String userType);
List<NameAndDuration> list = getNameAndDurationByUserType("TEST");
String userName = list.get(0).getName();
This query is probably not exact what you need because I don't know a structure of your entity classes. But if you show them I will correct the query...
The query is returning object array which you are storing in an normal object.Iterate through object array and set it to object like below
List<MyObj > test= new ArrayList<>();
List<Object[]> rows= query.list();
for (Object[] row : rows) {
MyObj temp=new MyObj (arg1,arg2);
temp.set((Dataype) row[0])//You need to create getters and setters for your pojo
..
test.add(temp);
}
I actually had a similar issue with this exception. I have 3 tables: Project, Asset, and ProjectAsset. ProjectAsset is the reference table where one project can have many assets. So I created 3 repositories, one for each entity. The problem is I placed my #Query in the ProjectAssetRepository and that didn't work due to the repository extending CrudRepository. ProjectAssetId is an embedded id made up of projectid and assetid. I can't just return Asset objects in this repository so I moved the method to AssetRepository and everything worked. If you are using cross-reference tables, make sure you pull the correct object or else you will run into this exception.
I want to ask, it is possible that I create query projections and criterion for more than one level deep?
I have 2 model classes:
#Entity
#Table(name = "person")
public class Person implements Serializable {
#Id
#GeneratedValue
private int personID;
private double valueDouble;
private int valueInt;
private String name;
#OneToOne(cascade = {CascadeType.ALL}, orphanRemoval = true)
#JoinColumn(name="wifeId")
private Wife wife;
/*
* Setter Getter
*/
}
#Entity
#Table(name = "wife")
public class Wife implements Serializable {
#Id
#GeneratedValue
#Column(name="wifeId")
private int id;
#Column(name="name")
private String name;
#Column(name="age")
private int age;
/*
* Setter Getter
*/
}
My Criteria API :
ProjectionList projections = Projections.projectionList();
projections.add(Projections.property("this.personID"), "personID");
projections.add(Projections.property("this.wife"), "wife");
projections.add(Projections.property("this.wife.name"), "wife.name");
Criteria criteria = null;
criteria = getHandlerSession().createCriteria(Person.class);
criteria.createCriteria("wife", "wife", JoinType.LEFT.ordinal());
criterion = Restrictions.eq("wife.age", 19);
criteria.add(criterion);
criteria.setProjection(projections);
criteria.setResultTransformer(Transformers.aliasToBean(Person.class));
return criteria.list();
and I hope, I can query Person, with specified criteria for wife property, and specified return resultSet.
so i used Projections for getting specified return resultSet
I want personID, name(Person), name(Wife) will returned. how API i must Use, i more prefer use Hibernate Criteria API.
This time, I used code above for getting my expected result, but it will throw Exception with error message :
Exception in thread "main" org.hibernate.QueryException: could not resolve property: wife.name of: maladzan.model.Person,
and whether my Restrictions.eq("wife.age", 19); is correct for getting person which has wife with 19 as her age value ?
Thanks
AFAIK it is not possible to project more than one level deep with aliastobean transformer. Your options are
create a flattened Data Transfer Object (DTO)
fill the resulting Person in memory yourself
implement your own resulttransformer (similar to option 2)
option 1 looks like this:
Criteria criteria = getHandlerSession().createCriteria(Person.class)
.createAlias("wife", "wife", JoinType.LEFT.ordinal())
.add(Restrictions.eq("wife.age", 19));
.setProjection(Projections.projectionList()
.add(Projections.property("personID"), "personID")
.add(Projections.property("name"), "personName")
.add(Projections.property("wife.name"), "wifeName"));
.setResultTransformer(Transformers.aliasToBean(PersonWifeDto.class));
return criteria.list();
I wrote the ResultTransformer, that does this exactly. It's name is AliasToBeanNestedResultTransformer, check it out on github.
Thanks Sami Andoni. I was able to use your AliasToBeanNestedResultTransformer with a minor modification to suit my situation. What I found was that the nested transformer did not support the scenario where the field is in a super class so I enhanced it to look for fields up to 10 levels deep in the class inheritance hierarchy of the class you're projecting into:
public Object transformTuple(Object[] tuple, String[] aliases) {
...
if (alias.contains(".")) {
nestedAliases.add(alias);
String[] sp = alias.split("\\.");
String fieldName = sp[0];
String aliasName = sp[1];
Class<?> subclass = getDeclaredFieldForClassOrSuperClasses(resultClass, fieldName, 1);
...
}
Where getDeclaredFieldForClassOrSuperClasses() is defined as follows:
private Class<?> getDeclaredFieldForClassOrSuperClasses(Class<?> resultClass, String fieldName, int level) throws NoSuchFieldException{
Class<?> result = null;
try {
result = resultClass.getDeclaredField(fieldName).getType();
} catch (NoSuchFieldException e) {
if (level <= 10){
return getDeclaredFieldForClassOrSuperClasses(
resultClass.getSuperclass(), fieldName, level++);
} else {
throw e;
}
}
return result;
}
My Hibernate projection for this nested property looked like this:
Projections.projectionList().add( Property.forName("metadata.copyright").as("productMetadata.copyright"));
and the class I am projecting into looks like this:
public class ProductMetadata extends AbstractMetadata {
...
}
public abstract class AbstractMetadata {
...
protected String copyright;
...
}
Instead of creating Data Transfer Object (DTO)
In projectionlist make below changes and it will work for you.
ProjectionList projections = Projections.projectionList();
projections.add(Projections.property("person.personID"), "personID");
projections.add(Projections.property("person.wife"), "wife");
projections.add(Projections.property("wife.name"));
Criteria criteria = null;
criteria = getHandlerSession().createCriteria(Person.class,"person").createAlias("person.wife", "wife");
criterion = Restrictions.eq("wife.age", 19);
criteria.add(criterion);
criteria.setProjection(projections);
criteria.setResultTransformer(Transformers.aliasToBean(Person.class));
return criteria.list();