Updating a "complex" Entity in JPA - java

entity = MainDao.findByUUID(getEntityManager(), dto.getUuid());
Adress i = new AdressDao().findById(dto.getIdAdress());
i.setPhone(dto.getPhone());
entity.setAdress(i);
return MainDao.update(getEntityManager(), entity);
I have a main Entity in which there is a #ManytoOne relationship to Adress. I want to update the field "phone" inside adress, how do I do it? My code fails to do so.
Hope you can help me out, it seems there is no "patch" method inside JPA. I would love to know the best practices.

By default #ManyToOne doesn't cascade the changes (as it refers to a parent which may be have other child associations).
You can do either of below,
save the changes of Address entity via your AddressDao like addressDao.save(addressEntity)
use #ManyToOne(cascade = CascadeType.ALL).
1st options is preferable.
Read about CascadeType to utilize wisefully.

Related

SpringData/Hibernate #ManyToOne cascading automatically when it should not

Given two entities like so
#Entity
public class Thing {
#ManyToOne
private ThingType thingType;
...
}
#Entity
public class ThingType {
private String name;
...
}
From everything I have read, the default cascade should be nothing so if I get a reference to a Thing thing, and change the name field of its ThingType, then using a JpaRepository<Thing, Long> call thingRepo.save(thing), I would expect the change to ThingTypes name not to be persisted.
However this is not the case and the change is persisted. I am not sure why this is happening? What I am missing here?
Relevant versions:
org.springframework.boot:spring-boot:jar:1.5.7.RELEASE
org.hibernate:hibernate-core:jar:5.0.12.Final
org.springframework.data:spring-data-jpa:jar:1.11.7.RELEASE
Well, I would have expected the same, but it seems that Hibernate has its own default behaviour. In the Hibernate forum someone asked almost the same question. The answer refers to the Hibernate "dirty check" feature, which will detect and perstst/merge the change. You might change that behaviour by using Hibernate's Cascade annotation.
Well cascading is something else, let me ask you something. Do following things:
Thing thing = session.get(Thing .class, someId);
thing.getThingType().setTitle("new title");
and nothing more, again you see hibernate updates thingType.
It is called dirty checking, as long as an entity is attached to an active hibernate session, and its persistence state changes hibernate automatically updates its associated row in database. Event without calling a save or update.
So what is cascade?
Consider following case:
Thing myThing = new Thing();
ThingType myThingType = new ThingType();
myThing.setThingType(myThingType);
session.save(myThing);
if the association cascade type is not set, then you will get an exception, because you are referencing a transient thingType object. But if you set the cascade type to persist, then hibernate first saves the thingType and then saves the thing, and everything goes fine.
So remember, if you fetch an object, then update its properties in the same session, there is no need to call a update or saveOrUpdate method on hibernate session (or jpa entityManager) because it is already in attached state, and its state is traced by hibernate.

JPA many-to-one relation - need to save only Id

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)

Spring Data JPA causes EntityExistsException with cascading save

I have a Challenge class, which has a many to one relationship with the User class. It is uni-directional, so it looks like this:
#Entity
#Table(name = "UserTable")
public class User {
#Id
private String userId;
}
#Entity
#Table(name = "ChallengeTable")
public class Challenge {
#Id
private String challengeId;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinColumn(name = "userId")
private User user;
}
I'm using Spring Data JPA, and when I use the save method from the CRUDRepository on a Challenge object, I want it to persist the attached user if that user doesn't already exist, and merge the user into the old user if it does already exist.
I'm using a findOne(String id) method in the UserRepository to get a user using a userId, and that's the user I'm setting in the Challenge.
It cascades just fine if the user doesn't already exist, but when I try to save it with a pre-existing user I get the exception:
javax.persistence.EntityExistsException: a different object with the same identifier value was already associated with the session: [com.mywebsite.model.User#zk9moo78sx685g6o9yphegdx6lpoll9x]
I'm not sure what I'm doing wrong here. Changing the CascadeType to ALL doesn't change anything. Trying to remove the CascadeType entirely and manually saving the User first doesn't work either. That gives me the error:
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing: com.mywebsite.model.Challenge.user -> com.mywebsite.model.User
That appears to take place when the transaction exits (as I have my service layer class annotated with #Transactional).
If I take out the #Transactional annotation and manually persist the user it seems to all work fine. (I still want the cascading saves and transactions on the service level though.)
Taking out the #Transactional and trying to use cascading saves fails with a SQLIntegrityConstraintViolationException exception because it seems like the User becomes a detached entity and it tries to persist it anew, but that primary key already exists so it fails.
Can anyone help me understand what's going on here, and help me get cascading saves working with transactions in Spring Data JPA?
I tried using hibernate-specific cascading options, and everything else I could think of, but I couldn't get cascading saves to work as a Hibernate CascadeType.SAVE_UPDATE is supposed to. I believe it's a hard limitation of JPA with Spring Data.
Instead, I added a layer between the service and the interface repository. It saves the dependent entity (the user) then the challenge.

Hibernate: Many To Many Relation with attributes: correct configuration with Annotations

I have a problem with Hibernate (Thanks to Thomas now the problem is more legible).
In Short:
How to configure a ManyToMany association with Hibernate when the relationship has an attribute and we need save, delete and update in cascade?
In Large:
Imagine the following DataBase:
User Profile
M______N
|
attribute
There are 3 tables here:
"User", "Profile" and "User_Profile".
Now imagine User_Profile has 1 attribute for the relation (and obviously the keys).
Ok, now this is translating to Hibernate by the following:
User:
// The relation is Many to Many, but considering that it has an attribute, this is OneToMany with the ManyMany RelationShip
#OneToMany(mappedBy="user", targetEntity=UserProfile.class)
#Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
#LazyCollection(LazyCollectionOption.TRUE)
private Set<UserProfile> userProfile = new HashSet<UserProfile>();
UserProfile:
#Id
#ManyToOne(targetEntity=User.class,fetch=FetchType.LAZY)
#Cascade({CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="...", referencedColumnName="...") })
private User user;
#Id
#ManyToOne(targetEntity=Profile.class,fetch=FetchType.LAZY)
#Cascade({CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="...", referencedColumnName="...") })
private Profile profile;
So, I think the configuration is correct, and the save, independently if the User has Profile childrens save all of them. The problem is when I try to update the user:
getHibernateTemplate().getSessionFactory().getCurrentSession().clear();
getHibernateTemplate().saveOrUpdate( user );
getHibernateTemplate().getSessionFactory().getCurrentSession().flush();
Hibernate don´t delete the Profile relation if there is an empty set of Profile childrens. Only add the profiles (override the old)... That´s rare... What´s the problem?
Thank you in advance
All you actually do is remove the relation and thus theres no DELETE to cascade, that's why nothing gets deleted.
Try adding the Hibernate cascade type DELETE_ORPHAN (using the #Cascade annotation) to make Hibernate delete entities that are not referenced anymore.
Additionally, I'd not remove the Mini entities alone. If there's no relation, i.e. the set of Minis is empty, it normally makes no sense to keep the SuperMini entities that now represent an empty collection (in rare cases it might make sense, just want you to think about whether you need them or not).
Edit:
Note that with DELETE_ORPHAN you should reuse the set, otherwise all the relations might be deleted and reinserted.
Basically Hibernate would then see the set being changed and would issue a delete for the "old" set and a reinsert for the "new" set. This could be wanted but in case you only want an update, i.e. only delete the entities that are not in the set anymore, you should do the following:
clear the set
add the "new" set to the reused and now cleared set using addAll(...)
This should trigger the update (and deletion of orphans) only.

Hibernate: One To Many Relation on cascade save but doesn't delete

I have a problem with Hibernate.
In Short:
How to configure a ManyToMany association with Hibernate when the relationship has an attribute and we need save, delete and update in cascade?
In Large:
Imagine the following DataBase:
Super Mini
M______N
|
attribute
There are 3 tables here:
"Mini", "Super" and "Super_Mini".
Now imagine Super_Mini has 1 attribute for the relation (and obviously the keys).
Ok, now this is translating to Hibernate by the following:
Super:
// The relation is Many to Many, but considering that it has an attribute, this is OneToMany with the ManyMany RelationShip
#OneToMany(mappedBy="mini", targetEntity=Mini.class)
#Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
#LazyCollection(LazyCollectionOption.TRUE)
private Set<SuperMini> superMini = new HashSet<SuperMini>();
SuperMini:
#Id
#ManyToOne(targetEntity=Super.class,fetch=FetchType.LAZY)
#Cascade({CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="...", referencedColumnName="...") })
private Super super;
#Id
#ManyToOne(targetEntity=Mini.class,fetch=FetchType.LAZY)
#Cascade({CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="...", referencedColumnName="...") })
private Mini mini;
So, I think the configuration is correct, and the save, independently if the object has Mini childrens save all of them. The problem is when I try to delete the object:
Super data = getHibernateTemplate().load(Super.class, idSuper);
getHibernateTemplate().getSessionFactory().getCurrentSession().clear();
data.setMini( new HashSet<Mini>() );
getHibernateTemplate().delete( data );
getHibernateTemplate().getSessionFactory().getCurrentSession().flush();
Hibernate don´t delete the Mini relation... What´s the problem? I know how to solve it by HQL, but maybe the configuration is not correct, I don´t know.
Thank you in advance,
Your question is not clear. Super does not contain a Set<Mini2>. It contains a Set<SuperMini2>. So the last code snippet doesn't make much sense.
Moreover, the targetEntity attribute on Super.superMini2 is incorrect, and unnecessary.
CascadeType.ALL include CascadeType.DELETE, so it's also unnecessary.
But to answer your question, I think the problem is that deleting Super cascades to SuperMini2 because the association has a cascade delete, but there is no cascade delete between SuperMini2 and Mini2, so of course, Mini2 instances are not deleted.
EDIT:
The answer is that the OP, before editing the question, removed all the entities from the collection of SuperMini before deleting the Super entity. So the cascade delete on the collection of Supermini didn't have anything to delete anymore.

Categories