Parent Object is not populated while creating a Child Object - java

I am playing around with SpringBoot lately along with Spring data jpa. Here's the thing. I have two classes Teacher and Course where there exists a OneToMany and ManyToOne relationship between them respectively. Course is a owning side of this relationship as seen below:
#Entity
#JsonSerialize(using = CourseSerializer.class)
#JsonIgnoreProperties(value = { "teacher", "students" }, allowGetters = true)
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Getter #Setter
private Long id;
#Getter #Setter
private String title;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "teacher_id", referencedColumnName = "id")
#Getter
#Setter
private Teacher teacher;
....
}
And Teacher class looks like this:
#Entity
#JsonIgnoreProperties(value = { "courses" }, allowGetters = true)
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Getter #Setter
private Long id;
#Getter #Setter
#JsonProperty("first_name")
private String firstName;
#Getter #Setter
#JsonProperty("last_name")
private String lastName;
#OneToMany(mappedBy = "teacher", cascade = CascadeType.ALL /*another problem: , fetch = EAGER*/)
#JsonSerialize(converter = ListCourseConverter.class)
#Getter #Setter
private List<Course> courses = new ArrayList<>();
....
}
Now Course cannot exist without a Teacher (optional=false). I am trying to make a POST call for the course creation this way to /api/course/teacherId:
{
"title": "Java Complete"
}
And I am expecting the following result from the call:
{
"id": 1,
"title": "Java Complete",
"teacher": "<firstName> <lastName>"
}
For this reason, I am using a CourseSerializer which serializes the Teacher object to display just that:
...
jgen.writeStartObject();
jgen.writeNumberField("id", value.getId());
jgen.writeStringField("title", value.getTitle());
jgen.writeStringField("teacher", value.getTeacher().getFirstName() + " " + value.getTeacher().getLastName());
jgen.writeEndObject();
...
But interestingly, I am seeing "null null" for the teacher field. My Controller -> Service looks like this for the above operation:
CourseController.java
---------------------
#PostMapping("/{teacherId}")
public Course createCourse(#RequestBody Course course, #PathVariable("teacherId") Long teacherId) {
course.setTeacher(new Teacher(teacherId));
return courseService.createCourse(course);
}
CourseService.java
------------------
public Course createCourse(Course course) {
return courseRepository.save(course);
}
So...
Problem 1) While doing course.getTeacher() for the returned object I see only ID field is populated. I have also tried changing the fetch type for the teacher field in Course class, making a findOne(course.getId()) call after the save(...) operation but doesn't work. But interestingly I see the proper result if I make a GET request after the earlier POST call. So, why am I not able to see the complete data in Course during POST since it is mapped with Teacher already?
Problem 2) Delete operation on Course does not work when the courses are set to fetch in EAGER fashion under Teacher class but works fine for LAZY fetch.

First, you don't need to use all those JSON properties if you use DTOs to receive and response.
course.setTeacher(new Teacher(teacherId)); this is not a good practice at all
use one and point to create the course, other to create the teacher.
after that, you can create a put method to update the course with the teacher
#PutMapping("/{courseId}/{teacherId}")
public Course addTeacherToCourse( #PathVariable("courseId") Long courseId,#PathVariable("teacherId") Long teacherId) {
return courseService.addTeacherToCourse(courseId,teacherId);
}
the course service should validate the courseID and teacherID and if they exist then update and save them.
here you can look at my example based on your code.

Related

Best way to save and update entity Parent and child using spring data jpa

I'm facing some behaviour about spring data jpa and I need to understand.
consider this :
Document
#Getter
#Setter
#ToString
#Entity
#Table(name = "document")
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Document{
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
private String title;
private String description;
#OneToMany(cascade = ALL, fetch = FetchType.LAZY, mappedBy = "document")
#ToString.Exclude
private Set<Template> templates = new HashSet<>();
public void addTemplates(Template template) {
templates.add(template);
template.setDocument(this);
}
}
Template
#Getter
#Setter
#ToString
#Entity
#Table
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class Template{
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
private String name;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "document")
private Document document;
}
Service
#RequiredArgsConstructor
#Service
#Transactional
#Slf4j
public class DocumentService {
private final DocumentRepository documentRepository;
private final TemplateRepository templateRepository;
public void createTemplate() {
Set<Template> template = new HashSet<>();
template.add("template1");
template.add("template2");
templateRepository.saveAll(template);
}
public void createMovie(DocumentDTO documentRequest) {
Set<Template> templates = toTemplate(documentRequest.getTemplates());
Document document = Document.builder()
.title(documentRequest.getTitle())
.description(documentRequest.getDescription())
.template(new HashSet<>())
.build();
templates.forEach(document::addTemplates);
documentRepository.save(document);
}
private Set<Template> toTemplate(TemplateDTO templatesDTO) {
return documentRequest.getTemplates().stream().map(templateDTO ->
Template.builder()
.id(templateDTO.getId())
.firstName(templateDTO.getFirstName())
.lastName(templateDTO.getLastName())
.build()
).collect(Collectors.toSet());
}
}
First => I created the template
And When I created a new document for instance
{
"title": "tre",
"description": "opppp",
"template": [
{
"id": 1,
"name": "template1"
},
{
"id": 2,
"name": "template2",
}
]
}
with this data configuration I'm getting this error detached entity passed to persist. So to resolve this error I put CASCADE Merge instead ALL on Parent like this
#OneToMany(cascade = MERGE, fetch = FetchType.LAZY, mappedBy = "document")
#ToString.Exclude
private Set<Template> template= new HashSet<>();
again I try to save document. document are ok but nothing happen in template. this is the sql in console
Hibernate: insert into document (id, description, title) values (null, ?, ?)
Why the child is not update with document ID ?? Because the Entity manager of spring jpa call persist instead merge.
Is there a way to save without having to explicitly call merge ??? because if I call a merge I'm getting a bunch of select child before update. So How Can I avoid this problem.
Couple of observations, when there is a bidirectional mapping, JoinColumn annotation at the child entity is unnecessary.
Always rely on persist, persistAndFlush or persistAllAndFlush instead of save as it persist only if there is new entity else it will try to merge. This is where you are able to see SQL statement generated is only on the parent entity and not on the child entity.
Refer,
https://vladmihalcea.com/best-spring-data-jparepository/
I think is helped some one. please follow Vlad blog. He explain why we need to add Version to avoid a multiple Select. https://vladmihalcea.com/jpa-persist-and-merge/
So in my case I add somethink like this in my child entity:
#Version
private Long version;

JPA ManyToOne returning complete parent entity vs id

I have a springboot application with JPA. The ORM setup is a ManyToOne and I have roughly followed the excellent post here from Vlad so that I have only setup the #ManyToOne on the child.
My Entities are HealthCheck (Many) which must have a Patient (One). The issue is that when I retrieve a HealthCheck via my Rest controller instead of getting just the id of the patient I get the whole entity.
For my project I probably will get the whole patient with a HealthCheck, but I would like to know how i could get just the HealthCheck with the patient_id instead of the whole patient entity if I so needed to do so.
HEALTH CHECK
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Data
public class HealthCheck {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Patient patient;
//Getters and Setters
PATIENT
#Entity
#Data
public class Patient {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(unique = true)
#NotEmpty(message = "Name must not be null or empty")
private String name;
// Getters and Setters
The HealthCheckServiceImpl uses the derived queries to get one by id, and its this call thats used to get a HealthCheck by the REST controller:
#Override
public HealthCheck get(Long id) {
log.info("Getting a single Health Check");
return healthCheckRepository.getById(id);
}
The result of a call to the REST controller results in something like:
{
"id": 2,
"patient": {
"id": 2,
"name": "Jono",
"hibernateLazyInitializer": {}
},
"other fields": "some comment",
"hibernateLazyInitializer": {}
}
Note1 the whole patient entity is returned
Note2 that because I have only used the ManyToOne annottaion on the child side I dont get the Jackson recursion issues some others do
QUESTION: How can I control the returned HealthCheck so that it includes the patient_id not the whole object?
UPDATE
The line that calls to the service is :
#GetMapping("get/{id}")
public ResponseEntity<HealthCheck> getHealthCheck(#PathVariable("id") Long id) {
return ResponseEntity.ok()
.header("Custom-Header", "foo")
.body(healthCheckService.get(id));
I breakpoint on healthCheckService.get(id)) but noting on the debugger looks like it contains an entity reference:
UPDATE2
Well, it seems you're returning your entities objects directly from your controller.
The issue is that when I retrieve a HealthCheck via my Rest controller instead of getting just the id of the patient I get the whole entity.
You can use the DTO Pattern explained here and here as response in your controller.
That, also decouples your domain from your controller layer.
This is a simplified example:
#Data
#NoArgsConstructor
public class HealthCheckDto {
private Long id;
private PatientDto patient;
private String otherField;
public HealthCheckDto(HealthCheck healthCheck) {
this.id = healthCheck.getId();
this.patient = new PatientDto(healthCheck.getPatient());
this.otherField = healthCheck.getOtherField();
}
}
#Data
#NoArgsConstructor
public class PatientDto {
private Long id;
public PatientDto(Patient patient) {
this.id = patient.getId();
}
}
// In your controller
public ResponseEntity<HealthCheckDto> getHealthCheck(Long id) {
HealthCheck healthCheck = healthCheckService.getById(id);
return ResponseEntity.ok(new HealthCheckDto(healthCheck));
}

JPA Hibertane unidirectional one-to-one with shared primary key save parent first NO REVERSE

I'v been searching internet for answer, but nothing was working for me. There is a lot of topics with similar cases but specific details are different in a way that make them unusable for me.
So I have two tables: t_item and t_item_info:
item_id field from t_item_info table references id field from t_item table. I'm using mysql db and id column form t_item is auto incremented
I need to make unidirectional one-to-one mapping in a specific way. Here are my classes:
#Entity
#Table(name = "t_item")
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "item_id")
private ItemInfo info;
}
And other one
#Entity
#Table(name = "t_item_info")
public class ItemInfo {
#Id
private Long itemId;
private String descr;
}
So the point is that i need Item object to have a reference to ItemInfo object. NOT The other way!
Item -> ItemInfo --YES
Item <- ItemInfo --NO
The other thing is that i need parent (Item) id to become id for a child (ItemInfo)
For example I create Item object with null id and set it's info field with ItemInfo object which also have null id field. Like this:
{
"id": null,
"name": "Some name",
"info": {
"itemId": null,
"descr": "some descr"
}
}
Then when Item object persists hibernate should generate id for parent(Item) object and set it as itemId field for child(ItemInfo).
I have been trying to achieve this with different hibernate annotations and I noticed that no matter how hard I tried Hibernate always seems to try to persist child object first. I noticed it in the logs when I turned sql logging on. insert into t_item_info always goes first (and dies because of null id :D)
So the question is: Is it even possible to achieve this and if so what should I change in my code to do so
I hope that what I'm trying to ask makes sens to you given my poor explanations =)
Why people always insist the child object table in one-to-one associations should be the one with the foreign key is beyond me.
Anyway, as a workaround, since both objects share the id and the association is non-optional, you might as well declare the autogenerated key for the child object:
#Entity
#Table(name = "t_item_info")
public class ItemInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long itemId;
private String descr;
}
and then use #MapsId for the parent:
#Entity
#Table(name = "t_item")
public class Item {
#Id
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "id")
#MapsId
private ItemInfo info;
}
Note that this approach, will, in a sense, fool Hibernate into thinking it is the Item that should be treated as the child object. You have been warned.
While there is an accepted answer here it looks to me like #Secondary table would be a better and more convenient solution: you may have 2 tables at the database level but I do not see any reason that that fact needs be exposed to any client code. There does not seem to be a lot of benefit to that? The following gives you a simpler API.
Entity:
#Entity
#Table(name = "t_item")
#SecondaryTable("t_item_info", pkJoinColumns={
#PrimaryKeyJoinColumn(name="id", referencedColumnName="item_id")})
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Colulumn(name = "description", table= "t_item_info"")
private String description;
}
API:
{
"id": null,
"name": "Some name",
"descr": "some descr"
}

JPA : How to create a self-join entity?

I have a user entity with an assistant column.
Every user has an assistant but there are circles as well.
For example : User A's assistant is User B and User B's assistant is
user A.
If I use #ManyToOne and #OneToMany annotations, then, there is an infinite recursion when converting objects to JSON, even #JsonManagedReference and
#JsonBackReference didn't help.
BaseEntity:
#MappedSuperclass
#Data
public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#Version
private int version;
}
User:
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Data
#EqualsAndHashCode(callSuper = true)
#Table(name = "Users")
public class User extends BaseEntity {
#Column
private String username;
#Column
private String name;
#JsonManagedReference
#ManyToOne
#JoinColumn(name = "assistant_id")
private User assistant;
#JsonBackReference
#OneToMany(mappedBy = "assistant")
private Set<User> assistants;
}
Are there any opportunity in Spring to solve this?
#JsonManagedReference/#JsonBackReference won't help, because the 'forward' references can still form a cycle, even when the 'inverse' references are not being serialized.
What you want is probably for the User assigned to the assistant property to be serialized without its own assistant property (so that any cycles break). Essentially, you have the same issue as here, except in your case A and B are the same class.
Apart from the solution described in the question I've linked to, you'll also want to specify which #JsonView to use when serializing the object. See the 'Using JSON Views with Spring' section here.
Could you create Assistant entity based on the same table and join?

EclipseLink how to delete orphans entities from the "one side" in a ManyToOne relationship

After searching an answer, I have seen the #PrivateOwner annotation but it doesn't solve my issue.
I'm using EclipseLink
here is the issue:
I have 2 Entities related with a 1:n bidirectional relationship.
A ComponentEntity can be related to one TransferDetailsEntity
A TransfertDetailsEntity is related to one or multiple ComponentEntity.
what I want to achieve is: when I delete the last Component referencing a TransfertDetails, this TransfertDetails should be deleted.
Same if I change the last reference to a TransfertDetails.
in short : As soon as a TransfertDetails is not referenced by any Component, it should be deleted.
As a workaround I call this method :
#Override
public void removeOrphanTransfer() {
for (TransferDetailsEntity transfer : transferDetailsRepository.findAll()) {
if (transfer.getComponents().isEmpty()) {
transferDetailsRepository.delete(transfer);
}
}
}
That works but it's not really efficient since it search through the entire table. and it's quite ugly...
here is the (simplified) code for Entities :
TransfertDetailsEntity:
#Entity
#Table(name = TransferDetailsEntity.TABLE_NAME)
#Getter
#Setter
#NoArgsConstructor
public class TransferDetailsEntity extends AbstractEntity {
[...]
#Id
#Column(name = ID, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
[...]
#OneToMany(mappedBy = "transferDetails")
private List<ComponentEntity> components;
[...]
}
ComponentEntity:
#Entity
#Table(name = ComponentEntity.TABLE_NAME, uniqueConstraints = #UniqueConstraint(name = ComponentEntity.TABLE_NAME
+ AbstractEntity.SEPARATOR + AbstractEntity.CONSTRAINT,
columnNames = { ComponentEntity.COLUMN_NAME_SERIAL, ComponentEntity.COLUMN_NAME_TYPE }))
#Getter
#Setter
#ToString(callSuper = true, exclude = { "parent" })
#NoArgsConstructor
public class ComponentEntity extends AbstractEntity {
[...]
#Id
#Column(name = ID, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
[...]
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = COLUMN_TRANSFER_DETAILS)
private TransferDetailsEntity transferDetails;
[...]
}
As mentioned earlier, #PrivateOwner on the #OneToMany annotation (in TransfertDetailsEntity) doesn't work...
any help appreciated
There is no automatic JPA or EclipseLink feature that will do this for you, your application will have to handle this.
The easiest I can think of is on removal of a ComponentEntity, get the referenced TransfertDetailsEntity and check its components list to see if it has other ComponentEntity references and remove it if it does not. You should be removing each ComponentEntity reference from the TransfertDetailsEntity.components list when you delete it anyway, so this list should be up to date and not incur any database hits.

Categories