I have a simple unidirectional ManyToOne relationship on an entity, which unfortunately is defined in a schema I cannot change.
It is defined as follows
#Entity
#Table(name="Profile")
...
public class Profile{
#ManyToOne
#JoinColumn(name="usr_id", nullable=false, updatable=false)
private User usr;
...
and all is well. The relationship is enforced with a foreign key in the db, hence the nullable = false and updatable = false. There is no mention of the profiles in user.
When I try to delete a Profile, hibernate also tries to delete the User entity, which is parent to other relationships and therefore fails. I have no CascadeType annotations anywhere.
My intent is to have a simple reference to the user using this profile in the usr field. This is a unidirectional relationship. The user entity should not be affected whenever I delete a profile.
This appers to be achievable when the usr field may be dereferenced before delete (I can see in the hibernate generated sql that hibernate attempts to set the field to null before deletion) - however that fails because of the foreign key.
Is what I'm trying to do achievable? If so, how?
(I'm using spring data on top of hibernate, if that is relevant.)
further Infos: I have tried optional=false, and it leads to the delete the parent entity behaviour. I have tried all fitting combinations of CascadeTypes, #OnDelete with NO_ACTION (still tries to delete the user) and defining a reverse but owned by user relationship - no success so far. On top of that, I tried the search function ;), which lead me to the conclusion that this is just my problem. If I missed an answered question, I'd appreciate a pointer in the right direction. Thanks.
Do you have some kind of other non-nullable association #ManyToOne or #OneToOne to the Profile entity? You can debug into the deletion process by setting a break point in e.g. the JDBC driver in e.g. Connection.prepareStatement and go down the stack frames to the cascading part to figure out why this cascading happens.
If all that doesn't help, please create a reproducing test case and submit an issue to the Hibernate issue tracker.
Related
I am yet again stuck with trying to delete data with Hibernate..
I am at point where I am starting to just stack annotation, hoping something would work... so far nothing has.
I need to delete old calculation when starting new for same time period.
I have the following query
#Modifying
#QueryHints(value = #QueryHint(name = HINT_FETCH_SIZE, value = "10"))
#Query(value = "DELETE FROM Group a WHERE a.c_date BETWEEN :dateA AND :dateB")
void deleteOld(#Param("dateA") LocalDate dateA, #Param("dateB") LocalDate dateB);
which uses entity Group, which has (on top of normal String, LocalDate and long types) attribute
#OneToMany(cascade=CascadeType.ALL, mappedBy = "owner", orphanRemoval = true)
#JsonManagedReference
#OnDelete(action = OnDeleteAction.CASCADE)
private List<Instrument> instruments = new ArrayList<>();
But I still get violated - child record found every time I try to run delete method.
I keep finding more and more annotations like this, from threads where people have the same kind of problems, but I would love to understand why is this a problem. From what I read Cascade and orphanRemoval should be all I need, but it sure does not seem to be.
Hibernate: 5.2.17.Final
Please help me to understand, why is this happening ?
The #OnDelete will delete records using a ON DELETE rule on the database, when using Hibernate to generate the schema. If you manage your own schema this will have no effect.
The #QueryHints you have specified doesn't really make sense here, for an DELETE query that is. Nothing will be fetched.
The fact that you are using an #Query basically bypasses the configuration in the #OneToMany, simply due to the fact that you write a query and apparently know what you are doing. So the mapping isn't taken into account.
If you want to delete the childs as then you have 3 options:
Add an additional query and first remove the childs, then the parents
Add an ON DELETE rule to your database, to automatically remove the childs
Retrieve the Group and remove using EntityManager.remove which will take the #OneToMany mappings into account as now Hibernate needs to manage the dependencies between the entities.
I'd like to remove entities from my table and have it auto-removed any entities that are childs of it.
Example:
class User {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval=true)
#OnDelete(action = OnDeleteAction.CASCADE)
List<Address> addresses;
}
When I remove a User that has no address, everything works fine. Also removing an address without removing the user works.
But: If I try to remove a user that has still some addresses, I'm getting org.hsqldb.HsqlException:
integrity constraint violation: foreign key no action; FK_ADDRESS_USER_ID table: ADDRESS
What might be wrong here?
Or is this not supported and I have to explicitly remove all contained Address objects first before deleting a user?
I believe you have a problem with a foreign key constraint. Use a DB tool like Aqua Data Studio or similar (you can probably also do this in your IDE, in Eclipse - Data Source Explorer view), to show the create script for your ADDRESS table. It should contain something like this:
ALTER TABLE TESTSCHEMA.ADDRESS
ADD CONSTRAINT FK1ED033D4E91AAFD9
FOREIGN KEY(FK_ADDRESS_USER_ID)
REFERENCES TESTSCHEMA.USER(ID)
ON DELETE CASCADE
The point being the ON DELETE CASCADE part in your case. If this is missing or is different, that probably is what is causing the problem. If the table is auto-generated by Hibernate, this constraint should be valid, but keep in mind that there are differences between databases. It could be that the table was generated before the Hibernate's #OnDelete annotation was added, so now you are getting the "foreign key no action" integrity constraint violation.
Not related to the issue, but do note that orphanRemoval=true will try to remove the address from the database, when it is removed from the collection in the User entity.
Also, check this out for details on Hibernate’s support for database ON DELETE CASCADE constraint.
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.
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.
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.