Which side of bidirectional mapping should be persisted - java

I was looking for an answer on SO and several articles, but I think clear explanation is unfortunately missed, so I decided to ask.
Say we have two entities (box contains several items):
#Entity
#Table(name = "box")
class Box
{
#Id
private Long id;
#OneToMany(mappedBy = "box")
private List<Item> items = new LinkedList<>();
public void addItem(Item item)
{
items.add(item);
item.setBox(this);
}
// setters, getters, delete synchronize method
}
#Entity
#Table(name = "item")
class Item
{
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "box_id")
private Box box;
// setters getters
}
Which side of the relationship should I persist?
Does it depend on the owning side of the relationship?
Does it depend on the cascade type and owning side has nothing in common?
Place CascadeType.PERSIST in the Box entity, then em.persist(box)
Place CascadeType.PERSIST in the Item entity, then em.persist(item)
Both, and persist is up to me - can be em.persist(box) or em.persist(item)

You can persist the relationship from both sides.
If you persist the box entity, you will only have to call the persist method of the entity manager once and pass the box entity as a parameter, with the item entity list, the box items parameter will have to have a cascade type.
If you persist the item entity, you will have to call the persist method of the entity manager N times, being N the number of items, passing the Item entity with the box parameter, the box parameter will have to have a cascade type. In the first insertion Box will not have ID, but once you insert the first Item, Box will have ID so in the following N-1 Items you will have to establish that Box with Id since if not, it will create N boxes.
Based on all this I think that in most logics it is better to persist the parent entity, cob cascade type persist in the property that represents the relation with the daughter class.

Related

When I try delete a entity with Hibernate, there isn't a error message but doesn't delete

I have three entities, Bag, Item and Category. One Bag can have Many Items and Many Items can have Many Categories:
Bag <----OneToMany----> Item
Item -----ManyToMany-----> Category
It isn't necessary store items in Category
When I try delete a Item without categories, there isn't any error message, but the Item isn't deleted. However if i try delete the same item in DataBase, it is deleted successfully.
On the other hand, if i try delete a Item with categories, i receive this error message:
"Cannot delete or update a parent row: a foreign key constraint fails (item_categories, CONSTRAINT fk_item_categories_items_id FOREIGN KEY (items_id) REFERENCES item (id))".
I would like delete the item and the relation with his categories, but keep categories. How could I do this?
For the first problem, I have tried add to the delete method bag.removeItems(item), but it only deletes tthe relationship between both and the item continues in DB.
Bag.java
#OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JsonIgnoreProperties("bag")
private Set<Item> items = new HashSet<>();
Item.java
#ManyToOne
#JsonIgnoreProperties("items")
private Bag bag;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "item_categories",
joinColumns = #JoinColumn(name="items_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="categories_id", referencedColumnName="id"))
private Set<Category> categories = new HashSet<>();
ItemResource.java
#DeleteMapping("/items/{id}")
#Timed
public ResponseEntity<Void> deleteItem(#PathVariable Long id) {
itemRepository.delete(id);
return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(ENTITY_NAME, id.toString())).build();
}
EDIT:
The methods of create and update works fine.
So the problem is that you a many-to-many relation which is solved by intermediate (or association) table, where the pairs item - category are stored.
When you delete the Item, an actual DELETE FROM item query is executed, but it fails since there is a foreign key constraint which won't allow the corresponding pairs to remain orphaned.
To solve this you can simply remove the associations yourself:
item.getCategories().clear();
As to where this code has to be executed, it's up to you. You can create a hook inside Item, like so:
#PreRemove
private void deleteCategories() {
this.getCategories().clear();
}
Or you could create an ItemService which would do something like
public void deleteItem(Long id) {
final Item item = itemRepository.findById(id);
// you can handle here when not found, which is a good thing to do
item.getCategories().clear();
itemRepository.deleteItem(item);
}
Or you could implement a custom method for JPA repository, which I don't like, but you can read up on that here How to add custom method to Spring Data JPA.

Spring-Data-Jpa Cascading from child to parent

Let say I have an app to handle a collection of books.
My app allow to add a new book to the library. When creating the book, user can select the Author in the list, and if the author doesn't exist yet, he's able to add him to the list, providing his name to a form field.
When the form is filled, data are sent to a WS, something like
{
"name" : "The Book name"
"author" : {
"name" : "author's name"
}
}
Then I map json into my entity which would be
Book :
#Entity
#Table(name = "book")
public class Book{
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Author author;
}
Author
#Entity
#Table(name = "author")
public class Author{
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "author", cascade = { CascadeType.ALL })
private List<Book> books;
}
This will not work as if user tries to add a new author, when I'll try to .save() I'll get an error :
org.hibernate.TransientPropertyValueException: object references an
unsaved transient instance
Is there a way to handle the case with Spring-Data-Jpa, or do I have to check manually that I got an author id in the json, and if not - meaning that this is a new author - mannually run the author creation and then save the new book?
Thx!
As you're guessing, and as the Javadoc says, cascade operations that must be cascaded to the target of the association". However, be sure you understand that the mappedBy defines the owning entity of the relationship. The owning entity is the entity that actually does the persisting operations, unless overridden by a cascade setting. In this case Child is the owning entity.
#Entity
public class Parent {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy="parent")
private Set<Child> children;
The cascade setting on the Parent works when you create a Set of children and set it into the Parent and then save the Parent. Then the save operation will cascade from the Parent to the children. This is a more typical and the expected use case of a cascade setting. However, it does cause database operations to happen auto-magically and this is not always a good thing.
A Cascade setting on the child will happen when the child is persisted, so you could put a cascade annotation there, but read on ...
#Entity
public class Child {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne(cascade=CascadeType.ALL)
private Parent parent;
You will persist both the parent and the child by persisting the child.
tx.begin();
Parent p = new Parent();
Child c = new Child();
c.setParent(p);
em.persist(c);
tx.commit();
and when you delete the child it will delete both the parent and the child.
tx.begin();
Child cFound = em.find(Child.class, 1L);
em.remove(cFound);
tx.commit();
em.clear();
this is where you have problems. What happens if you have more than one child?
em.clear();
tx.begin();
p = new Parent();
Child c1 = new Child();
Child c2 = new Child();
c1.setParent(p);
c2.setParent(p);
em.persist(c1);
em.persist(c2);
tx.commit();
All well and nice until you delete one of the children
em.clear();
tx.begin();
cFound = em.find(Child.class, 2L);
em.remove(cFound);
tx.commit();
then you will get an integrity constraint violation when the cascade propagates to the Parent but there is still a second Child in the database. Sure you could cure it by deleting all the children in a single commit but that's getting kind of messy isn't it?
Conceptually people tend to think that propagation goes from Parent to Child and so it is very counterintuitive to have it otherwise. Further, what about a situation where you don't want to delete the author just because the store sold all his or her books? In this case you might be mixing cascade, sometimes from child to parent and in other cases from parent to child.
Generally I think it is better to be very precise in your database code. It's much easier to read, understand, and maintain code that specifically saves the parent first then the child or children than to have an annotation somewhere else that I may or may not be aware of that is doing additional database operations implicitly.

Using primary key of one table as foreign key to another table [duplicate]

I receive following error when I save the object using Hibernate
object references an unsaved transient instance - save the transient instance before flushing
You should include cascade="all" (if using xml) or cascade=CascadeType.ALL (if using annotations) on your collection mapping.
This happens because you have a collection in your entity, and that collection has one or more items which are not present in the database. By specifying the above options you tell hibernate to save them to the database when saving their parent.
I believe this might be just repeat answer, but just to clarify, I got this on a #OneToOne mapping as well as a #OneToMany. In both cases, it was the fact that the Child object I was adding to the Parent wasn't saved in the database yet. So when I added the Child to the Parent, then saved the Parent, Hibernate would toss the "object references an unsaved transient instance - save the transient instance before flushing" message when saving the Parent.
Adding in the cascade = {CascadeType.ALL} on the Parent's reference to the Child solved the problem in both cases. This saved the Child and the Parent.
Sorry for any repeat answers, just wanted to further clarify for folks.
#OneToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "performancelog_id")
public PerformanceLog getPerformanceLog() {
return performanceLog;
}
Introduction
When using JPA and Hibernate, an entity can be in one of the following 4 states:
New - A newly created object that hasn’t ever been associated with a Hibernate Session (a.k.a Persistence Context) and is not mapped to any database table row is considered to be in the New or Transient state.
To become persisted we need to either explicitly call the persist method or make use of the transitive persistence mechanism.
Persistent - A persistent entity has been associated with a database table row and it’s being managed by the currently running Persistence Context.
Any change made to such an entity is going to be detected and propagated to the database (during the Session flush-time).
Detached - Once the currently running Persistence Context is closed all the previously managed entities become detached. Successive changes will no longer be tracked and no automatic database synchronization is going to happen.
Removed - Although JPA demands that managed entities only are allowed to be removed, Hibernate can also delete detached entities (but only through a remove method call).
Entity state transitions
To move an entity from one state to the other, you can use the persist, remove or merge methods.
Fixing the problem
The issue you are describing in your question:
object references an unsaved transient instance - save the transient instance before flushing
is caused by associating an entity in the state of New to an entity that's in the state of Managed.
This can happen when you are associating a child entity to a one-to-many collection in the parent entity, and the collection does not cascade the entity state transitions.
So, you can fix this by adding cascade to the entity association that triggered this failure, as follows:
The #OneToOne association
#OneToOne(
mappedBy = "post",
orphanRemoval = true,
cascade = CascadeType.ALL)
private PostDetails details;
Notice the CascadeType.ALL value we added for the cascade attribute.
The #OneToMany association
#OneToMany(
mappedBy = "post",
orphanRemoval = true,
cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();
Again, the CascadeType.ALL is suitable for the bidirectional #OneToMany associations.
Now, in order for the cascade to work properly in a bidirectional, you also need to make sure that the parent and child associations are in sync.
The #ManyToMany association
#ManyToMany(
mappedBy = "authors",
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
}
)
private List<Book> books = new ArrayList<>();
In a #ManyToMany association, you cannot use CascadeType.ALL or orphanRemoval as this will propagate the delete entity state transition from one parent to another parent entity.
Therefore, for #ManyToMany associations, you usually cascade the CascadeType.PERSIST or CascadeType.MERGE operations. Alternatively, you can expand that to DETACH or REFRESH.
This happens when saving an object when Hibernate thinks it needs to save an object that is associated with the one you are saving.
I had this problem and did not want to save changes to the referenced object so I wanted the cascade type to be NONE.
The trick is to ensure that the ID and VERSION in the referenced object is set so that Hibernate does not think that the referenced object is a new object that needs saving. This worked for me.
Look through all of the relationships in the class you are saving to work out the associated objects (and the associated objects of the associated objects) and ensure that the ID and VERSION is set in all objects of the object tree.
Or, if you want to use minimal "powers" (e.g. if you don't want a cascade delete) to achieve what you want, use
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
...
#Cascade({CascadeType.SAVE_UPDATE})
private Set<Child> children;
In my case it was caused by not having CascadeType on the #ManyToOne side of the bidirectional relationship. To be more precise, I had CascadeType.ALL on #OneToMany side and did not have it on #ManyToOne. Adding CascadeType.ALL to #ManyToOne resolved the issue.
One-to-many side:
#OneToMany(cascade = CascadeType.ALL, mappedBy="globalConfig", orphanRemoval = true)
private Set<GlobalConfigScope>gcScopeSet;
Many-to-one side (caused the problem)
#ManyToOne
#JoinColumn(name="global_config_id")
private GlobalConfig globalConfig;
Many-to-one (fixed by adding CascadeType.PERSIST)
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name="global_config_id")
private GlobalConfig globalConfig;
This occurred for me when persisting an entity in which the existing record in the database had a NULL value for the field annotated with #Version (for optimistic locking). Updating the NULL value to 0 in the database corrected this.
This isn't the only reason for the error. I encountered it just now for a typo error in my coding, which I believe, set a value of an entity which was already saved.
X x2 = new X();
x.setXid(memberid); // Error happened here - x was a previous global entity I created earlier
Y.setX(x2);
I spotted the error by finding exactly which variable caused the error (in this case String xid). I used a catch around the whole block of code that saved the entity and printed the traces.
{
code block that performed the operation
} catch (Exception e) {
e.printStackTrace(); // put a break-point here and inspect the 'e'
return ERROR;
}
Don't use Cascade.All until you really have to. Role and Permission have bidirectional manyToMany relation. Then the following code would work fine
Permission p = new Permission();
p.setName("help");
Permission p2 = new Permission();
p2.setName("self_info");
p = (Permission)crudRepository.save(p); // returned p has id filled in.
p2 = (Permission)crudRepository.save(p2); // so does p2.
Role role = new Role();
role.setAvailable(true);
role.setDescription("a test role");
role.setRole("admin");
List<Permission> pList = new ArrayList<Permission>();
pList.add(p);
pList.add(p2);
role.setPermissions(pList);
crudRepository.save(role);
while if the object is just a "new" one, then it would throw the same error.
beside all other good answers, this could happen if you use merge to persist an object and accidentally forget to use merged reference of the object in the parent class. consider the following example
merge(A);
B.setA(A);
persist(B);
In this case, you merge A but forget to use merged object of A. to solve the problem you must rewrite the code like this.
A=merge(A);//difference is here
B.setA(A);
persist(B);
If your collection is nullable just try: object.SetYouColection(null);
This issue happened to me when I created a new entity and an associated entity in a method marked as #Transactional, then performed a query before saving. Ex
#Transactional
public someService() {
Entity someEntity = new Entity();
AssocaiatedEntity associatedEntity = new AssocaitedEntity();
someEntity.setAssociatedEntity(associatedEntity);
associatedEntity.setEntity(someEntity);
// Performing any query was causing hibernate to attempt to persist the new entity. It would then throw an exception
someDao.getSomething();
entityDao.create(someEntity);
}
To fix, I performed the query before creating the new entity.
To add my 2 cents, I got this same issue when I m accidentally sending null as the ID. Below code depicts my scenario (and OP didn't mention any specific scenario).
Employee emp = new Employee();
emp.setDept(new Dept(deptId)); // --> when deptId PKID is null, same error will be thrown
// calls to other setters...
em.persist(emp);
Here I m setting the existing department id to a new employee instance without actually getting the department entity first, as I don't want to another select query to fire.
In some scenarios, deptId PKID is coming as null from calling method and I m getting the same error.
So, watch for null values for PK ID
It can also happen when you are having OneToMany relation and you try to add the child entity to the list in parent entity, then retrieve this list through parent entity (before saving this parent entity), without saving child entity itself, e.g.:
Child childEntity = new Child();
parentEntity.addChild(childEntity);
parentEntity.getChildren(); // I needed the retrieval for logging, but one may need it for other reasons.
parentRepository.save(parentEntity);
The error was thrown when I saved the parent entity. If I removed the retrieval in the previous row, then the error was not thrown, but of course that's not the solution.
The solution was saving the childEntity and adding that saved child entity to the parent entity, like this:
Child childEntity = new Child();
Child savedChildEntity = childRepository.save(childEntity);
parentEntity.addChild(savedChildEntity);
parentEntity.getChildren();
parentRepository.save(parentEntity);
If you're using Spring Data JPA then addition #Transactional annotation to your service implementation would solve the issue.
I also faced the same situation. By setting following annotation above the property made it solve the exception prompted.
The Exception I faced.
Exception in thread "main" java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.model.Car_OneToMany
To overcome, the annotation I used.
#OneToMany(cascade = {CascadeType.ALL})
#Column(name = "ListOfCarsDrivenByDriver")
private List<Car_OneToMany> listOfCarsBeingDriven = new ArrayList<Car_OneToMany>();
What made Hibernate throw the exception:
This exception is thrown at your console because the child object I attach to the parent object is not present in the database at that moment.
By providing #OneToMany(cascade = {CascadeType.ALL}) , it tells Hibernate to save them to the database while saving the parent object.
i get this error when i use
getSession().save(object)
but it works with no problem when I use
getSession().saveOrUpdate(object)
For the sake of completeness: A
org.hibernate.TransientPropertyValueException
with message
object references an unsaved transient instance - save the transient instance before flushing
will also occur when you try to persist / merge an entity with a reference to another entity which happens to be detached.
One other possible reason: in my case, I was attempting to save the child before saving the parent, on a brand new entity.
The code was something like this in a User.java model:
this.lastName = lastName;
this.isAdmin = isAdmin;
this.accountStatus = "Active";
this.setNewPassword(password);
this.timeJoin = new Date();
create();
The setNewPassword() method creates a PasswordHistory record and adds it to the history collection in User. Since the create() statement hadn't been executed yet for the parent, it was trying to save to a collection of an entity that hadn't yet been created. All I had to do to fix it was to move the setNewPassword() call after the call to create().
this.lastName = lastName;
this.isAdmin = isAdmin;
this.accountStatus = "Active";
this.timeJoin = new Date();
create();
this.setNewPassword(password);
There is another possibility that can cause this error in hibernate. You may set an unsaved reference of your object A to an attached entity B and want to persist object C. Even in this case, you will get the aforementioned error.
There are so many possibilities of this error some other possibilities are also on add page or edit page. In my case I was trying to save a object AdvanceSalary. The problem is that in edit the AdvanceSalary employee.employee_id is null Because on edit I was not set the employee.employee_id. I have make a hidden field and set it. my code working absolutely fine.
#Entity(name = "ic_advance_salary")
#Table(name = "ic_advance_salary")
public class AdvanceSalary extends BaseDO{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "employee_id", nullable = false)
private Employee employee;
#Column(name = "employee_id", insertable=false, updatable=false)
#NotNull(message="Please enter employee Id")
private Long employee_id;
#Column(name = "advance_date")
#DateTimeFormat(pattern = "dd-MMM-yyyy")
#NotNull(message="Please enter advance date")
private Date advance_date;
#Column(name = "amount")
#NotNull(message="Please enter Paid Amount")
private Double amount;
#Column(name = "cheque_date")
#DateTimeFormat(pattern = "dd-MMM-yyyy")
private Date cheque_date;
#Column(name = "cheque_no")
private String cheque_no;
#Column(name = "remarks")
private String remarks;
public AdvanceSalary() {
}
public AdvanceSalary(Integer advance_salary_id) {
this.id = advance_salary_id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public Long getEmployee_id() {
return employee_id;
}
public void setEmployee_id(Long employee_id) {
this.employee_id = employee_id;
}
}
I think is because you have try to persist an object that have a reference to another object that is not persist yet, and so it try in the "DB side" to put a reference to a row that not exists
Case 1:
I was getting this exception when I was trying to create a parent and saving that parent reference to its child and then some other DELETE/UPDATE query(JPQL). So I just flush() the newly created entity after creating parent and after creating child using same parent reference. It Worked for me.
Case 2:
Parent class
public class Reference implements Serializable {
#Id
#Column(precision=20, scale=0)
private BigInteger id;
#Temporal(TemporalType.TIMESTAMP)
private Date modifiedOn;
#OneToOne(mappedBy="reference")
private ReferenceAdditionalDetails refAddDetails;
.
.
.
}
Child Class:
public class ReferenceAdditionalDetails implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#OneToOne
#JoinColumn(name="reference",referencedColumnName="id")
private Reference reference;
private String preferedSector1;
private String preferedSector2;
.
.
}
In the above case where parent(Reference) and child(ReferenceAdditionalDetails) having OneToOne relationship and when you try to create Reference entity and then its child(ReferenceAdditionalDetails), it will give you the same exception. So to avoid the exception you have to set null for child class and then create the parent.(Sample Code)
.
.
reference.setRefAddDetails(null);
reference = referenceDao.create(reference);
entityManager.flush();
.
.
In my case , issue was completely different. I have two classes let's say c1 & c2. Between C1 & C2 dependency is OneToMany. Now if i am saving C1 in DB it was throwing above error.
Resolution of this problem was to get first C2's id from consumer request and find C2 via repository call.Afterwards save c2 into C1 object .Now if i am saving C1, it's working fine.
I was facing the same error for all PUT HTTP transactions, after introducing optimistic locking (#Version)
At the time of updating an entity it is mandatory to send id and version of that entity. If any of the entity fields are related to other entities then for that field also we should provide id and version values, without that the JPA try to persist that related entity first as a new entity
Example: we have two entities --> Vehicle(id,Car,version) ; Car(id, version, brand); to update/persist Vehicle entity make sure the Car field in vehicle entity has id and version fields provided
Simple way of solving this issue is save the both entity.
first save the child entity and then save the parent entity.
Because parent entity is depend on child entity for the foreign key value.
Below simple exam of one to one relationship
insert into Department (name, numOfemp, Depno) values (?, ?, ?)
Hibernate: insert into Employee (SSN, dep_Depno, firstName, lastName, middleName, empno) values (?, ?, ?, ?, ?, ?)
Session session=sf.openSession();
session.beginTransaction();
session.save(dep);
session.save(emp);
One possible cause of the error is the inexistence of the setting of the value of the parent entity ; for example for a department-employees relationship you have to write this in order to fix the error :
Department dept = (Department)session.load(Department.class, dept_code); // dept_code is from the jsp form which you get in the controller with #RequestParam String department
employee.setDepartment(dept);
I faced this exception when I did not persist parent object but I was saving the child. To resolve the issue, with in the same session I persisted both the child and parent objects and used CascadeType.ALL on the parent.
My problem was related to #BeforeEach of JUnit. And even if I saved the related entities (in my case #ManyToOne), I got the same error.
The problem is somehow related to the sequence that I have in my parent.
If I assign the value to that attribute, the problem is solved.
Ex.
If I have the entity Question that can have some categories (one or more) and entity Question has a sequence:
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "feedbackSeq")
#Id
private Long id;
I have to assign the value question.setId(1L);
Just make Constructor of your mapping in your base class.
Like if you want One-To-One relation in Entity A, Entity B.
if your are taking A as base class, then A must have a Constructor have B as a argument.

Spring JPA #OneToMany not persisting the children

Before I start I checked few posts and none of them resolved my problem.
Please can someone guide me here. I wanted to establish a Plant(1) to Inventories(n) Relationship.
I created couple of models, one for Plant as below where I mention the OneToMany relationship
#Entity
public class Plant implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long plantID;
#OneToMany(mappedBy = "plant", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Inventory> listInventory = new ArrayList<>();
getter and setter....
And another one for Inventory where I mention ManyToOne relationship
#Entity
public class Inventory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long inventoryId;
#ManyToOne
#JoinColumn(name = "plantID", nullable = false)
private Plant plant;
When I try to persist the data like below, it saves the plant (parent) but not its children's.
As part of persisting the data, I did something like below
public Plant addPlant(String plantName, List<Inventory> listMaterial, PlantRepository plantRepository, InventoryRepository inventoryRepository) {
Plant plant = new Plant();
List<Inventory> listInventory = new ArrayList<>();
plant.setPlantName(plantName);
for (Inventory inventory : listMaterial) {
inventory.setPlant(plant);
listInventory.add(inventory);
}
plant.setListInventory(listInventory);
return plantRepository.save(plant);
}
After reading few posts I found that I should set the association of plant with the inventory before persisting. So I did added the code for the same but it went on infinite or just my STS hanged.
I am sure I am doing something wrong but not sure where. Please can someone guide me through.
My expectation is that Inventory will be a reference table where I will have list of inventories. When I add a new plant, I will map few of the inventories to this plant. Similarly this will happen for many plants and many inventories.
As part of persistence, I will have to save plant and its inventories. I should also be able to pass the plant ID and retrieve the corresponding Inventories.
UPDATE 1:
Also I am not sure if the relation I am using is fair enough for this scenario. As my inventory is a reference table and at the same time, when a plant is mapped to multiple inventories, each inventory can be modified before persisting.
I tried #ManyToMany and it stores the relation in a 3rd table with a unique reference to both the tables but I wont be able to get the details of each inventory record.
With #ManyToMany, when I fetch it bring the values from the reference table and not the modified one which was persisted with the parent(plant) Please any advise
UPDATE 2
I tried with the same models but I changed the way I persist the data as below.
public Plant addPlant(String plantName, List<Inventory> listMaterial, PlantRepository plantRepository, InventoryRepository inventoryRepository) {
Plant plant = new Plant();
List<Inventory> listInventory = new ArrayList<>();
plant.setPlantName(plantName);
for (Inventory inventory : listMaterial) {
plant.addInventoryToPlant(inventory);
}
return plantRepository.save(plant);
}
Here is the add method in my plant model
public void addInventoryToPlant(Inventory inventory) {
listInventory.add(inventory);
inventory.setPlant(this);
}
It is just overwriting the inventory table with different plant IDs but not creating a reference table or join table to maintain all the possible mappings. If I try to add a plant with 2 inventories, it maps them first to the Inventory table. If I add another plant then this is getting overwritten. I was in an assumption that it will create a third table to maintain this entity
I have a feeling that those Inventory entities are detached and the Persistence Provider is not considering them during flush. You do not get an exception as #OneToMany is a special kind of relationship in flush algorithm because the act of persisting the owning entity does not depend on the target thus Hibernate will proceed and persist only the Plant entity.
Try using merge instead of persist:
plant.setListInventory(listInventory);
return plantRepository.merge(plant);
Update
You can also merge each inventory one by one using save as Spring JPA implicitly checks whether entity should be saved or merged:
for (Inventory inventory : listMaterial) {
Inventory mergedInventory = inventoryRepository.save(inventory);
mergedInventory.setPlant(plant);
listInventory.add(mergedInventory);
}
plant.setListInventory(listInventory);
return plantRepository.save(plant);
This answers the UPDATE 2 part:
As I understand, Plant and Inventory are in a many-to-many relationship, but there are additional properties that are to be stored along with the information that a specific Plant is holding a specific Inventory item.
In that case, you need an additional entity (let's call it StockItem) that will be used to hold that additional state. Both Plant and Inventory will then be in a one-to-many relationship with the new entity.
Your mapping will then become:
class Plant {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "plant_id")
private Set<StockItem> stockItems;
...
}
class StockItem {
#ManyToOne(mappedBy = "stockItems")
private Plant plant;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#JoinColumn(name = "inventory_id")
private Inventory inventory;
//put any fields here that may vary from one plant to plant to another
private int quantity;
}
class Inventory {
#OneToMany(mappedBy = "inventory")
public Set<StockItem> stockItems;
// leave any fields that will NOT vary from one plant to another here
private String name;
}
Things to note:
This mapping assumes that you will be adding new StockItems to a Plant (new items added to Inventory.stockItems will be ignored by JPA), in which case it will be enough to set the StockItem.inventory field to a proper value, add the StockItem to the Plant.stockItems list, and save the Plant entity
The StockItem.plant and Inventory.stockItems are not absolutely necessary, remove them if you do not need them

Delete elements from one-to-many relationships when removing them from a JPA Entity

I am using JPA (Hibernate) with the following entity class with one one-to-many relationship.
When I add elements to the list, and then persist the Organization entity, it adds the new elements to the proyects table, but when I remove elements from the list, nothing happens when persist (or merge), and I would like these elements to be removed from the database.
I tried also orphanRemoval=true in the OneToMany annotation, but it doesn't work.
#Entity
public class Organization {
#Id
#GeneratedValue
public long internalId;
#Basic
#Column(nullable = false, length = 100)
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "organization")
private List<Proyect> proyects;
// Getters and Setters
}
You need to set Proyect.organization to null and update that entity, since this property is responsible for the database entry (Proyect is the owning side in this case ).

Categories