#OneToOne field value not loaded after save - java

I have a Spring Data JPA Project, my entity is the following
#Entity
#Table(name = "delivery_offers")
public class DeliveryOffer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne
#JoinColumn(name = "DELIVERY_ID")
private Delivery delivery;
... other fields
The table also has a foreign key for DELIVERY_ID which references to DELIVERY.ID
My Dao is a JpaRepository object, I try to persist a DeliveryOffer with dao.saveAndFlush (deliveryOffer).
I also set delivery.id = xy
I would like to return the persisted DeliveryOffer object, so I make in my controller
return dao.saveAndFlush(deliveryOffer)
The problem is, that in the returned DeliveryOffer the referenced Delivery object fields are all null except of the id, which I set for deliveryOffer.delivery.id (the Delivery exists in the DB with the given id)
If I try to get an "older" deliveryOffer from a recent transaction with findOne, the referenced delivery is then filled correctly.
So I suppose, it has to do something with the transaction. I thought, saveAndFlush commits it immediately, so in the return object I would await the correct entity.
Do you have an idea, why the delivery is empty at this point?

Related

I am not able to update 'shop_table' table value for specifice row using hibernate local session factory object?

If, I am printing the session object, desired output is not correct i.e. -
SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList#7164ee40 updates=org.hibernate.engine.spi.ExecutableList#2d80b7f deletions=org.hibernate.engine.spi.ExecutableList#560d3d76 orphanRemovals=org.hibernate.engine.spi.ExecutableList#10fbc8b2 collectionCreations=org.hibernate.engine.spi.ExecutableList#463b2203 collectionRemovals=org.hibernate.engine.spi.ExecutableList#47a5a48 collectionUpdates=org.hibernate.engine.spi.ExecutableList#3b23213c collectionQueuedOps=org.hibernate.engine.spi.ExecutableList#75d3c0b2 unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])
I think valuable if print like this-
SessionImpl(PersistenceContext[entityKeys=[EntityKey[com.mode
l.Entity#1]],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[
] collectionCreations=[] collectionRemovals=[] collectionUpdates=[]])
My class for Shop is-
#Entity
#Table(name = "shops")
public class Shop {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "shop_domain")
private String shopDomain;
#JsonFormat(pattern="dd-MM-yyyy")
private Timestamp orderSyncDate;
#OneToOne(mappedBy = "shop", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonManagedReference
private NetsuiteConfig netsuiteConfig;
I am simply set some data to shop class object, trying to save in DB
, object hold ID i.e. already exist in DB , query as -
session factory.getCurrentSession().update(shop);
this actually not update the value in db, I don't know the reason, why?
You must commit your update for the changes to propagate into the DB:
...
sessionFactory.getCurrentSession().update(shop);
sessionFactory.getCurrentSession().commit();
...
References:
Hibernate Session merge, update, save, saveOrUpdate, persist example
Hibernate Lifecycle (for Baeldung)

How to add existing value in many to many relationship spring boot [duplicate]

I have a JPA-persisted object model that contains a many-to-one relationship: an Account has many Transactions. A Transaction has one Account.
Here's a snippet of the code:
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
private Account fromAccount;
....
#Entity
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
private Set<Transaction> transactions;
I am able to create an Account object, add transactions to it, and persist the Account object correctly. But, when I create a transaction, using an existing already persisted Account, and persisting the the Transaction, I get an exception:
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.paulsanwald.Account
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141)
So, I am able to persist an Account that contains transactions, but not a Transaction that has an Account. I thought this was because the Account might not be attached, but this code still gives me the same exception:
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
// the below fails with a "detached entity" message. why?
entityManager.persist(transaction);
How can I correctly save a Transaction, associated with an already persisted Account object?
The solution is simple, just use the CascadeType.MERGE instead of CascadeType.PERSIST or CascadeType.ALL.
I have had the same problem and CascadeType.MERGE has worked for me.
I hope you are sorted.
This is a typical bidirectional consistency problem. It is well discussed in this link as well as this link.
As per the articles in the previous 2 links you need to fix your setters in both sides of the bidirectional relationship. An example setter for the One side is in this link.
An example setter for the Many side is in this link.
After you correct your setters you want to declare the Entity access type to be "Property". Best practice to declare "Property" access type is to move ALL the annotations from the member properties to the corresponding getters. A big word of caution is not to mix "Field" and "Property" access types within the entity class otherwise the behavior is undefined by the JSR-317 specifications.
Remove cascading from the child entity Transaction, it should be just:
#Entity class Transaction {
#ManyToOne // no cascading here!
private Account account;
}
(FetchType.EAGER can be removed as well as it's the default for #ManyToOne)
That's all!
Why? By saying "cascade ALL" on the child entity Transaction you require that every DB operation gets propagated to the parent entity Account. If you then do persist(transaction), persist(account) will be invoked as well.
But only transient (new) entities may be passed to persist (Transaction in this case). The detached (or other non-transient state) ones may not (Account in this case, as it's already in DB).
Therefore you get the exception "detached entity passed to persist". The Account entity is meant! Not the Transaction you call persist on.
You generally don't want to propagate from child to parent. Unfortunately there are many code examples in books (even in good ones) and through the net, which do exactly that. I don't know, why... Perhaps sometimes simply copied over and over without much thinking...
Guess what happens if you call remove(transaction) still having "cascade ALL" in that #ManyToOne? The account (btw, with all other transactions!) will be deleted from the DB as well. But that wasn't your intention, was it?
Don't pass id(pk) to persist method or try save() method instead of persist().
Removing child association cascading
So, you need to remove the #CascadeType.ALL from the #ManyToOne association. Child entities should not cascade to parent associations. Only parent entities should cascade to child entities.
#ManyToOne(fetch= FetchType.LAZY)
Notice that I set the fetch attribute to FetchType.LAZY because eager fetching is very bad for performance.
Setting both sides of the association
Whenever you have a bidirectional association, you need to synchronize both sides using addChild and removeChild methods in the parent entity:
public void addTransaction(Transaction transaction) {
transcations.add(transaction);
transaction.setAccount(this);
}
public void removeTransaction(Transaction transaction) {
transcations.remove(transaction);
transaction.setAccount(null);
}
Using merge is risky and tricky, so it's a dirty workaround in your case. You need to remember at least that when you pass an entity object to merge, it stops being attached to the transaction and instead a new, now-attached entity is returned. This means that if anyone has the old entity object still in their possession, changes to it are silently ignored and thrown away on commit.
You are not showing the complete code here, so I cannot double-check your transaction pattern. One way to get to a situation like this is if you don't have a transaction active when executing the merge and persist. In that case persistence provider is expected to open a new transaction for every JPA operation you perform and immediately commit and close it before the call returns. If this is the case, the merge would be run in a first transaction and then after the merge method returns, the transaction is completed and closed and the returned entity is now detached. The persist below it would then open a second transaction, and trying to refer to an entity that is detached, giving an exception. Always wrap your code inside a transaction unless you know very well what you are doing.
Using container-managed transaction it would look something like this. Do note: this assumes the method is inside a session bean and called via Local or Remote interface.
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void storeAccount(Account account) {
...
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
entityManager.persist(account);
}
Probably in this case you obtained your account object using the merge logic, and persist is used to persist new objects and it will complain if the hierarchy is having an already persisted object. You should use saveOrUpdate in such cases, instead of persist.
My Spring Data JPA-based answer: I simply added a #Transactional annotation to my outer method.
Why it works
The child entity was immediately becoming detached because there was no active Hibernate Session context. Providing a Spring (Data JPA) transaction ensures a Hibernate Session is present.
Reference:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
An old question, but came across the same issue recently . Sharing my experience here.
Entity
#Data
#Entity
#Table(name = "COURSE")
public class Course {
#Id
#GeneratedValue
private Long id;
}
Saving the entity (JUnit)
Course course = new Course(10L, "testcourse", "DummyCourse");
testEntityManager.persist(course);
Fix
Course course = new Course(null, "testcourse", "DummyCourse");
testEntityManager.persist(course);
Conclusion : If the entity class has #GeneratedValue for primary key (id), then ensure that you are not passing a value for the primary key (id)
If nothing helps and you are still getting this exception, review your equals() methods - and don't include child collection in it. Especially if you have deep structure of embedded collections (e.g. A contains Bs, B contains Cs, etc.).
In example of Account -> Transactions:
public class Account {
private Long id;
private String accountName;
private Set<Transaction> transactions;
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Account))
return false;
Account other = (Account) obj;
return Objects.equals(this.id, other.id)
&& Objects.equals(this.accountName, other.accountName)
&& Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
}
}
In above example remove transactions from equals() checks. This is because hibernate will imply that you are not trying to update old object, but you pass a new object to persist, whenever you change element on the child collection.
Of course this solutions will not fit all applications and you should carefully design what you want to include in the equals and hashCode methods.
In your entity definition, you're not specifying the #JoinColumn for the Account joined to a Transaction. You'll want something like this:
#Entity
public class Transaction {
#ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
#JoinColumn(name = "accountId", referencedColumnName = "id")
private Account fromAccount;
}
EDIT: Well, I guess that would be useful if you were using the #Table annotation on your class. Heh. :)
Even if your annotations are declared correctly to properly manage the one-to-many relationship you may still encounter this precise exception. When adding a new child object, Transaction, to an attached data model you'll need to manage the primary key value - unless you're not supposed to. If you supply a primary key value for a child entity declared as follows before calling persist(T), you'll encounter this exception.
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
....
In this case, the annotations are declaring that the database will manage the generation of the entity's primary key values upon insertion. Providing one yourself (such as through the Id's setter) causes this exception.
Alternatively, but effectively the same, this annotation declaration results in the same exception:
#Entity
public class Transaction {
#Id
#org.hibernate.annotations.GenericGenerator(name="system-uuid", strategy="uuid")
#GeneratedValue(generator="system-uuid")
private Long id;
....
So, don't set the id value in your application code when it's already being managed.
Here is my fix.
Below is my Entity. Mark that the id is annotated with #GeneratedValue(strategy = GenerationType.AUTO), which means that the id would be generated by the Hibernate. Don't set it when entity object is created. As that will be auto generated by the Hibernate.
Mind you if the entity id field is not marked with #GeneratedValue then not assigning the id a value manually is also a crime, which will be greeted with IdentifierGenerationException: ids for this class must be manually assigned before calling save()
#Entity
#Data
#NamedQuery(name = "SimpleObject.findAll", query="Select s FROM SimpleObject s")
public class SimpleObject {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
private String key;
#Column
private String value;
}
And here is my main class.
public class SimpleObjectMain {
public static void main(String[] args) {
System.out.println("Hello Hello From SimpleObjectMain");
SimpleObject simpleObject = new SimpleObject();
simpleObject.setId(420L); // Not right, when id is a generated value then no need to set this.
simpleObject.setKey("Friend");
simpleObject.setValue("Bani");
EntityManager entityManager = EntityManagerUtil.getEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(simpleObject);
entityManager.getTransaction().commit();
List<SimpleObject> simpleObjectList = entityManager.createNamedQuery("SimpleObject.findAll").getResultList();
for(SimpleObject simple : simpleObjectList){
System.out.println(simple);
}
entityManager.close();
}
}
When I tried saving that, it was throwing that
PersistentObjectException: detached entity passed to persist.
All I needed to fix was remove that id setting line for the simpleObject in the main method.
Maybe It is OpenJPA's bug, When rollback it reset the #Version field, but the pcVersionInit keep true. I have a AbstraceEntity which declared the #Version field. I can workaround it by reset the pcVersionInit field. But It is not a good idea. I think it not work when have cascade persist entity.
private static Field PC_VERSION_INIT = null;
static {
try {
PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
PC_VERSION_INIT.setAccessible(true);
} catch (NoSuchFieldException | SecurityException e) {
}
}
public T call(final EntityManager em) {
if (PC_VERSION_INIT != null && isDetached(entity)) {
try {
PC_VERSION_INIT.set(entity, false);
} catch (IllegalArgumentException | IllegalAccessException e) {
}
}
em.persist(entity);
return entity;
}
/**
* #param entity
* #param detached
* #return
*/
private boolean isDetached(final Object entity) {
if (entity instanceof PersistenceCapable) {
PersistenceCapable pc = (PersistenceCapable) entity;
if (pc.pcIsDetached() == Boolean.TRUE) {
return true;
}
}
return false;
}
You need to set Transaction for every Account.
foreach(Account account : accounts){
account.setTransaction(transactionObj);
}
Or it colud be enough (if appropriate) to set ids to null on many side.
// list of existing accounts
List<Account> accounts = new ArrayList<>(transactionObj.getAccounts());
foreach(Account account : accounts){
account.setId(null);
}
transactionObj.setAccounts(accounts);
// just persist transactionObj using EntityManager merge() method.
cascadeType.MERGE,fetch= FetchType.LAZY
Resolved by saving dependent object before the next.
This was happened to me because I was not setting Id (which was not auto generated). and trying to save with relation #ManytoOne
#OneToMany(mappedBy = "xxxx", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE})
worked for me.
In my case I was committing transaction when persist method was used.
On changing persist to save method , it got resolved.
If above solutions not work just one time comment the getter and setter methods of entity class and do not set the value of id.(Primary key)
Then this will work.
Another reason I have encountered this issue is having Entities that aren't versioned by Hibernate in a transaction.
Add a #Version annotation to all mapped entities
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Version
private Integer version;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "orders")
private CustomerOrders orders;
}
#Entity
public class CustomerOrders {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Version
private Integer version;
private BigDecimal value;
}
This error comes from the JPA Lifecycle.
To solve, no need to use specific decorator. Just join the entity using merge like that :
entityManager.merge(transaction);
And don't forget to correctly set up your getter and setter so your both side are sync.
So I stumbled across this Question and Answers because I got the same Error but a very basic object with just Strings and Integers.
But in my case I was trying to set a Value to a Field which was annotated with #Id.
So if you are using #Id it seems that you can't create a new Object on a Class and set an Id by yourself and persist it to Database. You should then leave the Id blank. I wasn't aware and maybe this helps anyone else.
The problem here is lack of control.
When we use the CrudRepository/JPARepository save method we loose the transactional control.
To overcome this issue we have Transaction Management
I prefer the #Transactional mechanism
imports
import javax.transaction.Transactional;
Entire Source Code:
package com.oracle.dto;
import lombok.*;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
#Entity
#Data
#ToString(exclude = {"employee"})
#EqualsAndHashCode(exclude = {"employee"})
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO,generator = "ps")
#SequenceGenerator(name = "ps",sequenceName = "project_seq",initialValue = 1000,allocationSize = 1)
#Setter(AccessLevel.NONE)
#Column(name = "project_id",updatable = false,nullable = false)
private Integer pId;
#Column(name="project_name",nullable = false,updatable = true)
private String projectName;
#Column(name="team_size",nullable = true,updatable = true)
private Integer teamSize;
#Column(name="start_date")
private Date startDate;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="projectemp_join_table",
joinColumns = {#JoinColumn(name = "project_id")},
inverseJoinColumns = {#JoinColumn(name="emp_id")}
)
private List<Employee> employees;
}
package com.oracle.dto;
import lombok.*;
import javax.persistence.*;
import java.util.List;
#Entity
#Data
#EqualsAndHashCode(exclude = {"projects"})
#ToString(exclude = {"projects"})
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO,generator = "es")
#SequenceGenerator(name = "es",sequenceName = "emp_seq",allocationSize = 1,initialValue = 2000)
#Setter(AccessLevel.NONE)
#Column(name = "emp_id",nullable = false,updatable = false)
private Integer eId;
#Column(name="fist_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#ManyToMany(mappedBy = "employees")
private List<Project> projects;
}
package com.oracle.repo;
import com.oracle.dto.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepo extends JpaRepository<Employee,Integer> {
}
package com.oracle.repo;
import com.oracle.dto.Project;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProjectRepo extends JpaRepository<Project,Integer> {
}
package com.oracle.services;
import com.oracle.dto.Employee;
import com.oracle.dto.Project;
import com.oracle.repo.ProjectRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
#Component
public class DBServices {
#Autowired
private ProjectRepo repo;
#Transactional
public void performActivity(){
Project p1 = new Project();
p1.setProjectName("Bank 2");
p1.setTeamSize(20);
p1.setStartDate(new Date(2020, 12, 22));
Project p2 = new Project();
p2.setProjectName("Bank 1");
p2.setTeamSize(21);
p2.setStartDate(new Date(2020, 12, 22));
Project p3 = new Project();
p3.setProjectName("Customs");
p3.setTeamSize(11);
p3.setStartDate(new Date(2010, 11, 20));
Employee e1 = new Employee();
e1.setFirstName("Pratik");
e1.setLastName("Gaurav");
Employee e2 = new Employee();
e2.setFirstName("Ankita");
e2.setLastName("Noopur");
Employee e3 = new Employee();
e3.setFirstName("Rudra");
e3.setLastName("Narayan");
List<Employee> empList1 = new LinkedList<Employee>();
empList1.add(e2);
empList1.add(e3);
List<Employee> empList2 = new LinkedList<Employee>();
empList2.add(e1);
empList2.add(e2);
List<Project> pl1=new LinkedList<Project>();
pl1.add(p1);
pl1.add(p2);
List<Project> pl2=new LinkedList<Project>();
pl2.add(p2);pl2.add(p3);
p1.setEmployees(empList1);
p2.setEmployees(empList2);
e1.setProjects(pl1);
e2.setProjects(pl2);
repo.save(p1);
repo.save(p2);
repo.save(p3);
}
}

why is detached entity treated as transient?

I am using Hibernate as JPA provider. I have fetched account entity from the database and detached it. So, the account entity must now be in a detached state.
Now when I try to persist the detached entity, I was expecting it to fail, as entitymananger.persist method works only on transient and managed entities. But it is running INSERT query on database.
Further debugging the entitymananger.persist implementation, I found that it is considering account entity as TRANSIENT. I put the breakpoint here to see entity state,
The code used for testing:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MainApplication.class)
public class HibernateTest {
#PersistenceContext
private EntityManager entityManager;
#Test
#Transactional
#Commit
public void testDetached() {
Account account = entityManager.find(Account.class, 1); // account is now managed entity
entityManager.detach(account); // detach the account entity
entityManager.persist(account);// entityManager is treating account as transient object and ran INSERT query on the database
}
}
Adding the Account class
#Entity
#lombok.Data
#Table(name = "account")
class Account {
#Id
#Column(name = "userid")
private int userid;
#Column(name = "balance")
private int balance;
}
My questions are:
Why is entitymanager.persist considering detached entity as transient ?
I tried understanding Hibernate determination of whether an entity is transient or detached (code). the logic is complex and couldn't internalize why it is done the way it is done.
Please let me know if you need any further details. Thanks.
An entity is considered DETACHED if :
1. ID is not null (otherwise it is transient)
2. It is not managed
3. It has not been removed
Im not sure how you are assigning the ID property, but i guess the problem is somewhere around there.
Leave the ID generation to hibernate:
#Id
#GeneratedValue <<<<<<<
#Column(name = "userid")
private int userid;
That should fail the test.

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.

JPA OneToOne and OneToMany entity instantiation/creation

My application uses a lot of OneToMany and OneToOne references between domain level value-objects, most of them are entities, either being a super class or a subclass of something.I would like to provide my application a consistent(yet easy) way to save those instances and the actual method save() is as such
#Transactional
public void save(Post post){
try{
JPA.em().persist(post);
}catch (EntityExistsException eee){
JPA.em().merge(post);
}catch(ConstraintViolationException cve){
JPA.em().refresh(post);
}
}
The current problem is how to properly instantiate those object and which strategy choose in the cascadeType, i would like to save nested object when saving an object with references with other entities, it works now but only for the first time, after that i get a Unique index or primary key violation given that SQL insert into Utente (passwd, DTYPE, username) values (?, 'Redattore', ?) [23505-168].Clearly my JPA provider (hibernate 3.6.9) fails to not update an existing row, instead it tries to insert a new entry in the DB.Here are some classes i am using:
#Entity
#Table
public class Post extends Domanda {
#Column(nullable = false)
private String nome;
#OneToMany(cascade = CascadeType.ALL)
private List<Commento> commenti;
#OneToMany(cascade = CascadeType.ALL)
private List<Risorsa> risorse;
#OneToOne(cascade = CascadeType.ALL)
private NodoApprendimento nodo;
#Column
private int visibilità;
#Column
private boolean isDraft;
Post is referenced by a few classes among which i there is:
#Entity
public abstract class Partecipante extends Utente{
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
private Set<Post> contributi;
Then i would like to know the proper way to initialize and persisteORupdate those referenced object in the database, thanks in advance.
This isn't a provider problem, but a usage issue. When you call persist, JPA does not require providers to execute an insert immediately - they are usually delayed to flush or commit time. So you will not get an EntityExistsException in most cases. Either way, the transaction state should be marked for rollback - you should not be relying on persist to determine if merge should be called. Either call em.find, or just call em.merge on your entity and allow the JPA provider to determine if it should do an insert or an update for you.

Categories