Spring Data JPA save child object with the ID of parent object - spring

I have two objects, one parent and one child as follows :
#Entity
#Table(name="category")
public class CategoryModel {
private #Id #GeneratedValue Long id;
private String name;
#OneToMany(mappedBy="category", cascade=CascadeType.PERSIST)
private List<AttributeModel> attributes;
}
#Entity
#Table(name="attribute")
public class AttributeModel {
private #Id #GeneratedValue Long id;
private String name;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="category_id")
private CategoryModel category;
}
I also have dtos which maps to these model objects but I ommited them.
When I try to save a category object with this payload Attribute values are also created in the attribute table but with null category ids.
{
"name":"Chemicals",
"attributes":[
{"name": "volume"}, {"name":"humidity"}
]
}
What can I do to have my attribute values persisted into the database with the category id which is created before them?

First of all, this problem is not a "Spring Data JPA" problem, it is a JPA (probably Hibernate) problem.
Analysis
Since you left out the code for the controller and the JSON mapping, I have to guess a bit:
fact 1: The relationship between category and attributes is controlled by the attribute AttributeModel.category but not by CategoryModel.attributes. (That is how JPA works).
observation 2: Your JSON object define CategoryModel.attributes (i.e. opposite to how JPA works).
Without knowing your JSON mapping configuration and controller code, I would guess that the problem is: that your JSON mapper does not set the AttributeModel.category field when it deserialises the JSON object.
Solution
So you need to instruct the JSON mapper to set the AttributeModel.category field during deserialisation. If you use Jackson, you could use:
#JsonManagedReference and
#JsonBackReference
#Entity
#Table(name="category")
public class CategoryModel {
...
#JsonManagedReference
#OneToMany(mappedBy="category", cascade=CascadeType.PERSIST)
private List<AttributeModel> attributes;
}
#Entity
#Table(name="attribute")
public class AttributeModel {
...
#JsonBackReference
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="category_id")
private CategoryModel category;
}

I solved this by manually setting child object's reference to the parent object as follows :
public Long createCategory(CategoryDto categoryDto) {
CategoryModel categoryModel = categoryDto.toModel(true,true);
categoryModel.getAttributes().forEach(a -> a.setCategory(categoryModel));
return categoryRepository.save(categoryModel).getId();
}

Related

Spring JPA bidirectional relation on multiple nested entities

I know there has been multiple questions on bidirectional relations using spring jpa in the past but my case is a little bit different because i am using 3 entities with 2 relationships to implement a medical system
I have 3 entities : doctor/patient/appointment
here is the code for the 3 entities
please note all setters , getters and constructors implemented but ommited here for clarity
Patient class
#Entity
public class resPatient {
#Id
#GeneratedValue( strategy= GenerationType.IDENTITY )
private long code;
private String name;
private String gender;
private String email;
private String mobile;
private int age;
private String notes;
#OneToMany(mappedBy = "patient")
List<resPackageMembership> memberships;
#OneToMany(mappedBy = "patient")
List<resAppointment> appointments;
#OneToMany(fetch = FetchType.LAZY,mappedBy = "patient")
List<resMedImage> medImages;
Doctor class
#Entity
public class resDoctor {
#Id
#GeneratedValue( strategy= GenerationType.IDENTITY )
private long code;
private String name;
private String mobile;
private String email;
private String gender;
private int age;
private String speciality;
#OneToMany(mappedBy = "doctor")
List<resAppointment> appointments;
Appointment class
#Entity
public class resAppointment {
#Id
#GeneratedValue( strategy= GenerationType.IDENTITY )
private long code;
private String speciality;
#Basic
#Temporal(TemporalType.TIMESTAMP)
private Date dateCreated;
#Basic
#Temporal(TemporalType.TIMESTAMP)
private Date dateToVisit;
private String status;
private String notes;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "doctorCode")
private resDoctor doctor;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "patientCode")
private resPatient patient;
the way my medical system should work is that when i get a patient using my restful controller i want all the patient data including his appointments but this leads to an infinite loop as the appointment has the doctor which also has appointments and so on.
i cannot user #JSONIGNORE as there are 2 relationships i want to get the patient with his appointments which should have the doctor without the appointments array and should not have any patient data as i already am in the patient object
As a general best-practice, it's recommended to separate the entities from the data transfer objects used for the rest controllers. With DTO's in place, you have more control on which data to include and serialize within them to avoid the circlular references.
If you like check out https://bootify.io, it generates the DTOs from your database schema, but the custom endpoint you still need to define/build.
I develop an annotation processor called beanknife recently, it support generate DTO from any class. You need config by annotation. But you don't need change the original class. This library support configuring on a separate class. Of course you can choose which property you want and which you not need. And you can add new property by the static method in the config class. For your question:
// this will generate a DTO class named "resPatientView".
// You can change this name using genName attribute.
#ViewOf(value=resPatient.class, includePattern = ".*")
public class PatientViewConfigure {
// here tell the processor to automatically convert the property appointments from List<resAppointment> to List<resAppointmentWithoutPatient>.
// resAppointmentWithoutPatient is the generated class configured at the following.
// Note, although at this moment it not exists and your idea think it is an error.
// this code really can be compiled, and after compiled, all will ok.
#OverrideViewProperty("appointments")
private List<resAppointmentWithoutPatient> appointments;
}
// here generated a class named resAppointmentWithoutPatient whick has all properties of resAppointment except patient
#ViewOf(value=resAppointment.class, genName="resAppointmentWithoutPatient", includePattern = ".*", excludes={"patient"})
public class AppointmentWithoutPatientViewConfigure {
// the doctor property will be converted to its dto version which defined by the configure class DoctorWithoutAppointmentsViewConfigure.
#OverrideViewProperty("doctor")
private resDoctorWithoutAppointments doctor;
}
// here we generate a class which has all properties of resDoctor except appointments
#ViewOf(value=resDoctor.class, genName="resDoctorWithoutAppointments", includePattern = ".*", excludes={"appointments"})
public class DoctorWithoutAppointmentsViewConfigure {}
// in you rest controller. return the dto instead of the entities.
resPatient patient = ...
resPatientView dto = resPatientView.read(patient);
List<resPatient> patients = ...
List<resPatientView> dto = resPatientView.read(patients);
At the end, the class resPatientView will has the same shap with resPatient except its appointments not having patient property and its doctor property is replaced with a version without appointments property.
Here are more examples.
The version 1.10 is ready. Will fix some bug and support the configure bean to be managed by spring.

Passing parent id reference when creating child object through REST api

I am using spring boot (version - 2.1.1). I have a one to many database model exposed for CRUD operations through rest api's. The model looks as below. How do I configure the POST /departments api (that creates a department object) to accept just the organization id in the input json body?
#PostMapping
public Long createDepartment(#RequestBody Department Department) {
Department d = departmentService.save(Department);
return d.getId();
}
Note - I do not want to allow creating organization object when creating a department.
Model object mapping
#Entity
#Table(name="ORGANIZATIONS")
public class Organization{
#Id
#GeneratedValue
Private long id;
#Column(unique=true)
Private String name;
#OneToMany(mappedBy = "organization", fetch = FetchType.EAGER)
private List<Department> departments;
}
#Entity
#Table(name="DEPARTMENTS")
Public class Department{
#Id
#GeneratedValue
Private long id;
#Column(unique=true)
Private String name;
#ManyToOne(fetch = FetchType.EAGER)
private Organization organization;
}
Thanks!
The easiest and most sane way in my opinion is to utilize the DTO (Data Transfer Object) pattern.
Create a class that represent the model you want to get as your input:
public class CreateDepartmentRequest {
private long id;
// getters and setters
}
Then use it in your controller:
#PostMapping
public Long createDepartment(#RequestBody CreateDepartmentRequest request) {
Department d = new Department();
d.setId(request.getId());
Department d = departmentService.save(d);
return d.getId();
}
Side note, its better to ALWAYS return JSON through REST API (unless you use some other format across your APIs) so you can also utilize the same pattern as I mentioned above to return a proper model as a result of the POST operation or a simple Map if you don't want to create to many models.

Spring JPA saving distinct entities with composite primary key not working as expected, updates same entity

I have a logic that saves some data and I use spring boot + spring data jpa.
Now, I have to save one object, and after moment, I have to save another objeect.
those of object consists of three primary key properties.
- partCode, setCode, itemCode.
let's say first object has a toString() returning below:
SetItem(partCode=10-001, setCode=04, itemCode=01-0021, qty=1.0, sortNo=2, item=null)
and the second object has a toString returning below:
SetItem(partCode=10-001, setCode=04, itemCode=01-0031, qty=1.0, sortNo=2, item=null)
there is a difference on itemCode value, and itemCode property is belonged to primary key, so the two objects are different each other.
but in my case, when I run the program, the webapp saves first object, and updates first object with second object value, not saving objects seperately.
(above image contains different values from this post question)
Here is my entity information:
/**
* The persistent class for the set_item database table.
*
*/
#Data
#DynamicInsert
#DynamicUpdate
#Entity
#ToString(includeFieldNames=true)
#Table(name="set_item")
#IdClass(SetGroupId.class)
public class SetItem extends BasicJpaModel<SetItemId> {
private static final long serialVersionUID = 1L;
#Id
#Column(name="PART_CODE")
private String partCode;
#Id
#Column(name="SET_CODE")
private String setCode;
#Id
#Column(name="ITEM_CODE")
private String itemCode;
private Double qty;
#Column(name="SORT_NO")
private int sortNo;
#Override
public SetItemId getId() {
if(BooleanUtils.ifNull(partCode, setCode, itemCode)){
return null;
}
return SetItemId.of(partCode, setCode, itemCode);
}
#ManyToMany(fetch=FetchType.LAZY)
#JoinColumns(value = {
#JoinColumn(name="PART_CODE", referencedColumnName="PART_CODE", insertable=false, updatable=false)
, #JoinColumn(name="ITEM_CODE", referencedColumnName="ITEM_CODE", insertable=false, updatable=false)
})
private List<Item> item;
}
So the question is,
how do I save objects separately which the objects' composite primary keys are partially same amongst them.
EDIT:
The entity extends below class:
#Setter
#Getter
#MappedSuperclass
#DynamicInsert
#DynamicUpdate
public abstract class BasicJpaModel<PK extends Serializable> implements Persistable<PK>, Serializable {
#Override
#JsonIgnore
public boolean isNew() {
return null == getId();
}
}
EDIT again: embeddable class.
after soneone points out embeddable class, I noticed there are only just two properties, it should be three of it. thank you.
#Data
#NoArgsConstructor
#RequiredArgsConstructor(staticName="of")
#Embeddable
public class SetGroupId implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#NonNull
private String partCode;
#NonNull
private String setCode;
}
Check howto use #EmbeddedId & #Embeddable (update you might need to use AttributeOverrides in id field, not sure if Columns in #Embeddable works).
You could create class annotated #Embeddable and add all those three ID fields there.
#Embeddable
public class MyId {
private String partCode;
private String setCode;
private String itemCode;
}
Add needed getters & setters.
Then set in class SetItem this class to be the id like `#EmbeddedId´.
public class SetItem {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name="partCode",
column=#Column(name="PART_CODE")),
#AttributeOverride(name="setCode",
column=#Column(name="SET_CODE"))
#AttributeOverride(name="itemCode",
column=#Column(name="ITEM_CODE"))
})
MyId id;
Check also Which annotation should I use: #IdClass or #EmbeddedId
Be sure to implement equals and hashCode in SetGroupId.
Can you provide that class?

Spring Data Load All lazy initialized Child Tables from Parent

I have a parent Entity with 2 child entities that are lazily loaded. I would like to load all the associated child entities when the parent entity is loaded
#Entity
public class Author {
#Id
#GeneratedValue
private Long id;
private String firstName;
private String lastName;
#OneToMany(mappedBy ="author",cascade= CascadeType.ALL,fetch=FetchType.LAZY)
private List<Post> posts;
#OneToMany(mappedBy ="author",cascade= CascadeType.ALL,fetch=FetchType.LAZY)
private List<Book> books;
}
Load all Books and Posts based on the Author firstname by using Dynamic query.
public interface AuthorRepository extends CrudRepository<Author, Long> {
public List<Author>findByFirstNameAndPostsAndBooks()
}
The Above Findby does not work, please assist me to construct the correct Query.
Also i am trying to avoid #Query or QueryDSL for the time being
To fetch authors by their firstname you should change you query to this:
public List<Author>findByFirstName(String firstname);
JPA will load the authors posts and books automatically because of your #OneToMany annotations in your Author class.
Add spring.jpa.properties.hibernate.enable_lazy_load_no_trans=tr‌​ue to your application.properties file to enable lazy loading without transactional annotations.

best way to save child resource Spring Data REST

I have two entities:
#Entity
public class Father{
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "father",cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Child> children;
// other
}
and
#Entity
public class Child{
#Id
#GeneratedValue
private Long id;
private String name;
#ManyToOne(fetch = FetchType.EAGER)
private Father father;
// other
}
By using Spring Data REST i can save entity:
Create Father by POST
{
"name":"Father1"
}
And then create child by POST http://localhost:8080/children/
{
"name":"Child1",
"father":"http://localhost:8080/fathers/1"
}
Or i can save two independent entities and binds the resource pointed to by the given URI(s) to the resource.
Wich way is the best?
And i can't understand this: can i add child to father by:
curl -X PUT -H "ContentType: text/uri-list" http://localhost:8080/children/1 http://localhost:8080/fathers/1/children
At first you created father entity and saved it.
Then you want to bind new child to your child, you will the following in your child create request
{
"father.id": 1,
"name":"SomeName"
}
Spring will automatically convert to father entity when request will be received.

Resources