Hibernate dynamic instantiations with collections, is it possible? - java

I would like to write a hql query using a dynamic instantiation with a list as one of it's parameters.
Simplified example:
A HQL query with a dynamic instantiation:
select new x.y.UserDto(u.name, u.contacts) from User u where u.xyz=:param1 ...
and my dto class constructor is:
public class UserDto {
private String name;
private List contacts;
public UserDto(String name, List contacts) {
this.name = name;
this.contacts = contacts;
}
...
}
And the entity mapping:
public class User {
#olumn(name="NAME")
String name;
#ManyToMany(targetEntity= Contacts.class, fetch = FetchType.EAGER)
#JoinTable(name="USER_DEPARTMENT_CONTACTS",
joinColumns=#JoinColumn(name="DEPARTMENT_ID"),
inverseJoinColumns=#JoinColumn(name="USER_ID"))
private List<Contacts> contacts;
...
}
So as you can see all I want is to create a new object that has some properties and collections of an entity.
I can understand that Hibernate would need one or more queries to achieve this as this would generate multiple result rows for each entity.
Does anyone knows if it is possible to create a new object which is a combination of properties and collections?

Sorry but it is not possible. According to JPA specification,
The type of the query result specified by
the SELECT clause of a query is AN
ENTITY abstract schema type, A
STATE-FIELD type - NOT A COLLECTION -,
the result of an aggregate function,
the result of a construction
operation, OR SOME SEQUENCE OF THESE.
You could use the following instead:
select DISTINCT new x.y.UserDto(u) from User u LEFT JOIN FETCH u.contacts
So, this way you would have the users with your contacts fetched

Related

jpa entitymanager - map query result into nested dto classes

I'm using jpa EntityManager with hibernate in my java spring application. Suppose I have a user entity like below:
public class User {
private Long id;
...
#ManyToOne
private Address address
}
And I have Custom user dto object for passing into client:
public class UserDTO {
private Long id;
private AddressDTO address;
...
}
And I have a UserRepository that exceute normal jpql query with EntityManager and Query.
Note, I need to have custom dto, because my dto has some fields that does not exist in entity and must be calculated in query. Now my question: is there any way with EntityManager that map flat query result into my nested UserDTO? In fact, I need to map result of address in AdressDTO inside UserDto and so on.
Note: I want to use jpql not native sql query.
You can construct DTO right in JPQL.
Here is an example.
select new your.package.UserDTO(u.id, a.country, a.city, a.street)
from User u join u.address a
where ...
Such query returns List<UserDTO>.
Of course UserDTO has to have appropriate constructor:
public UserDTO(Long id, String country, String city, String street){
this.id = id;
this.address = new AddressDTO(country, city, street);
}
You're on the right way.
You really need to fetch User and then convert it to UserDTO.
Don't build DTO within your queries.
For that conversion you need Java Mapper. I prefer MapStruct but there is a plenty of such tools (ModelMapper, Dozer etc).
MapStruct is smart enough to manage nested objects as well.

Select only some columns from a table

Is there a way to select only some columns from a table using jpa?
My tables are huge and I am not allowed to map all the columns in my entities. I tried to create an entity (as a side note, I don't have PKs in my tables):
#Entity
#Table(name = "SuperCat")
#Getter
#Setter
public class Cat{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name="nameCat")
private String name;
}
and then in my repository to
public interface CatRepository extends
CrudRepository<Cat, Long> {
#Query(
"SELECT name FROM Cat")
Page<Cat> getAlCats(Pageable pageable);
This is only a simple example, but the idea is the same. I have searched a lot and I found projections, but there you need to map the whole table, then I found native queries, but still doesn't apply. I know I can return an Object and the other solution is to use query with NEW and create my own object (no #entity, like a pojo). But is there a way that I can do this using jpa, to be able to use repository and services, if I am creating my own pojo then i will create a #transactional class put the queries (with NEW) there and this is it. I don't like this approach and I don't think that the jpa does't allow you to select only some columns, but I didn't find a proper way.
Maybe you will ask what is the result if I am doing like this:
I get this error: "Cannot create TypedQuery for query with more than one return using requested result type [java.lang.Long]"
(For new queries, I am talking about : http://www.java2s.com/Tutorials/Java/JPA/4800__JPA_Query_new_Object.htm maybe I was not clear)
You can do the same by using below approach.
Just create a constructor in entity class with all the required parameters and then in jpa query use new operator in query like below.
String query = "SELECT NEW com.dt.es.CustomObject(p.uniquePID) FROM PatientRegistration AS p";
TypedQuery<CustomObject> typedQuery = entityManager().createQuery(query , CustomObject.class);
List<CustomObject> results = typedQuery.getResultList();
return results;
And CustomObject class should look like below with the constructor.
public class CustomObject {
private String uniquePID;
public CustomObject(String uniquePID) {
super();
this.uniquePID = uniquePID;
}
public String getUniquePID() {
return uniquePID;
}
public void setUniquePID(String uniquePID) {
this.uniquePID = uniquePID;
}
}
spring-data-jpa projection not need to map the whole table, just select the necessary fileds :
// define the dto interface
public interface CatDto {
String getName();
// other necessary fields
...
}
#Query(value = "select c.name as name, ... from Cat as c ...)
Page<CatDto> getAllCats(Pageable pageable);
By this way, CatDto is an interface and it only includes some fileds part of the whole table. Its fields name need to match the select field's alias name.

Static Factory Method in JPQL query instead of constructor

Currently I use a lot of queries which use constructors for building value objects in JPQL as below
#Query("SELECT new com.DocDTO(d.documentId, d.docType) FROM Document d where d.parentId=:parentId")
Set<DocDTO> getDocsWithinFolder(#Param("parentId") Long parentId);
But as code gets complex, I have a need to build objects with various combinations of constructor parameters, leading to the classic telescoping problem.
As explained in Effective Java (Item1) is there a way to build a JPQL query by passing a factory method instead of a constructor ? I am thinking something along the lines of
#Query("SELECT DocDTO.query1(d.documentId, d.docType) FROM Document d where d.parentId=:parentId")
Set<DocDTO> getDocsWithinFolder(#Param("parentId") Long parentId);
and then build the appropriate static factory method query1 inside the DocDTO class. Is this possible in JPQL ?
You can use Dynamic projection to solve this problem. Dynamic projection let you to change return type of single query dynamically. To better understand this lets take a example of this User entity:
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private String email;
// setter and getters
}
If you want to get only name of a user using dynamic projection first you will need to create a interface like this:
public interface Name {
String getLastName();
String getFirstName();
}
In your repository you will need to create query like this:
<T> List<T> findByLastName(String lastName, Class<T> type);
or with #Query
#Query("select u.firstName,u.lastName from User u where lastName=?1")
<T> List<T> findByLastName(String lastName,Class<T> type);
In your service:
List<Name> name = findByLastName("xyz",Name.class);

How to fetch a field lazily with Hibernate Criteria

Each row of the table Person (having name, firstname and age) shall be read.
EntityManager em = emf.createEntityManager();
Session s = (Session) em.getDelegate();
Criteria criteria = s.createCriteria(Person.class);
criteria.setFetchMode("age", FetchMode.SELECT);
But the SQL shows
Hibernate:
select
person0_.name,
person0_.firstname,
person0_.age
from
SCOPE.PERSON person0_
How to let the age be lazy ONLY for the Criteria??
I think that lazy mode only makes sense with associations. If you are accessing a plain table it will load all the fields.
If you want the age field not to appear in the SQL and so not being loaded into memory then use projections:
Criteria crit = session.createCriteria(Person.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.property("name"));
projList.add(Projections.property("firstname"));
crit.setProjection(projList);
Setting the FetchMode of the "age" property on a criteria has no effect because the fetching strategy at this point is for associated objects only but not for properties. See section 20.1. Fetching strategies of the hibernate docs.
Hibernate uses a fetching strategy to retrieve associated objects
if the application needs to navigate the association. Fetch strategies
can be declared in the O/R mapping metadata, or over-ridden by a
particular HQL or Criteria query.
The only way for lazy loading of a property is the #Basic annotation set to FetchType.LAZY. See here, or if you use .hbm.xml files for mapping use lazy=true, see this section of the hibernate docs.
The #Basic annotation allows you to declare the fetching strategy for
a property. If set to LAZY, specifies that this property should be
fetched lazily when the instance variable is first accessed. It
requires build-time bytecode instrumentation, if your classes are not
instrumented, property level lazy loading is silently ignored.
Lazy loading of properties also use buildtime bytecode instumentation (hibernate is changing the entity classes after compilation to allow lazy loading of properties). Read 20.1.8. Using lazy property fetching
An other possible solution (except for all the other solutions) to your problem is to make a simpler Person class and use a constructor query like:
public class PersonDTO {
private String name;
private String firstname;
private Person(String name, String firstname) {
this.name = name;
this.firstname = firstname;
}
// getters & setters
}
Query q = session.createQuery("select new your.package.name.PersonDTO("
+ "p.name, p.firstname) from Person p");
q.list();
You could even use your existing Person class, just extend it with an appropriate constructor, but I would prefer explicitness.
But all the solutions presented here do not implement a lazy loading of the age attribute. The only way to do this is the #Basicannotation, or you have to implement your own lazy loading.
If your age is an object like the PersonAge of #Dragan you could associate the fecth mode with the criteria rather than the entity like you do.
So, I think you have three options:
age as primitive and projection like #Paco says (Person.age will be null and not a Proxy, you lose the lazyness that you want)
age as primitive without projection (more bytes in the wire)
age as PersonAge + criteria.setFetchMode (you will get the lazyness that you want at the cost of an extra object/table/mapping)
For Projection you could use ResultTransformer to
Criteria crit = session.createCriteria(Person.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.property("name"));
projList.add(Projections.property("firstname"));
crit.setProjection(projList);
crit.setResultTransformer(new ResultTransformer() {
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
String name = (Long) tuple[0];
String firstName = (String) tuple[1];
return new Person(name , firstName);
}
#Override
public List<Reference> transformList(List collection) {
return collection;
}
});
I think you could create a PersonProxy on your own that triggers a query for retrieve the age but this is kind of awful.
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
String name = (Long) tuple[0];
String firstName = (String) tuple[1];
return new PersonProxy(name , firstName);
}
class PersonProxy {
Person realPerson;
public getAge(){
// create a query with realPerson.id for retrieve the age.
}
}
Your reasoning is valid (in general; we can however argue about the specific example of the age field), but unfortunately there is no straight-forward solution for this. Actually, Hibernate has the concept of fetch profiles, but it is currently very limited (you can override the default fetch plan/strategy only with the join-style fetch profiles).
So, the possible workaround to your issue could be as follows.
1) Move age to a separate entity and associate the Person entity with it with a lazy one-to-one relationship:
#Entity
class PersonAge {
private Integer age;
}
#Entity
class Person {
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, optional = false)
#JoinColumn(name = "PERSON_AGE_ID")
private PersonAge personAge;
public Integer getAge() {
return personAge.getAge();
}
public void setAge(Integer age) {
personAge.setAge(age);
}
}
2) Define a fetch profile which overrides the default one:
#FetchProfile(name = "person-with-age", fetchOverrides = {
#FetchProfile.FetchOverride(entity = Person.class, association = "personAge", mode = FetchMode.JOIN)
})
3) Enable this profile for each session in the application:
session.enableFetchProfile("person-with-age");
Depending on the framework you use, there should be an easy hook/interceptor which you will use to enable the profile for each session (transaction) that is craeted. For example, an approach in Spring could be to override AbstractPlatformTransactionManager.doBegin of the transaction manager in use.
This way the personAge will be eagerly loaded in all the sessions in the application, unless the fetch profile is explicitly disabled.
4) Disable the fetch profile in the session in which you use the desired Criteria query:
session.disableFetchProfile("person-with-age");
This way the default fetch plan/strategy is used (specified in the entity mappings), which is the lazy loading of the PersonAge.
You can simply define a new entity SimplePerson mapped to the same persons database table which contains only the following attributes:
id
name
firstName
This way, when selecting a SimplePerson with both Criteria and HQL, the age column will not be retrieved.
Another alternative is to use lazy loading for basic attributes, but mapping multiple subentities to the same database table is much more flexible.
I would like to add (or maybe clarify) the followings. Given the main class (Settlement) with an attribute class (Customer):
#Entity
#Table(name = "settlement")
public class Settlement extends IdBasedObject {
...
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "customer_id_fk")
private Customer customer;
}
#Entity
#Table(name = "customer", schema = SchemaUtil.SCHEMA_COMMON)
public class Customer extends IdBasedObject {
#Column(name = "organization_type")
#Enumerated(EnumType.ORDINAL)
private CompanyType companyType;
#Column(name = "organization_legal_name")
private String companyLegalName;
...
}
If you would like to get all the distinct customers from the Settlement, you would use the Projections distinct on the 'customer' property and followed by creating an alias from the Settlement class:
public List<Customer> findUniqueCustomer() throws Exception {
Session session = super.getSessionFactory().openSession();
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.distinct(Projections.property("customer")));
Criteria criteria = session.createCriteria(Settlement.class);
criteria.setProjection(projectionList);
criteria.createAlias("customer", "customer");
return criteria.list();
}
Now, if you do that, you will get back a list of non-proxy error 'could not initialize proxy - no Session' for each of the customer object.
Fortunately, criteria provides the setResultTransformer function that will 're-shape' the return.
criteria.setResultTransformer(new ResultTransformer() {
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
Customer customerObject = (Customer) tuple[0];
Customer customer = new Customer();
customer.setId(customerObject.getId());
customer.setVersion(customerObject.getVersion());
customer.setCompanyType(customerObject.getCompanyType());
customer.setCompanyLegalName(customerObject.getCompanyLegalName());
return customer;
...
}
#SuppressWarnings("rawtypes")
#Override
public List<Customer> transformList(List collection) {
return collection;
}
});
The tuple[0] essentially contains the customer object value, since the customer object is not proxied, you will get the error. In the transformTuple function, you have a chance to 're-create' each of the customer object thereby avoiding the 'non-proxied' error.
Please give a try.

Getting list of string(Collection) from an object using hibernate criteria

I have a class as following
Class Department {
private String departmentName;
private List<String> students;
....
....
....
}
Now i want to fetch students attribute (List<String>) only from Department class but not entire Department object with restriction departName := Chemistry.
How to write hibernate criteria for this???
About you question: no, this projection is not possible because collection of elements has the same lifecycle of owner entity
Getting collections of elements with criteria is not possibile (read here). You have to work with native SQL to return List<String>.

Categories