I have a projection interface used in a native query in Spring JPA, one of the values mapped as String is an enum inside Java.
I would like to get the enum description field based on the string returned from the database in my projection interface, I thought of a custom annotation for that, but I didn't come up with a construction idea.
Can anyone share a solution to this scenario.
Ex My Enum
#AllArgsConstructor
#Getter
public enum PersonType {
PHYSICAL_PERSON("F","Physical Person"),
LEGAL_PERSON("J","Legal Person");
private String code;
private String description;
}
My inteface projection
public interface PersonProjection {
Long getId();
String getName();
String getObs();
String getPersonType();
}
My repository
#Query(value = "select p.id as id, p.description as name, p.obs as obs, p.type as personType from public.person p where p.code = :code", nativeQuery = true)
List<PersonProjection> findByCode(#Param("code") Long code);
My interface is being returned from the database like this:
id=1,name="Mike",obs="Tests",personType="F";
Is there any way in this projection interface I can use an annotation where I can get the description of the enum by the value of the returned string
Ex:
public interface PersonProjection {
Long getId();
String getName();
String getObs();
#DescriptionEnum(PersonType.class)
String getPersonType();
}
In the domain class with the JPA annotations I can, but since I'm out, returning the data in an interface like a DTO, I can't get the same resource.
Related
I have an interface which is extending crud repository
public interface PersonRepo extends CrudRepository<Person, String> {
#Query(value="select name from PERSON where addr=?1", nativeQuery = true)
List<Person> getPeronUsingAddress(String addr);
}
Person entity looks like this:
class Person {
private String name;
private String phoneNumber;
private String address;
//along with getters setters and all basic hibernate annotation to persist and retrieve
}
the person object is saved into the databse and at the time of retrieving the native query is working fine as hibernate executes correct query. But I am not able to get the return type.
If the return type is List of Person then I am getting InvalidDataAccessResourceUsageException
If I create an interface and use the list of interface as return type like
interface response {
String getName();
}
List of Response interface getPeronUsingAddress(String addr);
then I am getting proxy object in the service. I am not able to get the datas from proxy object.
Another approach I did is to use List of object as return type. But it is not possible to downcast to my Person Object.
How to do that.? Or is there any other solution by which I can return selective columns from crud repository and get a Java object with those selected Columns.
In order to fetch selected columns from an entity, you can do like below :
class Person {
private Integer id;
private String name;
private String phoneNumber;
private String address;
//along with getters setters and all basic hibernate annotation to persist and retrieve
}
Create a DTO or Java Object like below :
public class PersonDTO {
private Integer id;
private String name;
private String phoneNumber;
private String address;
public PersonDTO(Integer id, String name, String phoneNumber, String address) {
// logic here
}
//If you want just want name and phone number.
public PersonDTO(String name, String phoneNumber) {
// logic here
}
// you can't create overridden constructors as all members are of same type and at runtime program won't be able to differentiate unless you provide some logic for it.
// getters, setters, any other methods here...
}
Now below will be you Query but it's not native, if you want to keep native query then you will need to use ResultTransformer like here
#Query("select new your.package.PersonDTO(p.name, p.phoneNumber) from Person p where p.id = :id")
public PersonDTO getPersonById(Integer id);
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.
There is an entity with CLOB property:
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
#Column(columnDefinition = "clob")
#Lob
private String description;
//getters and setters here
}
Now I would like to have a method that can return only part of this class, so here is a projection class:
public interface PersonProjection {
String getLastName();
String getDescription();
}
and the repository:
#Repository
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
#Query(value = "select pe.last_name as lastName, pe.description from person pe where pe.last_name = :lastName", nativeQuery = true)
List<PersonProjection> findAllByLastName(#Param("lastName") String lastName);
}
Using native query in this example is not needed but in my case I would like to call an Oracle's function in the where clause (JSON_TEXTCONTAINS to be specific).
Now using this repository in the controller causes:
Could not write JSON: Projection type must be an interface!; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Projection type must be an interface! (through reference chain: java.util.ArrayList[0]->com.sun.proxy.$Proxy103[\"description\"])
Exception seems to be caused because query returns description field as an instance of java.sql.Clob interface, but in projection class this field is defined as String. Exception is not thrown when entity class is used as a return type from repository method (and there description is also defined as a String).
Complete project to check this problem can be found here.
you can parse clob to varchar in sql.
Ex: dbms_lob.substr( pe.description, 4000, 1 ) as description
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.
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);