Static Factory Method in JPQL query instead of constructor - java

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

Related

SpringBoot DTO + JPA with List<T> property

Let say I have the following entity, (I skip the constructor for simplicity):
public class Person {
int id;
String name;
String lastName;
Date birthday;
List<Vehicles> vehicles;
}
And I want to create a DTO in order to get only what I need from the DB:
public class PersonDTO {
int id;
String name;
List<Vehicles> vehicles;
}
My Crud repository looks like this:
Person findById(int personId);
But I want to change it to:
PersonDTO findById(int personId);
It works perfectly if I remove the vehicles property, (is a List), but I do need this list. Any clue?
Citing the Spring Data JPA reference:
Another way of defining projections is by using value type DTOs (Data Transfer Objects) that hold properties for the fields that are supposed to be retrieved. These DTO types can be used in exactly the same way projection interfaces are used, except that no proxying happens and no nested projections can be applied.
Your List<Vehicles> vehicles is a nested projection, so your apporach is not applicable. You need to make use of an interface-based projection in this case:
public interface PersonProjection {
int getId();
String getName();
List<VehicleProjection> getVehicles();
interface VehicleProjection {
// Getters for desired fields of Vehicle as above for Person(Projection)
}
}
Then you can change your repository method to
PersonProjection findById(int personId);
Unfortunately, the interface-based approach is slightly less performant because, as the reference documentation states, proxying happens.

How to select few fields from a Entity in Spring Boot using Spring Data?

I have a use case where I want to display the contents of an entity but hide certain fields. My entity is as follows -
Entity
public class StudentDetail {
#Id
private Long ID;
private String firstName;
private String middleName;
private String lastName;
#JsonFormat(pattern="dd-MMM-yyyy", timezone="IST")
#Temporal(TemporalType.DATE)
private Date dateOfBirth;
}
It has many other properties as well which I am not showing here.
Repository -
#Repository
public interface StudentDetailsRepository extends JpaRepository<StudentDetail, Integer> {
#Query("select d from StudentDetail d where month(d.dateOfBirth) = ?1 ")
List<StudentDetail> getStudentListBasedOnDateOfBirth(int month);
}
Service class -
public List<StudentDetail> getStudentBirthdayDetails(int month) {
List<StudentDetail> StudentDetail = StudentDetailsRepository.getStudentListBasedOnDateOfBirth(month);
return StudentDetail;
}
And there is a controller class which calls the Service class with a month parameter to filter the data set.
What I want to do is modify the query in Repository class and include only the firstname, middleName and lastName property. The Repository class should hide the dateOfBirth field. I realise that the following query will return the filtered items -
select d.firstName, d.middleName, d.lastName from StudentDetail d where month(d.dateOfBirth) = ?1
However, the return type of the Repository class is of Entity Type StudentDetail . Selecting only few fields from it will result in error. So, I want to know what changes should I make in the repo/service and controller class (assuming only return types of the classes will change ) ?
This is called projection, and Spring offers you two ways to accomplish it.
Keep in mind this exists in JPA terms, not only in Spring.
Taking your Repository as a starting point
#Repository
public interface StudentDetailsRepository extends JpaRepository<StudentDetail, Integer> {
...
}
we can use
interface-based projection
simply create an interface which represents the result you want to have
public interface StudentDetailProjection {
String getFirstName();
String getMiddleName();
String getLastName();
}
and add a method to your Repository
#Repository
public interface StudentDetailsRepository extends JpaRepository<StudentDetail, Integer> {
StudentDetailProjection get...(...);
}
Spring will subclass that interface automatically and it will ask JPA to execute a query which will extract only the specified fields.
class-based projection
works in almost the same way as interface-based projection, but no proxying and sub-classing is necessary, as you're offering Spring a concrete class.
public class StudentDetailProjection {
private final String getFirstName;
private final String getMiddleName;
private final String getLastName;
public StudentDetailProjection(
final String getFirstName,
final String getMiddleName,
final String getLastName,
) {...}
// Getters
}
Documentation goes more in depth.
Also, a must read is this blog post by Vlad Mihalcea, the master of JPA.
The method might look, approximately, like
#Query("select new your.package.StudentDetailProjection(d.firstName, d.middleName, d.lastName) from StudentDetail d where month(d.dateOfBirth) = ?1")
List<StudentDetailProjection> getStudentListBasedOnDateOfBirth(final int month);
This will go along the concrete class option (2), because a constructor is required.

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.

Call method in JPA

I'm using ObjectDB with JPA. I would like to call myMethod(). For example:
entityManager.createQuery("SELECT ... FROM ... WHERE MyClass.myMethod() = 100")
Is it possible? Maybe any annotation is required before method in the class?
#Entity
public class MyClass implements Serializable {
#Basic
private int x;
#Basic
private int y;
public int myMethod() {
return x*1000+y;
}
}
JPQL is not exactly an object-based query language. You can't define your own methods, and JPQL provides a very limited set of functions. So if you want to keep within the JPA spec then the answer is no; would have to be JPA-implementation specific - DataNucleus JPA certainly allows you to have your own methods in the query language (as a vendor extension), no idea about your quoted JPA provider - that said though, it would only execute such a query in the datastore if you put the code for that method in a query method implementation (as opposed to in the class)
Yes, you can! And no additional annotations are required.
ObjectDB is an implementation of an object-oriented database system (OODBS) and as a result allows you to interact with database items as objects, that includes calling methods, using inheritance and polymorphism, etc.
This is a simple working example I have. With a class like this:
#Entity
public class Person {
private static final long serialVersionUID = 1L;
#Id #GeneratedValue
private long id;
private String firstName;
private String lastName;
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFullName() {
return firstName + " " + lastName;
}
}
This query returns correct results:
entityManager.createQuery(
"SELECT p FROM Person p WHERE p.getFullName()='John Johnson'", Person.class).getResultList();
JPQL is translated into SQL, so you cannot include a Java method call, as your database (most likely) does not support Java.
In JPA 2.1 you will be able to use the FUNCTION operator to call "database" functions. Some database do support defining functions in Java, but normally a proprietary database language is used (such as PL/SQL).
EclipseLink supports both FUNC and FUNCTION operators for calling database functions. You can also define your own operators using the OPERATOR operator which allows you to define your own custom database function call in Java.
http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL#EclipseLink_special_operators
Not the way you are looking for. If you want to use custom method, you can create and register them before using them.
e.g. create a function as below:
public int myMethod(int x, int y){
return x*1000+y;
}
Then register this function using registerFunction() with the dialect of your database. Once done, you can write a query as :
from MyClass data where myMethod(data.x, data.y) =100;
Hoe this helps.
No you can't do that. Since JPA always works with prepared statement (parameterized SQL), you can only set parameters in the WHERE clause of the JPQL query like
entityManager.createQuery("SELECT .... FROM ... WHERE
someCondition=:someValue).setParameter("someValue", "parameterValue");
Make the method (myMethod()) return that value somehow and replace the second parameter which is parameterValue of the setParameter() method by the value returned by your method, myMethod().

Hibernate dynamic instantiations with collections, is it possible?

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

Categories