JPA: delete parent with related children - java

I have following entities:
#Entity
#Table(name = "chat",
uniqueConstraints = {
#UniqueConstraint(columnNames = {"user_1", "user_2"})
})
public class Chat {
#ManyToOne
#JoinColumn(name = "user_1")
#OnDelete(action = OnDeleteAction.CASCADE)
private User user1;
#ManyToOne
#JoinColumn(name = "user_2")
#OnDelete(action = OnDeleteAction.CASCADE)
private User user2;
}
User class:
#Entity
#Table(name="users",
uniqueConstraints={
#UniqueConstraint(columnNames={"company_id", "username"})
}
)
public class User {
#Id
#GenericGenerator(name = "uuid-gen", strategy = "uuid2")
#GeneratedValue(generator = "uuid-gen",strategy=GenerationType.IDENTITY)
private String id;
// there is no field/reference to Chat entity
}
And User entity without any references to Chat entity. I need to remove user with it's chats. Problem is that user id (that I want to remove) could be either in user1 or user2 field. For example, I have user A and user B. They have chat C. And if I try to remove, for example, user A, it should remove user A and chat C. But with provided configuration, I have following error:
Cannot delete or update a parent row: a foreign key constraint fails
(`mydb`.`chat`, CONSTRAINT `FKqslncg7pcc89gvjjpp9jypbha`
FOREIGN KEY (`user_2`) REFERENCES `users` (`id`))
As possible solution I used this answer. But using of
entityManager.remove(user);
entityManager.clear();
does not help. Also, I checked ddl code and there is no any mention of Cascade actions.
How to fix this?

I found a solution. The first thing that you need is to add the human-readable ForeignKey constraint and remove #OnDelete. Now my code looks like this:
#ManyToOne
#JoinColumn(name = "user_1", foreignKey = #ForeignKey(name = "user_1_fk"))
private User user1;
#ManyToOne
#JoinColumn(name = "user_2", foreignKey = #ForeignKey(name = "user_2_fk"))
private User user2;
Then I dropped this table, launched an application to allow hibernate re-create this table with proper FK names provided in annotations. Then I opened MySQL workbench and modified foreign keys for this table using following SQL:
ALTER TABLE chat DROP FOREIGN KEY `user_1_fk`;
ALTER TABLE chat DROP FOREIGN KEY `user_2_fk`;
ALTER TABLE chat
ADD CONSTRAINT `user_1_fk`
FOREIGN KEY (`user_1` )
REFERENCES `users` (`id` )
ON DELETE CASCADE;
ALTER TABLE chat
ADD CONSTRAINT `user_2_fk`
FOREIGN KEY (`user_2` )
REFERENCES `users` (`id` )
ON DELETE CASCADE;
That's all.

Related

Deleting records from tables that have foreign keys. Java + Hibernate + MySQL

When I am trying to delete User from the users table with the help of Hibernate using its id it occurs an exception:
Caused by:com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`ewp`.`user_task_history`, CONSTRAINT `FK_mkjvq9fr0e1hdgi3ekl0hluuu` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`))`.
And Code:
Class User:
#Entity
#Table(name = "users")
public class User implements UserDetails{
...
#OneToMany(targetEntity = UserTaskHistory.class, fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
private Set<UserTaskHistory> userTaskHistories;
...
Class UserTaskHistory:
#Entity
#Table(name = "user_task_history")
public class UserTaskHistory{
...
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
...
The same thing happens when I'm trying to delete other objects having foreign keys. Is there any other way to make Hibernate to delete by itself all the objects that have links of foreign keys of the object I'm trying to delete?

OneToOne bidirectional mapping foreign key auto fill

I have a relationship one-to-one between two tables, but the foreign key is on the one I don't need to map, the DBA did this in favor of future changes.
Let's imagine we have User and Address, today every user has only one address, and it will be mapped this way, but DBA believe in the future it could be a one to many mapping, so the foreign key of user is on the address, but the application have instances of users, which is important to fetch address automatically.
We did it right, as follow:
#Entity
#Table(name = "user")
class User {
#Id
#Column(name = "user_id")
private Long id;
//...
#OneToOne(cascade = CascadeType.MERGE, mappedBy = "user")
private Address address; // this attribute is crucial
}
#Entity
#Table(name = "address")
class Address {
#Id
#Column(name = "address_id")
private Long id;
#OneToOne
#JoinColumn(name = "user_id")
private User user; // this attribute is not needed for the business at all, but mappedBy requires it
//...
}
Database:
-- SQL:
CREATE TABLE user
(
user_id INT NOT NULL,
-- ...
CONSTRAINT user_pk PRIMARY KEY (user_id)
);
CREATE TABLE address
(
address_id INT NOT NULL,
user_id INT NOT NULL,
-- ...
CONSTRAINT address_pk PRIMARY KEY (address_id),
CONSTRAINT address_user_id_fk FOREIGN KEY (user_id) REFERENCES user (user_id),
CONSTRAINT address_user_id_uk UNIQUE (user_id) -- it says it's a one to one relation for now, if in the future it won't be anymore, just remove this constraint
);
The problem is when save a instance of user with a new instance of address, the user's attribute of address is null, so I was expecting Hibernate was smart enough to set it the value from the user's instance it comes from.
I'd been looking for a solution for a couple of days, but still didn't find how to solve this, meanwhile I'm setting the value manually, but I expect I don't need to do so.
The standard solution is to properly update both sides of the bidirectional association (although only the owning side needs to be updated for the association to be saved to the database). Add to the Address setter in the User class:
public void setAddress(Address address) {
this.address = address;
address.setUser(this);
}
Also, you may want to extend cascading options for the address property to include PERSIST as well, so that it is always persisted together with its user:
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "user")
private Address address;
Then you can set an address to a user and persist both:
user.setAddress(address);
session.persist(user);
If "private User user" is not needed, delete it and delete also mappedBy in 'User' entity. Use a uni-directional relation.
In the case of your example, mappedBy means that the owner of the association is the 'Address' entity, so save the instance of Adress and not User. Like :
adress.setUser(user);
session.save(adress);
You need to add CasecadeType.PERSIST to make the creation Address casecade with creation of User.
In your java code, you need to do:
user.setAddress(address);
address.setUser(user);
session.persist(user);
Then your user will be created with an address.
If when you just want to read the Address of a new created User, then you need to do :
// your code to persist a new User and Address
session.flush();
session.refresh(user);
If it's not what you want, then you need to share your own Java code and give a detailed description on what you're expecting.

Hibernate Code Generation and #Cascade({CascadeType.ALL})

Please consider the following mySQL tables:
CREATE TABLE storeman.user (
id INT NOT NULL AUTO_INCREMENT,
email VARCHAR(50) NOT NULL,
display_name VARCHAR(50),
password CHAR(32),
...
PRIMARY KEY (id),
UNIQUE INDEX (email)
);
CREATE TABLE storeman.user_preferences (
id INT NOT NULL AUTO_INCREMENT,
notify_login BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (id),
CONSTRAINT fk_user_preferences FOREIGN KEY (id) REFERENCES user (id) ON DELETE CASCADE
);
As you may see there's a one-to-one relationship between the two tables.
When running the Hibernate Code Generator, I get the follwoing (relevant part only)
#Entity
#Table(name = "user", catalog = "storeman", uniqueConstraints = #UniqueConstraint(columnNames = "email"))
public class User implements java.io.Serializable {
...
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
public UserPreferences getUserPreferences(){
return this.userPreferences;
};
...
}
The issue with that is when I save the User entity, the linked UserPreferences is not saved automartically. Solving this is easy:
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
#Cascade({CascadeType.ALL})
public UserPreferences getUserPreferences(){
return this.userPreferences;
};
However, If for any reason I will have to re run the Hibernate Code Generator again, the #Cascade({CascadeType.ALL}) will be gone, and this is dangerous as I'm relying on the fact that linkled tables are automatically saved in my code.
So the question is: is there a way to modify mySQL script on top so that, while running hibernate reverse engineering code generation, the #Cascade annotation is automatically added?
mySql physical table doesn't say anything about cascading other then the foreign key. Only it can add ON DELETE CASCADE ON DELETE UPDATE.
Then you run Hibernate Code Generator, CascadeType and #Cascade definitions are not translated into DDL
Try to use jpa annotations much as possible.
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user",cascade= CascadeType.ALL)
public UserPreferences getUserPreferences(){
return this.userPreferences;
};

JPA - Mapping OneToMany association between the same table using an intermediate table

I'm creating an application where one large aspect is the ability for users to share content with friends. I'm trying to represent this in the object model and I'm having trouble getting the association to work properly. I'm using a mapping table that records the friender and the friendee, both of which are represented by the primary key (id) of the user. A user can have many friends, and also be referenced by other users. This is what the schema looks like:
Users:
int user_id (PK)
varchar(32) email
varchar(64) password
Users_Map:
int users_map_id (PK)
int friendee_id (FK references users(user_id))
int friender_id (FK references users(user_id))
And this is how I have the User entity set up:
#Data
#Entity
#Table(name = "users")
public class User extends AbstractPersistable<Long> {
#Id
#Column(name = "user_id")
private Long id;
#Column
private String email;
#Column
private String password;
#OneToMany
#JoinTable(name = "users_map",
joinColumns = { #JoinColumn(name = "friender_id") },
inverseJoinColumns = { #JoinColumn(name = "friendee_id") })
private List<User> friends;
}
I run into the following error when deploying the application:
org.hibernate.AnnotationException: A Foreign key refering
com.x.webapp.data.entity.User from
com.x.webapp.data.entity.User has the wrong number of
column. should be 2
I've tried quite a few other configurations, including adding a "referencedColumnName" attribute to each #JoinColumn, but they have also yielded errors. I'm also not entirely sure whether the schema I currently have is the best way to go about mapping users together.
I appreciate any help!
Removing the extension of AbstractPersistable fixed the problem - that contained an #Id reference and clashed with the #Id reference I put inside of User.

Java hibernate: Update foreign key in manytoone relationship automatically

I have a problem with the auto update of foreign keys which appears as following:
I have a two tables HamKeyword and HamKeywordAlias. One entry in the hamKeyword has 0…n entries in HamKeywordAlias. This relationship is reflected with a foreign key field in the HamKeywordAlias table. Both tables have their own primary keys. I defined the two tables using reverse engineering of hibernate eclipse tools as follows:
#Entity
#Table(name = "HAM_KEYWORDS")
public class HamKeywords implements java.io.Serializable {
private long keywordid;
private String keyword;
…
#Id
#GenericGenerator(name="gen",strategy="increment")
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "KEYWORDID", unique = true)
public long getKeywordid() {
return this.keywordid;
}
…
#OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL, mappedBy = "hamKeywords")
public Set<HamKeywordsAlias> getHamKeywordsAliases() {
return this.hamKeywordsAliases;
}
#Entity
#Table(name = "HAM_KEYWORDS_ALIAS", schema = "dbo", catalog = "ham")
public class HamKeywordsAlias implements java.io.Serializable {
#Id
#GenericGenerator(name="gen",strategy="increment")
#GeneratedValue(generator="gen")
#Column(name = "ALIASID", unique = true, nullable = false)
public long getAliasid() {
return this.aliasid;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "KEYWORDID", nullable = false, updatable = false, insertable = true)
public HamKeywords getHamKeywords() {
return this.hamKeywords;
}
Now to my problem. I try to add a new entry to HamKeyword with 1 new related HamKeywordAlias:
HamKeywords hkw = new HamKeywords();
HamKeywordsAlias hka = new HamKeywordsAlias();
hka.setAlias("new alias");
hkw.setHamKeywordsAliases(new HashSet<HamKeywordsAlias>());
Set<HamKeywordsAlias> hkaS = hkw.getHamKeywordsAliases();
hkaS.add(hka);
hkw.setHamKeywordsAliases(hkaS);
session.flush();
session.save(hkw);
session.getTransaction().commit();
This code fails with the error message:
ERROR: The value NULL can not be inserted in table 'KEYWORDID'-Spalte, 'ham.dbo.HAM_KEYWORDS'. No NULL allowed for INSERT. Exception in thread "main" org.hibernate.exception.ConstraintViolationException: could not execute statement
(Please note that I translated the error message into english, it might be a bit different languagewise)
Obviously, the foreign key in field KEYWORDID of the HamKeywordAlias table is not be updated. I double checked this by removing the NOT NULL constraint. What happens is, that the enty into the ALIAS table is inserted but with a NULL in the field keywordid.
I tested furthermore adding manually rows into the HamKeywordAlias table. Retrieving an entry of the HamKeyword table and retrieving the related Aliases with following code works great:
HamKeywords hamCurrentKeyword = (HamKeywords) session.get(HamKeywords.class, (long)1);
hamCurrentKeyword.getHamKeywordsAliases();
Thus I assume that I defined the many to one relation correctly. However, the foreign key is not updated automatically.
Can you assist me why this is not be done?
Thanks
Felix
You have a bidirectional OneToMany association. The owner of the association is the Many side: HamKeywordsAlias.hamKeywords. That's the side that Hibernate cares about. But you didn't initialize it. You added an alias to the keywords' collection of aliases, but failed to set the keywords of the alias:
hka.setHamKeywords(hkw);

Categories

Resources