Reputation: 1538
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:
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.
Upvotes: 63
Views: 105166
Reputation: 11
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 theSELECT
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 Springuse
JpaRepository#getOne()
Used in this example,in Hibernate, use
load()
Assume two entities,
Author
andBook
, involved in a unidirectional@ManyToOne
association (Author is the parent-side) We fetch the author via a proxy (this will not trigger aSELECT
), we create a new bookwe 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 noSELECT
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.
Upvotes: 1
Reputation: 103
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.
Upvotes: 3
Reputation: 3293
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.
Upvotes: 65
Reputation: 813
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.
Upvotes: 49
Reputation: 5071
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
Upvotes: 1
Reputation:
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;
Upvotes: 19
Reputation: 12225
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:
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;
Upvotes: 11
Reputation: 51
Use cascade in manytoone annotation @manytoone(cascade=CascadeType.Remove)
Upvotes: -8