I have Employee Class like below
#Entity
public class Employee{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
id;
Department dept;
Address add;
#JoinColumn(name = "manager_emp_id", insertable = false, updatable = false)
Manager Employee;
//other fields and their getters and setters
}
when i call entityManager.persist(Employee emp) to persist i get below error
javax.persistence.PersistenceException:
org.hibernate.PersistentObjectException: detached entity passed to
persist : com.myComp.Employee
i verfied employee.id is null or 0. So i am not getting why its considering Employee as detached instance instead of transient ?
Can it because its some member variable(instead of Employee) has id as non zero value and hibernate is trying to persist ? If yes
can i get which exact class is detached instance as there are many member variables?
Guessing but:
manager is probably also Employee, when you set on your new Employ objcet the manager, most likely you are using existing one but that you did not retrieve it from the same EnityManger (or haven't marge it). And the exception. Without the your DAO code or other entities it is only guessing.
So i am not getting why its considering Employee as detached instance instead of transient ?
I cannot tell you either, because your code example is too brief (and also snytactically incorrect).
Can it because its some member variable(instead of Employee) has id as non zero value and hibernate is trying to persist ?
Possibly.
If yes can i get which exact class is detached instance as there are many member variables?
This you already have. Based on the exception you posted, it is an instance of Employee. Based on the given info I have to guess, that you have employee also reference itself (maybe via department?). If you are not cascading the persist operation you might hit this exception when one of the lower level employees is tried to be persisted. But as said, your sample is too brief and incomplete as to really give you a full fledged answer.
Related
I'm newbie in Hibernate and I'm trying to learn about JPA and Hibernate.
I want to know that what is the reason that Hibernate does not allow to save the object which references an unsaved transient instance? I want to know WHY this is a problem?
I asked someone and some of them answer me like this:
How could we possibly map the customer to the address, if there is no
adress record in the DB yet?
and
you are assigning particular Address to Customer. But Address does
not have any ID
but honestly I can't understand them.
(I know that an exception will be thrown and the solution is Cascade but I want to the reason of the problem inside the database)
now, let's assume we have all of these code:
(I use Bidirectional One-To-One relationship for my example)
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#OneToOne(mappedBy = "customer")
private Address address;
}
#Entity
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String street;
private String zipCode;
#OneToOne
private Customer customer;
}
public static void main(String[] args) {
EntityManager entityManager = emf.createEntityManager();
entityManager.getTransaction().begin(); // Begin Transaction
Customer c1 = new Customer("Mi", "S");
Address addrss1 = new Address("5412 S 5th", "212524");
c1.setAddress(addrss1);
addrss1.setCustomer(c1);
entityManager.persist(c1);
entityManager.getTransaction().commit(); // Commit
entityManager.close();
}
and let's assume that the exception is not thrown and java and hibernate have allowed us to run our code and this is our customer table.
id firstName lastName
---------------------------------
1 Mi S
and this is our address table:
id street zipCode customer_id
---------------------------------------------
- - - -
now, what is the problem? everything in these Bidirectional One-To-One relationship seems right.
then what is the problem?
PS:
if it is possible, please explain and show me code.
I can understand better with code. thank you.
I want to see for example if we are allowed to save the object which references an unsaved transient instance, what problems will we face in our code and in our tables (for example do we have any problem when we want to retrieve a customer and etc)
Because your adress entity have the primay key of customer as a foreign key ,(since mappedby is in Customer entity) ,and the customer referenced by the adress has no id ,which tells hibernate that that entity was never persisted in the database (which literally means transient) ,and hibernate needs a persisted/managed entity to make sure it exists in the database so that the adress object can be associated with an existing customer.
Customer is new, and it is clear from the persist call you want to insert it, but it isn't clear what you want to happen to any of customer's references. To make it clear, you define what you want the JPA provider (Hibernate) to do in the mappings under any/all circumstances - this is what the cascade operations refer to. In this case, JPA will look at the customer.address OneToOne mapping and find nothing defined; Address is NOT managed in this EntityManager context, so it doesn't know what to do to handle this relationship, so it signals you've made a mistake by throwing an error.
If it let it through, your Customer instance references something that does not exist, and its state does not match what is in the database. What you pass into persist should be what you would get back on reads, so it should reflect the state that is in the database.
The issue isn't directly with your persist call, as the spec does allow providers to ignore references to detached/new instances that don't have cascade settings - what happens is just undefined. Where you go wrong in this situation is on flush/commit, which is when the persistence unit is synchronized to the database (section 3.2.4 of JPA 3.0), which requires providers to go through managed entities and then determine any changes. Adding a new address pre persist will result in the same issue as if you did it post persist, and requires providers to throw an IllegalStateException if it discovers new or removed entities and rollback the transaction.
Why this is a problem: JPA is very big on entity Identity, as this enables caching of these entities in multiple levels of caches, and this entity might go into those caches as it is. It has to know what to do with references to entities that do not exist, and the spec decided to require an exception. Even to your app this is and should be a problem, as the EntityManager context is a unit of work, and the state within that unit of work is based on something that is wrong. Your Customer doesn't really have an address when this is said and done, yet your application business logic thinks it assigned one, with state that just isn't going to be there afterward.
You already know the solutions:
correct the customer to have a valid, managed address by calling persist on it directly in this same EntityManager context.
set the cascade options on the mapping to cascade persist to address for you
don't set addresses on a new customer in the same operation.
I have an #Entity A that references another entity B using OneToOne relation ship. I fetch entity A using spring data JpaRepository
A a = aRepository.findById(1);
int b_id = a.getB().getId();
As you can see I need to query ID of the B table, however in order to do that, I need to call getter of the B table, which will cause lazy-loading the B table itself. I do not want to do that because the only thing I need is the get ID, nothing else, and that ID is present in the first A table.
Is there any trick that will help me to get ID of the dependent table without triggering new query?
UPDATE
#Entity
class A {
#Id
private Long id;
#OneToOne
private B b;
}
#Entity
class {
#Id
private Long id;
}
Without looking at the entity mapping, I suspect, your entity classes might be using hibernate annotations on the field. With this if you call even the getId() method as in a.getB().getId() on the entity it will result in initializing the proxy (i.e., B object) and hits the database to fetch it.
So if the intent is only to get the id of the entity you can place the hibernate annotations on the getter methods instead. This doesn't result initializing the proxy (B object) to return the id. Although accessing any property other than id will result in hitting the database.
Have a look at related bug at HHH-3718
So, try using property/getter AccessType instead of field access. As an example instead of placing the annotations on field
#Id
#GeneratedValue(...)
private long id;
place them on the getters
#Id
#GeneratedValue(...)
public long getId() { ... }
Make sure you make similar changes to all the fields of B entity. Although you can explore #Access(AccessType.PROPERTY/FIELD) later.
There is already a related bug HHH-3718 regarding this behavior.
And a related topic on hibernate forum regarding field vs property access type that might be of interest for you Field Vs Property access
Posting your entities classes would help, if this doesn't resolve the issue.
I have 2 classes: Driver and Car. Cars table updated in separate process. What I need is to have property in Driver that allows me to read full car description and write only Id pointing to existing Car. Here is example:
#Entity(name = "DRIVER")
public class Driver {
... ID and other properties for Driver goes here .....
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "CAR_ID")
private Car car;
#JsonView({Views.Full.class})
public Car getCar() {
return car;
}
#JsonView({Views.Short.class})
public long getCarId() {
return car.getId();
}
public void setCarId(long carId) {
this.car = new Car (carId);
}
}
Car object is just typical JPA object with no back reference to the Driver.
So what I was trying to achieve by this is:
I can read full Car description using detailed JSON View
or I can read only Id of the Car in Short JsonView
and most important, when creating new Driver I just want to pass in JSON ID of the car.
This way I dont need to do unnesessery reads for the Car during persist but just update Id.
Im getting following error:
object references an unsaved transient instance - save the transient instance before flushing : com.Driver.car -> com.Car
I dont want to update instance of the Car in DB but rather just reference to it from Driver. Any idea how to achieve what I want?
Thank you.
UPDATE:
Forgot to mention that the ID of the Car that I pass during creation of the Driver is valid Id of the existing Car in DB.
You can do this via getReference call in EntityManager:
EntityManager em = ...;
Car car = em.getReference(Car.class, carId);
Driver driver = ...;
driver.setCar(car);
em.persist(driver);
This will not execute SELECT statement from the database.
As an answer to okutane, please see snippet:
#JoinColumn(name = "car_id", insertable = false, updatable = false)
#ManyToOne(targetEntity = Car.class, fetch = FetchType.EAGER)
private Car car;
#Column(name = "car_id")
private Long carId;
So what happens here is that when you want to do an insert/update, you only populate the carId field and perform the insert/update. Since the car field is non-insertable and non-updatable Hibernate will not complain about this and since in your database model you would only populate your car_id as a foreign key anyway this is enough at this point (and your foreign key relationship on the database will ensure your data integrity). Now when you fetch your entity the car field will be populated by Hibernate giving you the flexibility where only your parent gets fetched when it needs to.
You can work only with the car ID like this:
#JoinColumn(name = "car")
#ManyToOne(targetEntity = Car.class, fetch = FetchType.LAZY)
#NotNull(message = "Car not set")
#JsonIgnore
private Car car;
#Column(name = "car", insertable = false, updatable = false)
private Long carId;
That error message means that you have have a transient instance in your object graph that is not explicitly persisted. Short recap of the statuses an object can have in JPA:
Transient: A new object that has not yet been stored in the database (and is thus unknown to the entitymanager.) Does not have an id set.
Managed: An object that the entitymanager keeps track of. Managed objects are what you work with within the scope of a transaction, and all changes done to a managed object will automatically be stored once the transaction is commited.
Detached: A previously managed object that is still reachable after the transction commits. (A managed object outside a transaction.) Has an id set.
What the error message is telling you is that the (managed/detached) Driver-object you are working with holds a reference to a Car-object that is unknown to Hibernate (it is transient). In order to make Hibernate understand that any unsaved instances of Car being referenced from a Driver about be saved should also be saved you can call the persist-method of the EntityManager.
Alternatively, you can add a cascade on persist (I think, just from the top of my head, haven't tested it), which will execute a persist on the Car prior to persisting the Driver.
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
#JoinColumn(name = "CAR_ID")
private Car car;
If you use the merge-method of the entitymanager to store the Driver, you should add CascadeType.MERGE instead, or both:
#ManyToOne(fetch=FetchType.LAZY, cascade={ CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "CAR_ID")
private Car car;
public void setCarId(long carId) {
this.car = new Car (carId);
}
It is actually not saved version of a car. So it is a transient object because it hasn't id. JPA demands that you should take care about relations. If entity is new (doesn't managed by context) it should be saved before it can relate with other managed/detached objects (actually the MASTER entity can maintain it's children by using cascades).
Two ways: cascades or save&retrieval from db.
Also you should avoid set entity ID by hand. If you do not want to update/persist car by it's MASTER entity, you should get the CAR from database and maintain your driver with it's instance. So, if you do that, Car will be detached from persistence context, BUT still it will have and ID and can be related with any Entity without affects.
Add optional field equal false like following
#ManyToOne(optional = false) // Telling hibernate trust me (As a trusted developer in this project) when building the query that the id provided to this entity is exists in database thus build the insert/update query right away without pre-checks
private Car car;
That way you can set just car's id as
driver.setCar(new Car(1));
and then persist driver normal
driverRepo.save(driver);
You will see that car with id 1 is assigned perfectly to driver in database
Description:
So what make this tiny optional=false makes may be this would help more https://stackoverflow.com/a/17987718
Here's the missing article that Adi Sutanto linked.
Item 11: Populating a Child-Side Parent Association Via Proxy
Executing more SQL statements than needed is always a performance penalty. It is important to strive to reduce their number as much as possible, and relying on references is one of the easy to use optimization.
Description: A Hibernate proxy can be useful when a child entity can be persisted with a reference to its parent ( #ManyToOne or #OneToOne lazy association). In such cases, fetching the parent entity from the database (execute the SELECT statement) is a performance penalty and a pointless action. Hibernate can set the underlying foreign key value for an uninitialized proxy.
Key points:
Rely on EntityManager#getReference() In Spring
use JpaRepository#getOne() Used in this example,
in Hibernate, use load()
Assume two entities, Author and Book, involved in a unidirectional #ManyToOne association (Author is the parent-side) We fetch the author via a proxy (this will not trigger a SELECT), we create a new book
we set the proxy as the author for this book and we save the book (this will trigger an INSERT in the book table)
Output sample:
The console output will reveal that only an INSERT is triggered, and no SELECT
Source code can be found here.
If you want to see the whole article put https://dzone.com/articles/50-best-performance-practices-for-hibernate-5-amp into the wayback machine. I'm not finding a live version of the article.
PS. I'm currently on a way to handle this well when using Jackson object mapper to deserialize Entities from the frontend. If you're interested in how that plays into all this leave a comment.
Use cascade in manytoone annotation
#manytoone(cascade=CascadeType.Remove)
This question already has answers here:
How to fix the Hibernate "object references an unsaved transient instance - save the transient instance before flushing" error
(33 answers)
Closed 2 years ago.
I have a database with two tables User and Country. I want to relationship where many user can belong to one county. I implement this using hibernate using the following model classes:
#Entity (name = "user")
public class User {
#Id #GeneratedValue (strategy = GenerationType.IDENTITY)
private int userId;
private String username;
private String password;
#ManyToOne ()
#JoinColumn (name = "countryId")
private Country country;
//Getter & Setter
}
#Entity (name = "country")
public class Country {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private int countryId;
private String countryName;
//Getter & Setter
}
When I try to save the user object I get the following exception:
org.hibernate.HibernateException: Data was not saved: object references an unsaved transient instance - save the transient instance before flushing: com.kyrogaming.models.Country
How can I fix the problem?
You can't save things to Hibernate until you've also told Hibernate about all the other objects referenced by this newly saved object. So in this case, you're telling Hibernate about a User, but haven't told it about the Country.
You can solve problems like this in two ways.
Manually
Call session.save(country) before you save the User.
CascadeType
You can specify to Hibernate that this relationship should propagate some operations using CascadeType. In this case CascadeType.PERSIST would do the job, as would CascadeType.ALL.
Referencing existing countries
Based on your response to #zerocool though, you have a second problem, which is that when you have two User objects with the same Country, you are not making sure it's the same Country. To do this, you have to get the appropriate Country from the database, set it on the new user, and then save the User. Then, both of your User objects will refer to the same Country, not just two Country instances that happen to have the same name. Review the Criteria API as one way of fetching existing instances.
Looks like Users that are added in your Country object, are not already present in the DB. You need to use cascade to make sure that when Country is persisted, all User which are not there in data but are associated with Country also get persisted.
Below code should help:
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "countryId")
private Country country;
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 anyway 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
Same answer given here
I had the same problem. In my case it arises, because the lookup-table "country" has an existing record with countryId==0 and a primitive primary key and I try to save a User with a countryID==0. Change the primary key of country to Integer. Now Hibernate can identify new records.
For the recommendation of using wrapper classes as primary key see this stackoverflow question
It should be CascadeType.Merge, in that case it will update if the record already exists.
Well if you have given
#ManyToOne ()
#JoinColumn (name = "countryId")
private Country country;
then object of that class i mean Country need to be save first.
because it will only allow User to get saved into the database if there is key available for the Country of that user for the same. means it will allow user to be saved if and only if that country is exist into the Country table.
So for that you need to save that Country first into the table.
It is because of CASCADE TYPE
if you put
#OneToOne(cascade=CascadeType.ALL)
You can just save your object like this
user.setCountry(country);
session.save(user)
but if you put
#OneToOne(cascade={
CascadeType.PERSIST,
CascadeType.REFRESH,
...
})
You need to save your object like this
user.setCountry(country);
session.save(country)
session.save(user)
I am trying to establish a relationship between 2 entities which would be zero-to-one. That is, the Parent can be saved without the associated Child entity and also along with the assoicated Child.
Following are the 2 Entity classes...
Employee (Parent)
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name="EMP_NAME")
private String name;
#PrimaryKeyJoinColumn
#OneToOne(cascade = {CascadeType.ALL})
private EmployeeInfo info;
#Column(name="EMP_ENUM")
private Integer enumId;
EmployeeInfo (Child)
public class EmployeeInfo {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name="EMPLOYEE_EMAIL")
private String email;
With such kind of a relation and id column of the only Parent (Employee) table set to AUTO INCREMENT in MySql DB, the problem is that while saving a Parent->Child object graph, I get the following exception
org.springframework.orm.hibernate3.HibernateJdbcException: JDBC exception on Hibernate data access: SQLException for SQL [insert into EMP_INFO
Caused by: java.sql.SQLException: Field 'id' doesn't have a default value
I tried setting the Child Table's Id property to AUTO INCREMENT in the DB , and the persistence of such a Parent->Child object graph is successful.
However, the problem described here surfaces, because I have a scenario in which I would like to save the parent (Employee) object without the associated EmpInfo object, and hence do NOT want to have AUTO INCREMENT on the Child's id column.
One solution could be not use the PrimaryKeyJoinColumn, but use a particular JoinColumn, but that adds an unnecessary column to my existing Table.
Has anyone come across such a problem? If yes, any pointers would be much helpful.
Finally, I got it working thanks to Pascal and some googling from my side. Apparently, I cannot use the Native key generator for such relationships where the parent can exist without the child (optional = true).
The thing that worked finally was the following, leaving me the downside of having to deal with Hibernate specific annotation (#GenericGenerator) and also having to make-do with bi-directional relationships instead of the unidirectional that I wanted.
Employee (Parent) class remains unchanged as above. It has AUTO INCREMENT on the Id column.
As for the child class (EmployeeInfo) it changed to the following, and again WITHOUT having the AUTO INCREMENT set on the Id column.
#Table(name="EMP_INFO")
#Entity
public class EmployeeInfo {
#Id
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign", parameters={
#Parameter(name="property", value="verifInfo")})
private Long id;
#OneToOne(optional=false)
#JoinColumn (name="id")
private Employee emp;
#Column(name="EMPLOYEE_EMAIL")
private String email;
This helped me achieve what I wanted but on the downside, GenericGenerator is not a JPA annotation, it is a hibernate annotation, and sadly I have to make do with that as of now because JPA does not currently support this(or any similar) annotation.
Anyway, it helps to get through such cases :-)
I have a scenario in which I would like to save the parent (Employee) object without the associated EmpInfo object.
The optional attribute of a OneToOne is true by default, which is what you want.
However, you are somehow misusing the #PrimaryKeyJoinColumn here (well, it actually depends on what you really want to achieve but your current combination of annotations is not correct).
IF you want to map a OneToOne with a shared primary-key, use the #PrimaryKeyJoinColumn. But in that case, don't use a GeneratedValue on EmployeeInfo and set the id manually or, if you don't want to set it manually, use the Hibernate specific foreign generator that I already mentioned in your previous question. Check also the related question mentioned below.
And IF you do not want to use a shared primary key (like in your current code since you're trying to get the id generated by the database), then do not use the PrimaryKeyJoinColumn.
You have to make a choice.
References
JPA 1.0 specification:
9.1.32 PrimaryKeyJoinColumn Annotation
Related question
JPA Hibernate One-to-One relationship.