how to save bidirectional nested object in spring boot - spring

This is my invoice Entity
#OneToMany( fetch = FetchType.LAZY, mappedBy = "invoice", cascade = CascadeType.ALL )
#ToString.Exclude
private List< LineItem > lineItems;
#ManyToOne
#ToString.Exclude
#JoinColumn( name = "invoice_id", referencedColumnName = "id" )
private Invoice invoice;
Here i dont want to update all the fields in lineItem object which is coming from the front end while iterating
i want set two new fields(setInvoice,setUUID) ... how it is possible
String uuid = UUID.randomUUID().toString();
invoice.setId( uuid );
invoice.setIsActive( 1 );
invoice.setCreatedBy( "userId" );
invoice.setLastUpdatedBy( "userId" );
List< LineItem > lineItemsLists = new ArrayList<>();
LineItem item = new LineItem();
List< LineItem > lineItems = invoice.getLineItems();
if(lineItems.size()>0){
lineItems.forEach( lineItem -> {
String lineItemUUID = UUID.randomUUID().toString();
item.setId( lineItemUUID );
item.setInvoice( invoice );
item.setDescription( lineItem.getDescription() );
lineItemsLists.add( item );
} );
}
invoice.setLineItems( lineItemsLists );
Invoice save = invoiceRepository.save( invoice );

Related

Hibernate/Kotlin/Spring - ManyToMany cant delete items

I have two tables that are linked with #ManyToMany .I want that when deleting items that it deletes only own items and not the connected ones. I have 2 Classes. In Class "A" there is no issue , it deletes only its own items and not the connected one (the ones that represent class B). But when i delete items from class "B" i am getting error that is not possible because of foreign key. I have tried to use CascadeType.REMOVE , which will work but it will also delete the connected items from class "A" which i don't want to.
Class A:
#Entity
#TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)
data class Student (
.....
#ManyToMany( fetch = FetchType.LAZY , cascade = [
CascadeType.PERSIST,
CascadeType.MERGE
])
#JoinTable(name = "student_teacher",
joinColumns = [JoinColumn(name = "student_id", referencedColumnName = "id")],
inverseJoinColumns = [JoinColumn(name = "teacher_id", referencedColumnName = "id")]
)
#JsonIgnore
val teachers: MutableSet<Teacher> ,
)
{
val teachersList: MutableSet<Teacher>
get() = teachers
}
Class B:
#Entity
#TypeDef(name = "list-array", typeClass = ListArrayType::class)
data class Teacher(
.....
){
#ManyToMany(mappedBy = "teachers",fetch = FetchType.LAZY)
#JsonIgnore
val students: MutableSet<Student> = MutableSet<Student>()
}
Got it work this way:
#Entity
#TypeDef(name = "list-array", typeClass = ListArrayType::class)
data class Teacher(
.....
){
#ManyToMany(mappedBy = "teachers",fetch = FetchType.LAZY)
#JsonIgnore
val students: MutableSet<Student> = MutableSet<Student>()
#PreRemove
fun removeTeacher() {
for (student in students) {
student.teachersList.remove(this)
}
}
}

Update the Foreign Key with JPA

I created 2 entities :
#Entity
#Table(name="products")
public class ProductEntity {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String productKeyId;
// many to one relationship with category
#ManyToOne
#JoinColumn(name = "category_id")
private CategoryEntity category;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private double price;
#Column(nullable = false)
private int qty;
private String imgPath;
// getters & setters
}
And :
#Entity
#Table(name="categories")
public class CategoryEntity {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#Column(length = 30, nullable = false)
private String categoryKeyId;
#Column(nullable = false)
private String name;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name="parent_id", nullable=true)
private CategoryEntity parentCategory;
// allow to delete also subcategories
#OneToMany(mappedBy="parentCategory", cascade = CascadeType.ALL)
private List<CategoryEntity> subCategories;
//Here mappedBy indicates that the owner is in the other side
#OneToMany(fetch = FetchType.EAGER, mappedBy = "category", cascade = CascadeType.REMOVE)
private List<ProductEntity> products;
}
I have datas in the database generated :
Here is my Product table
And the category table
My issue is the following. I use a REST API to update the product and the category (if needed).
{
"name": "Pizza12",
"price": 25.0,
"qty": 15,
"imgPath": "anotherpathImage",
"category": {
"categoryKeyId": "VMz7EM6tNfoOAQtO1SHPYcH14jj0Cy",
"name": "Fish"
}
}
In my service I try to update both part separatelly :
#Override
public ProductDto updateProduct(String productKeyId, ProductDto productDto) {
// create a return object of type Product
ProductDto returnValue = new ProductDto();
// create Entity objects to request on the database
ProductEntity productEntity = productRepository.findByProductKeyId(productKeyId);
CategoryEntity categoryEntity = categoryRepository.findCategoryEntityByProductKeyId(productKeyId);
ModelMapper modelMapper = new ModelMapper();
if (productEntity == null)
throw new ApplicationServiceException(ErrorMessages.NO_RECORD_FOUND.getErrorMessage());
productEntity.setProductKeyId(productKeyId);
productEntity.setName(productDto.getName());
productEntity.setPrice(productDto.getPrice());
productEntity.setQty(productDto.getQty());
productEntity.setImgPath(productDto.getImgPath());
// update the category
CategoryEntity updatedCategory = categoryRepository.save(categoryEntity);
productEntity.setCategory(updatedCategory);
// productEntity.setCategory(categoryEntity);
System.out.println("product entity : " + productEntity.toString());
ProductEntity updatedProduct = productRepository.save(productEntity);
updatedProduct.setCategory(updatedCategory);
returnValue = modelMapper.map(updatedProduct, ProductDto.class);
return returnValue;
}
Unfortunatelly, it doesn't seem to work as expected. The product is updated, the category remains the same.
I finally solved my Issue thanks to Janar and Repoker.
#Override
public ProductDto updateProduct(String productKeyId, ProductDto productDto) {
// create a return object of type Product
ProductDto returnValue = new ProductDto();
// create Entity objects to request on the database
ProductEntity productEntity = productRepository.findByProductKeyId(productKeyId);
CategoryEntity categoryEntity = categoryRepository.findByCategoryKeyId(productDto.getCategory().getCategoryKeyId());
//CategoryEntity categoryEntity = categoryRepository.findCategoryEntityByProductKeyId(productKeyId);
ModelMapper modelMapper = new ModelMapper();
if (productEntity == null)
throw new ApplicationServiceException(ErrorMessages.NO_RECORD_FOUND.getErrorMessage());
productEntity.setProductKeyId(productKeyId);
productEntity.setName(productDto.getName());
productEntity.setPrice(productDto.getPrice());
productEntity.setQty(productDto.getQty());
productEntity.setImgPath(productDto.getImgPath());
// update the category
CategoryEntity updatedCategory = categoryRepository.save(categoryEntity);
productEntity.setCategory(productEntity.getCategory());
// productEntity.setCategory(categoryEntity);
System.out.println("product entity : " + productEntity.toString());
ProductEntity updatedProduct = productRepository.save(productEntity);
updatedProduct.setCategory(updatedCategory);
returnValue = modelMapper.map(updatedProduct, ProductDto.class);
return returnValue;
}
I was not persisting the new values entered but the values that were initially set...

Hibernate saves child entity with null parent id

Hibernate doesn't want to save IDs for child entities. I have the following tables:
#Entity
#Table(name = "ct_orders")
data class Order(
#Id
#Column(name = "id")
#GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
val id: Int = 0,
#OneToMany(fetch = FetchType.LAZY, cascade = arrayOf(CascadeType.ALL), mappedBy = "order")
val route: List<Route>? = null,
...
)
#Entity
#Table(name = "ct_routes")
#JsonIgnoreProperties("id", "order")
data class Route(
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int = 0,
#Column
val location: Point = GeoHelpers.point(),
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
val order: Order? = null,
#Column
val title: String = ""
)
ct_routes saving with null in order_id. Is there some problem with relationships? Or, may be there is something wrong in my code?
Here is the part of code, which saves an Order entity:
val order = orderRepository.save(Order(
...
route = GeoHelpers.placesListToEntities(data.places),
...
))
fun placesListToEntities(points: List<PlaceDto>) = points.map {
Route(
location = Helpers.geometry(it.location.latitude, it.location.longitude),
title = it.title
)
}
You're modeling bidirectional #OneToMany and as shown in the example in the documentation you're responsible for setting the parent value on the child entity:
val order = orderRepository.save(Order(...).apply{
...
route = GeoHelpers.placesListToEntities(this, data.places),
...
})
fun placesListToEntities(order:Order, points: List<PlaceDto>) = points.map {
Route(
order = order,
location = Helpers.geometry(it.location.latitude, it.location.longitude),
title = it.title
)
}
PS. Since Route is an entity you could change your model a bit to enforce the constraints on the langauge level i.e:
class Route internal constructor() {
lateinit var order: Order
constructor(order: Order) : this() {
this.order = order
}
}
See this question for more details.

Hibernate OneToOne bidirectional foreign key on insert

I'm having a problem when calling insert using hibernate. The foreign key is not being passed down to the child on the OneToOne mappings, but it's working fine for the OneToMany mappings.
School.java
private long schoolId;
private set<Student> students;
private Principal principal;
#Id
#Column(name = "SCHOOL_ID", unique = true, nullable = false, precision = 15, scale = 0)
public long getSchoolId() {
return schoolId;
}
public void setSchoolId( long schoolId ) {
this.schoolId = schoolId;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "school")
public set<Student> getStudents() {
return students;
}
public void setStudents( set<Student> students ) {
this.students = students;
}
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "school")
public Principal getPrincipal() {
return principal;
}
public void setPrincipal( Principal principal ) {
this.principal = principal;
}
Student.java
private long studentId;
private School school;
other data....
#Id
#Column(name = "STUDENT_ID", unique = true, nullable = false, precision = 15, scale = 0)
public long getStudentId() {
return studentId;
}
public void setStudentId( long studentId ) {
this.studentId = studentId;
}
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SCHOOL_ID", nullable = false)
public School getSchool() {
return school;
}
public void setSchool( School school ) {
this.school = school;
}
Principal.java
private long principalId;
private School school;
//other data....
#Id
#Column(name = "PRINCIPAL_ID", unique = true, nullable = false, precision = 15, scale = 0)
public long getPrincipalId() {
return principalId;
}
public void setPrincipalId( long principalId ) {
this.principalId = principalId;
}
#JsonIgnore
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SCHOOL_ID", nullable = false)
public School getSchool() {
return school;
}
public void setSchool( School school ) {
this.school = school;
}
With this example, when i try to save an School object, the hibernate would call insert on School, then Students, then Principal. When it calls insert on all the students, hibernate calls
insert into STUDENT ( SCHOOL_ID, STUDENT_ID ) values (?, ?)
which is correct. But when it tries to call insert on the principal, hibernate calls
insert into PRINCIPAL ( PRINCIPAL_ID) values (?)
which causes
ORA-01400: cannot insert NULL into ("PRINCIPAL"."SCHOOL_ID")
since the foreign key for the School object is not being inserted. I don't understand why it inserts the foreign key for the OneToMany tables, but not the OneToOne tables. Does anyone know how to fix this? these tables are bi-directional.
Also, I'm have a controller that takes in the School object and save to DB. The object looks like this
{
"name" : "data",
"students" : [ {"name", "data"}],
"principal" : {"name", "data"}
}
when I receive this school object, do I have to loop through the child and set the parent to school? because in this example it's only 2 levels, but I would need to build 4-5 levels, and would not like to loop all the way down to set each of the parents. I don't have to use bi-directional, if uni-directional work, I would do that.
Add mappedBy to the OneToOne annotation in Principal class:
#OneToOne(mappedBy="principal", fetch = FetchType.LAZY)

Querying composite table in Hibernate

I am working on a Spring-MVC application where I have a many-to-many relationship in which I have to query in 2 tables to get the values I require. I will explain in more detail.
I have 2 tables GroupAccount, GroupMembers with many-to-many
relationship. Now there is a junction table called membertable where
id from GroupMembers and GroupAccount is stored.
This is what I am looking for :
I pass a groupAccounId and username as parameters. Now, in the
GroupMembers table, there is a username stored. In groupAccount,
there is groupAccountId is stored.
Now in the memberjunction, I have composite key
memberid,GroupAccountId, I would like the member id for the username
which has a matching groupAccountId I submit.
Below is the SQL code and Spring-mvc code to understand more better.
CREATE TABLE public.groupaccount (
groupid NUMERIC NOT NULL,
groupname VARCHAR,
groupaccountstatus BOOLEAN DEFAULT false NOT NULL,
adminusername VARCHAR,
CONSTRAINT groupid PRIMARY KEY (groupid)
);
CREATE TABLE public.groupmembers (
memberid INTEGER NOT NULL,
musername VARCHAR
CONSTRAINT memberid PRIMARY KEY (memberid)
);
CREATE TABLE public.memberjunction (
memberid INTEGER NOT NULL,
groupid NUMERIC NOT NULL,
CONSTRAINT membergroupid PRIMARY KEY (memberid, groupid)
);
GroupMembersDAOImpl :#
#Override
public List<Integer> returnMemberIdWithMatchingUsername(String memberUsername) {
session = this.sessionFactory.getCurrentSession();
org.hibernate.Query query = session.createQuery("From GroupMembers as " +
"n where n.memberUsername=:memberUsername");
query.setParameter("memberUsername",memberUsername);
List<GroupMembers> memberList = query.list();
List<Integer> memberIdList = new ArrayList<>();
for(GroupMembers members :memberList){
memberIdList.add(members.getMemberid());
}
return memberIdList;
}
GroupAccount model :
#Entity
#Table(name="groupaccount")
public class GroupAccount {
#Id
#Column(name="groupid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "groupaccount_seq_gen")
#SequenceGenerator(name = "groupaccount_seq_gen",sequenceName = "groupaccount_seq")
private Long groupId;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "memberjunction", joinColumns = {#JoinColumn(name = "groupid")},
inverseJoinColumns = {#JoinColumn(name = "memberid")})
private Set<GroupMembers> groupMembersSet = new HashSet<>();
public void setGroupMembersSet(Set<GroupMembers> groupMembersSet){
this.groupMembersSet = groupMembersSet;
}
}
GroupMembers model class :
#Entity
#Table(name="groupmembers")
public class GroupMembers {
#Id
#Column(name="memberid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "groupmembers_seq_gen")
#SequenceGenerator(name = "groupmembers_seq_gen",sequenceName = "groupmembers_seq")
private int memberid;
#ManyToMany(mappedBy = "groupMembersSet")
private Set<GroupAccount> groupAccounts = new HashSet<>();
public void setGroupAccounts(Set<GroupAccount> groupAccounts){
this.groupAccounts = groupAccounts;
}
public Set<GroupAccount> getGroupAccounts(){
return this.groupAccounts;
}
}
Query I am using :
#Override
public int getMemberIdForCanvas(String memberUsername, Long groupId) {
session = this.sessionFactory.getCurrentSession();
org.hibernate.Query query = session.createQuery("select distinct m.memberId from GroupMembers m\n" +
"join m.groupAccounts a\n" +
"where a.memberUsername = :userName and m.groupId=:groupId");
query.setParameter(memberUsername,"memberUsername");
query.setParameter(String.valueOf(groupId),"groupId");
int memberid = (Integer)query.uniqueResult();
return memberid;
}
Any help would be nice. Thanks a lot.
Here's the documentation for joins and HQL. Please read it.
The query is as simple as
select distinct m.memberId from GroupMembers m
join m.groupAccounts a
where a.memberUsername = :userName
Please also fix your naming. A GroupMembers instance is a single group member. So the class should be named GroupMember, without s. Repeating the name of the class in the fields of this class is also redundant: member.getId() is more readable and less verbose than member.getMemberId(). Same for the other fields.

Resources