SpringBoot DTO + JPA with List<T> property - java

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.

Related

Returning nested custom classes from Spring JPA, Hibernate

I have a Student class.
#Entity
public class Student {
private long id;
private String name;
private String department;
private String subDepartment;
private int marks;
//getters, setters and constructors.
}
I want to be able to get min, max and average marks grouped by department and then subDepartment.
My repository:
public class StudentRepository extends JpaRepository<Student,Long> {
#Query("Select s.department, s.subDepartment , min(marks), max(marks), avg(marks) from Student as s groupby s.department, s.subDepartment")
List<Object[]> getStatsByDepartmentAndSubDepartment()
}
Of course, this works but I want to avoid using List<Object[]> and instead get something like:
Map<String, Map<String,Stats>> class
Where first key is Department, second key is subDepartment and Stats class encapsulates min,max,average
Is there a way to do this with Spring JPA. ? I checked and nested classes are not possible.
How about Hibernate or any other solution? I am a newbie when it comes to Hibernate.
I am using Spring boot and can add any dependency needed. Any database is ok, even H2 for a start.
You cannot return a map.
You can convert the rows using a class DTO.
Class DTO with constructor
You can also create a class DTO:
class StudentDTO {
public StudentDTO(String dep, String subDep, Integer min, Integer max, Double avg ) {
this.stats = new Stats(min, max, avg);
...
}
}
And then call it in the select clause:
select new StudentDTO(s.department, s.subDepartment, avg(s.marks), ...) from ...
This will return a List<StudentDTO>.
As long as the constructor matches the select clause, it will work fine.
Interface DTO
You can also define DTO using interfaces, but I don't think it works if you have nested classes.
This will work though:
interface StudentViev {
String getDepartment();
String getSubdepartment();
Integer getMinMark();
Integer getMaxMark();
Double getAvgMark();
}
and now you can use it as type for the list:
#Query("Select s.department as department, s.subDepartment as subDepartment, min(marks) as minMark, max(marks) as maxMark, avg(marks) as avgMark from Student as s groupby s.department, s.subDepartment")
List<StudentView> getStatsByDepartmentAndSubDepartment()
Note that I've used aliases in the select cluase so that the column names returned match the getters in the interface

Projections not being applied after upgrading to Spring Boot 2.5.4

For some reason, Projections are not being applied any longer after upgrading to Spring Boot 2.5.4. I really can't figure out why.
I have a class WhoDidWhatWhen (I've changed the class / variable names in the example):
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class WhoDidWhatWhen {
#Id
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
private User who;
#NotNull private String what;
#NotNull #PastOrPresent private Date when;
}
and I have a projection in the same package as the above class:
#Projection(
name = "whoDidWhatWhenProjection",
types = {WhoDidWhatWhen.class})
public interface WhoDidWhatWhenProjection {
#Value("#{target.id}")
long getId();
User getWho();
String getWhat();
Date getWhen();
}
and finally I have my RestRepository:
#RepositoryRestResource(exported = false, excerptProjection = WhoDidWhatWhenProjection.class)
public interface WhoDidWhatWhenRepository
extends PagingAndSortingRepository<WhoDidWhatWhen, Long>, JpaSpecificationExecutor<WhoDidWhatWhen> {}
For some reason the projection / excerpt just isn't being picked up and applied and I just can't figure out why.
I even tried to manually register to projection, but to not avail:
#Configuration
public class RestConfiguration implements RepositoryRestConfigurer {
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.getProjectionConfiguration().addProjection(WhoDidWhatWhenProjection.class);
}
}
Has anyone experienced this before?
I don't know an answer to your particular problem, but I would like to offer you an alternative, which is Blaze-Persistence Entity Views. You can imagine this to be like Spring Data Projections on steroids, with mappings that affect the SQL and thus improve performance. Mappings are fully type validated to avoid runtime surprises and the integration is seamless.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(WhoDidWhatWhen.class)
public interface WhoDidWhatWhenProjection {
#IdMapping
Long getId();
String getWhat();
Date getWhen();
UserDto getWho();
#EntityView(User.class)
interface UserDto {
#IdMapping
Long getId();
String getName();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
WhoDidWhatWhenProjection a = entityViewManager.find(entityManager, WhoDidWhatWhenProjection.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<WhoDidWhatWhenProjection> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!

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.

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

Using embedded nested class to represent complicated state in JPA

I am trying to use an inner class as embeddable to represent some complicated properties of the outer class. When I store this, there is no information from the inner class in the database schema generated by eclipselink.
Does what I'm trying to do seem like a good idea? Why doesn't eclipselink seem to recognize them #Basic attribute on the getRate() in Attributes?
Some other info: Measure must be instantiated using a factory which is provided to the constructor of Person, so I don't even know how I'm going to be able to use this at all. It seems more and more likely that I'll have to make a separate class just to store the state of Person in simple terms (like doubles, not Measures) and use those to create the real Person-type objects, but that has very sad implications for the rest of my application.
#Entity
public static class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Transient
public Measure<Double, CleanupRate> rate;
#Embedded
private Attributes attributes;
#Embeddable
public static class Attributes {
#Transient
private Person person;
public Attributes() {
}
public Attributes(Person person) {
this.person = person;
}
#Basic
public double getRate() {
return person.rate.getInternalValue();
}
public void setRate(double value) {
person.rate.setInternalValue(value);
}
}
public Person() {
rate = udm.getMeasureFactory().createMeasure(0.0, CleanupRate.class);
attributes = new Attributes(this);
}
public void setRate(double rate) {
this.rate.setValue(rate);
}
}
Edit:
In order to inject the measure dependency into my objects when they are retrieved from storage, I've added an interface which injects the dependency and used it in my DAO. Since the DAO can be injected, I can propagate the dependency down to the retrieved objects. I got the idea from a blog.
private <T extends UomInjectable> List<T> //
getListOfUomInjectableType(final Class<T> klass) {
List<T> result = getListOfType(klass);
for (UomInjectable injectable : result) {
injectable.injectUomFactory(udm);
}
return result;
}
It is using the access type from the Person class, which is set to field, and so not seeing the annotation at the property level.
You will need to change the access type using Access(PROPERTY) on the embeddable class, and should remove the #Transient annotation on the person attribute.
I think in general you're going to be in trouble having Entities (Embeddable or otherwise) that need constructors with arguments. I'm not sure how that might be related to your schema generation issue, but I think this will be a problem trying to persist/retrieve these objects.
As you hinted, JPA requires all entity types to have a no-argument constructor. While your Attributes class has one, it leaves the 'person' field as null which will fairly quickly result in NPE's. Same with the Person constructor (maybe you left out the one that passes in 'udm' from the sample code?).
The set the Person for the Attributes, just use property access in Person and set it in your setAttributes method.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Embeddables#Relationships

Categories