Using Entity Graph with DTO Projection using ResultTransformer returns null value - spring

Hello I am new in jpa + criteria API + hibernate..
I have doubt related to use of ResultTransformer in jpa criteria API.
I am having two entity Department and Employee. one to many mapping between department and employee. i want to use entitygraph with DTO projection
1. Department
#Entity
#NamedEntityGraph(name = "departmentWithEmployee", attributeNodes = #NamedAttributeNode("setOfEmployee"))
#Table(name = "tblDepartment")
public class Department {
#Id
private String id;
private String name;
#OneToMany(mappedBy = "department")
private Set<Employee> setOfEmployee;
//....getter & setter
}
2. Employee
#Entity
#Table(name = "tblEmployee")
public class Employee {
#Id
private String id;
#ManyToOne
#JsonIgnore
private Department department;
private String firstName;
private String lastName;
//...getter & setter
}
DepartmentDTO.java
public class DepartmentDTO implements Serializable {
private String id;
private String name;
private Set<EmployeeDTO> setOfEmployee;
//... getter & setter..
}
I am executing query with entity graph and I want to get all departments from database and serialize with DepartmentDTO.java
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<DepartmentDTO> criteria = builder.createQuery(DepartmentDTO.class);
Root root = criteria.from(Department.class);
criteria.select(root);
EntityGraph graph = entityManager.getEntityGraph("departmentWithEmployee");
List<DepartmentDTO> list = entityManager.createQuery(criteria).setHint("javax.persistence.fetchgraph", graph)
.unwrap(org.hibernate.Query.class).setResultTransformer(Transformers.aliasToBean(DepartmentDTO.class)).list();
when i will get size of list it will give me correct result but it will give list of department with null value like
(In database i am having total 3 departments)
Output :
[
{
"id": null,
"name": null,
"setOfEmployee": null
},
{
"id": null,
"name": null,
"setOfEmployee": null
},
{
"id": null,
"name": null,
"setOfEmployee": null
}
]
I am getting all fields with null value.
So what is the issue here , is any mistake in use of ResultTransformer? Or is any better way to execute this query where I can get records using DTO ..?

Related

Spring Boot JPA Using Many-to-Many relationship with additional attributes in the join table

I have two simple classes Student and Course. I am trying to set up many to many relationship between these classes. I want to use additional table whose PRIMARY KEY is the combination of the primary keys of student and course tables (student_id and course_id).
The student class:
#Entity
#Table(name = "student")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#OneToMany(mappedBy = "student")
private Set<CourseStudent> courses;
}
The course class:
#Entity
#Table(name = "course")
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String courseName;
#OneToMany(mappedBy = "course")
Set<CourseStudent> students;
}
The entity that stores the relationship between course and the student:
#Entity
#NoArgsConstructor
#Data
public class CourseStudent {
#EmbeddedId
CourseStudentKey id;
#ManyToOne
#MapsId("studentId")
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#MapsId("courseId")
#JoinColumn(name = "course_id")
Course course;
public CourseStudent(Student student, Course course) {
this.student = student;
this.course = course;
this.rating = 0;
}
int rating;
}
Attention: Since I want to have additional features in this entity (for example, storing the rating of the students for courses), I don't want to use #JoinTable idea that we implement in the Student class.
Since I have multiple attributes in the primary key of CourseStudent entity, I used the following class
#Embeddable
#Data
public class CourseStudentKey implements Serializable {
#Column(name = "student_id")
Long studentId;
#Column(name = "course_id")
Long courseId;
}
I have the following POST request to insert the student into a course:
#PostMapping("/insert/students/{studentId}/courses/{courseId}")
public CourseStudent insertStudentIntoCourse(#PathVariable(value = "studentId") Long studentId,
#PathVariable(value = "courseId") Long courseId) {
if (!studentRepository.existsById(studentId)) {
throw new ResourceNotFoundException("Student id " + studentId + " not found");
}
if (!courseRepository.existsById(courseId)) {
throw new ResourceNotFoundException("Course id " + courseId + " not found");
}
CourseStudent courseStudent = new CourseStudent(
studentRepository.findById(studentId).get(),
courseRepository.findById(courseId).get()
);
return courseStudentRepository.save(courseStudent);
}
I have manually added Student and the Course into my local database and send this request by using Postman.
http://localhost:8080/insert/students/1/courses/1
However, I get the following error:
{
"timestamp": "2022-08-04T12:33:18.547+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/insert/students/1/courses/1"
}
In the console, I get NullPointerException. What is the thing I am doing wrong here?

OneToOne ConstraintViolation while saving a new Record, PK Provided

We have an Entity called Customers that has a OneToOne relationship to the Entity Address.
The Customer's PK should be manually defined. The Address' PK should be automatically defined.
So, in Customer I omitted the #GeneratedValue and I'm providing is value manually. But, when trying to save I'm getting the following error:
2018-11-07 10:42:17.810 ERROR 1257 --- [nio-8080-exec-2] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [Validation failed for classes [br.com.customers.entity.Address] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='não pode ser nulo', propertyPath=street, rootBeanClass=class br.com.customers.entity.Address, messageTemplate='{javax.validation.constraints.NotNull.message}'}
The problem is that the address.street is being provided and I can't realize why JPA is complaining that it's null...
Here are the JSON body that I'm trying to save. (It's being deserialized correctly, as, Address is not NULL)
{
"customer_Id": 50,
"name": "name",
"company_name": "company_name",
"email": "email#provider.com",
"business_phone": "(00) 1111-2222",
"mobile_phone": "(00) 1111-2222",
"document": "123456789",
"state_registration_number": "ISENTO",
"state_registration_type": "NO_CONTRIBUTOR",
"city_registration_number": "ISENTO",
"classification": "AUTO",
"address": {
"street": "STREET NAME",
"number": "NUMBER",
"complement": "COMPLEMENT",
"zip_code": "ZIP_CODE",
"neighborhood": "NEIGHBORHOOD",
"city": "CITY",
"state": "STATE"
}
}
Here are the Customer Entity:
#Data
#Entity(name = "X_CUSTOMERS")
public class Customer {
#Id
private int customer_Id;
#NotNull
private String name;
private String company_name;
private String email;
private String business_phone;
private String mobile_phone;
#NotNull
private String document;
private String state_registration_number;
private String state_registration_type;
private String city_registration_number;
#NotNull
private String classification;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinColumn(name = "address_id")
private Address address;
}
And here, Address Entity:
#Data
#Entity(name = "X_ADDRESS")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int address_Id;
#NotNull
private String street;
private String number;
private String complement;
private String zip_code;
private String neighborhood;
private String city;
private String state;
}
What Am I doing wrong?
Thanks!!!
Adding the code do persist the entities:
Customer Repository:
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
}
To persist:
#RestController
#RequestMapping("/customers")
public class CustomersController {
private CustomerRepository customerRepository;
public CustomersController(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
#PostMapping
public Customer postCustomer(#RequestBody Customer customer) {
return customerRepository.save(customer);
}
}
From reading the Hibernate documentation, the save operation only persist entities with auto generated ids. So, if you intend to set the id yourself, then what you need, is to change your insert method for persist. And since you customer has an id that is not auto generated, maybe this could be the issue. You can read more in this blog.
#PostMapping
public Customer postCustomer(#RequestBody Customer customer) {
return customerRepository.persist(customer);
}
Hope it helps.
If you add CascadeType.MERGE, it will work
#OneToOne(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "address_id")
private Address address;
you set the customer id(50) so the following line of SimpleJpaRepository will be executed.
return this.em.merge(entity);

JPA Hibernate Split data between two tables

I have a REST API that will receive some customer data on the following format:
{
"customer_Id": 50,
"name": "name",
"company_name": "company_name",
"email": "email#provider.com",
"business_phone": "(00) 1111-2222",
"mobile_phone": "(00) 1111-2222",
"document": "123456789",
"state_registration_number": "ISENTO",
"state_registration_type": "NO_CONTRIBUTOR",
"city_registration_number": "ISENTO",
"classification": "AUTO",
"address": {
"street": "STREET NAME XXX",
"number": "NUMBER XX",
"complement": "COMPLEMENT",
"zip_code": "ZIP_CODE",
"neighborhood": "NEIGHBORHOOD",
"city": "CITY",
"state": "STATE"
}
}
I'd like to save this data on two tables: One table should contains the "main" customer data, and the other one should contais the customer's "address" data.
So, I defined the Customer entity as below:
#Data
#Entity(name = "X_CUSTOMERS")
public class Customer {
#Id
private int customer_Id;
#NotNull
private String name;
private String company_name;
private String email;
private String business_phone;
private String mobile_phone;
#NotNull
private String document;
private String state_registration_number;
private String state_registration_type;
private String city_registration_number;
#NotNull
private String classification;
#OneToOne(cascade = CascadeType.ALL)
private Address address;
}
And the Address entity as
#Data
#Entity(name = "X_ADDRESS")
public class Address {
#NotNull
private String street;
private String number;
private String complement;
private String zip_code;
private String neighborhood;
private String city;
private String state;
}
But, I couldn't realize how to create a relationship between them. Should I create a customer_id attribute on the Address entity? Should I define some additional Tags on Customer's address attribute? Note that I don't have a customer on the JSON data that is posted by the REST Client and, if a Customer is Update ou Deleted, the Address data should be Updated / Deleted also.
Sorry if this is a such trivial question. I'm learning the basics of JPA/Hibernate these days and your answer will guides me to the right direction to avoid things such 'reinventing the wheel'.
Thanks a lot!
If we consider Address to be a Value Object rather than entity then it can be mapped as below. In your case, it probably is correct to model it as a VO: if you were building a database of addresses then it could be considered an entity. See further here:
Value vs Entity objects (Domain Driven Design)
We can then make the address class an #Embeddable rather than an entity: it will not then have any identity of its own. To have the customer and address details stored in separate tables we can also use JPAs #SecondaryTable funtionality:
https://docs.oracle.com/javaee/7/api/javax/persistence/SecondaryTable.html
We have then the model classes as below. With these mappings your JSON updates will work as expected.
Customer:
#Data
#Table(name = "customers")
#SecondaryTable(name = "customer_addresses", pkJoinColumns={
#PrimaryKeyJoinColumn(name="customer_id",
referencedColumnName="customer_id")})
public class Customer {
protected static final String ADDRESS_TABLE_NAME = "customer_addresses";
// other fields
#Embedded
private Address address;
}
Address:
#Data
#Embeddable
public class Address {
#NotNull
#Column(table = Customer.ADDRESS_TABLE_NAME)
private String street;
#Column(table = Customer.ADDRESS_TABLE_NAME)
private String number;
#Column(table = Customer.ADDRESS_TABLE_NAME)
private String complement;
#Column(table = Customer.ADDRESS_TABLE_NAME)
private String zip_code;
#Column(table = Customer.ADDRESS_TABLE_NAME)
private String neighborhood;
#Column(table = Customer.ADDRESS_TABLE_NAME)
private String city;
#Column(table = Customer.ADDRESS_TABLE_NAME)
private String state;
}
This is how i do it :
#OneToOne (fetch=FetchType.EAGER, cascade = CascadeType.ALL, optional = false)
#NotNull(message = "L'addresse du domicile est requise!", groups = Seventh.class)
#Getter
#Setter
private Address homeAddress;
No need for any inverse mapping and this lets me save a customer and his address in one fell swoop!
You need an ID for your address entity as well, something like :
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = false)
#Getter
#Setter
private Long id;

Save Entity Using Foreign Key

I have a question that I hope I can describe clearly. I have the following classes:
#Entity
public class Filter {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY,orphanRemoval = true)
#JoinColumn(name = "filter_id", nullable = false)
private Set<FilterMedication> medications;
//setter and getters are not show
...}
.
#Entity
public class FilterMedication {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#ManyToOne
#JoinColumn(name = "medication_id", nullable = false)
private Medication medication;
// Setters and getters are not shown
.....}
.
#Entity
#Table(name = "medication")
public class Medication {
#Column(name = "generic_name")
private String genericname;
private String name;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
// Setters and getters are not shown
.....}
Basically Filters one-to-many relationship with FilterMedicaton, and FilterMedication has many-to-one relationship with Medication.
I created a repository to query for Filters
public interface FilterRepository extends JpaRepository <Filter, Long> {}
I can add a new filter by sending the following JSON object to the save() function
{
"id": 1,
"name": "Test1",
"medications": [
{
"id": 2,
"medication": {
"genericname": "Oxymetazoline HCl Nasal Soln 0.05%",
"name": "12 HOUR NASAL SPRAY 0.05 % NA SOLN",
"strength": "0.05%",
"form": "Solution",
"route": "Nasal"
}
}
]
}
Now time for the question: Is there a way to pass the Medication Foreign Key instead of the complete Medication object, Spring JPA will convert the foreign key to the proper object? The JSON code will be something like this
{
"id": 1,
"name": "Test1",
"medications": [
{
"id": 2,
"FORIGEN KEY": 1
}
]
}
Technically, I can write a function to do so; however, I feel that there is a better and cleaner way to do it.
//convert json to java obj
Filter filter = new Gson().fromjson(yourjson, Filter.class);
//get the fiterMedication (id = 2)
int id = filter.getMedications().getId();
FilterMedication filterMedication = filterMedicationRepository.get(id);
Filter newFilter = new Filter();
newFilter.setId(filter.getId());
....//set name
newFilter.getMedications.add(filterMedication);//get the set of medications and add the element filterMedication
//save newFilter

Spring Data Rest #EmbeddedId cannot be constructed from Post Request

I have a JPA entity Person and an entity Team. Both are joined by an entity PersonToTeam. This joining entity holds a many-to-one relation to Person and one to Team. It has a multi-column key consisting of the ids of the Person and the Team, which is represented by an #EmbeddedId. To convert the embedded id back and forth to the request id I have a converter. All this follows the suggestion on Spring Data REST #Idclass not recognized
The code looks like this:
#Entity
public class PersonToTeam {
#EmbeddedId
#Getter
#Setter
private PersonToTeamId id = new PersonToTeamId();
#ManyToOne
#Getter
#Setter
#JoinColumn(name = "person_id", insertable=false, updatable=false)
private Person person;
#ManyToOne
#Getter
#Setter
#JoinColumn(name = "team_id", insertable=false, updatable=false)
private Team team;
#Getter
#Setter
#Enumerated(EnumType.STRING)
private RoleInTeam role;
public enum RoleInTeam {
ADMIN, MEMBER
}
}
#EqualsAndHashCode
#Embeddable
public class PersonToTeamId implements Serializable {
private static final long serialVersionUID = -8450195271351341722L;
#Getter
#Setter
#Column(name = "person_id")
private String personId;
#Getter
#Setter
#Column(name = "team_id")
private String teamId;
}
#Component
public class PersonToTeamIdConverter implements BackendIdConverter {
#Override
public boolean supports(Class<?> delimiter) {
return delimiter.equals(PersonToTeam.class);
}
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
if (id != null) {
PersonToTeamId ptid = new PersonToTeamId();
String[] idParts = id.split("-");
ptid.setPersonId(idParts[0]);
ptid.setTeamId(idParts[1]);
return ptid;
}
return BackendIdConverter.DefaultIdConverter.INSTANCE.fromRequestId(id, entityType);
}
#Override
public String toRequestId(Serializable id, Class<?> entityType) {
if (id instanceof PersonToTeamId) {
PersonToTeamId ptid = (PersonToTeamId) id;
return String.format("%s-%s", ptid.getPersonId(), ptid.getTeamId());
}
return BackendIdConverter.DefaultIdConverter.INSTANCE.toRequestId(id, entityType);
}
}
The problem with this converter is, that the fromRequestId method gets a null as id parameter, when a post request tries to create a new personToTeam association. But there is no other information about the payload of the post. So how should an id with foreign keys to the person and the team be created then? And as a more general question: What is the right approach for dealing many-to-many associations in spring data rest?
After running into the same issue I found a solution. Your code should be fine, except I return new PersonToTeamId() instead of the DefaultIdConverter if id is null in fromRequestId().
Assuming you are using JSON in your post request you have to wrap personId and teamId in an id object:
{
"id": {
"personId": "foo",
"teamId": "bar"
},
...
}
And in cases where a part of the #EmbeddedId is not a simple data type but a foreign key:
{
"id": {
"stringId": "foo",
"foreignKeyId": "http://localhost:8080/path/to/other/resource/1"
},
...
}

Resources