Hibernate, JPA cant delete one-to-many relation - java

I have one-to-many relation:
#Entity
#Table(name = "Users")
public class User {
#Id
#Column(name = "user_id", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "login", nullable = false)
private String login;
#Column(name = "password", nullable = false)
private String password;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "role_id", nullable = false)
private Role role;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = javax.persistence.CascadeType.ALL)
private Set<Contacts> contacts = new HashSet<Contacts>();
And I'm trying to delete User object with all Contacts; I tried to use:
cascade = javax.persistence.CascadeType.ALL
cascade =
javax.persistence.CascadeType.REMOVE
#Cascade(CascadeType.DELETE) from org.hibernate.annotations
#Cascade(CascadeType.DELETE_ORPHAN) from org.hibernate.annotations
but nothing helped. I always get exception:
org.hibernate.util.JDBCExceptionReporter - Cannot delete or update a
parent row: a foreign key constraint fails
(contactmanager.contact, CONSTRAINT contact_ibfk_1 FOREIGN KEY
(user_id) REFERENCES
UPD
Code that deletes a User is as follows:
#Transactional
public void removeUser(User user) {
sessionFactory.getCurrentSession().delete(user);
}
I'll appreciate any help! Thanks.

My recommendation here would be to do the relationship management yourself. Cascading removes can be tricky (especially in a situation like yours where the owner of your bi-directional relationship is not the one declaring the cascade) and often times quite dangerous so I usually prefer to avoid them. Especially if you are running a version of JPA pre-2.0 then you don't have too much of a choice. I would just change the removal method to something like:
#Transactional
public void removeUser(User user) {
Set<Contacts> contacts = user.getContacts();
for (Contact contact : contacts) {
sessionFactory.getCurrentSession().delete(contact);
}
contacts.clear();
sessionFactory.getCurrentSession().delete(user);
}

Related

JPA #JoinTable with composite (2 column) primary keys

In a spring-boot app, I've got the following entity definition:
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#Column(nullable = false, name = "username", length = 100)
private String username;
#JoinTable(name = "userrole",
joinColumns = { #JoinColumn(name = "username") },
inverseJoinColumns = { #JoinColumn(name = "role") }
)
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Role> roles;`
I'm using Spring-data-jpa,Hibernate with H2 as the database.
The trouble is that spring-data-jpa, hibernate always generate/creates the join table (DDL) 'userrole' with a single column primary key. e.g. 'username'.
Hence, if records such as {'username', 'user_role'} and {'username', 'admin_role'} is inserted in the join table ('userrole'), the next insert fails with an error due to the 'duplicate' primary key.
I've tried using both columns in the above definition, as well as the following variation:
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JoinColumns({
#JoinColumn(name = "username"),
#JoinColumn(name = "role") })
private List<Role> roles;`
But that they resulted in the same or worse problems, e.g. and in the latter, even table creation fails because only a single column is used as primary key for the jointable. Role is simply another table with 2 columns 'role' and 'description', basically a role catalog.
How do we specify to JPA that the #JoinTable should use both 'username' and 'role' columns as composite primary keys?
edit:
I tried using an independent table/entity as suggested, thanks #Kamil Bęben
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#Column(nullable = false, name = "username", length = 100)
private String username;
#OneToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.ALL,
mappedBy = "username",
orphanRemoval = true
)
#ElementCollection
private List<UserRole> roles;
UserRole is defined as such
#Data
#NoArgsConstructor
#Entity
#Table(name = "userrole")
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "userrole_seq")
Long id;
#Column(nullable = false, name = "username", length = 100)
private String username;
#Column(nullable = false, name = "role", length = 50)
private String role;
the repository for that user-roles join table is defined as
#Repository
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
UserRole findByUsernameAndRole(String username, String role);
List<UserRole> findByUsername(String username);
List<UserRole> findByRole(String role);
}
Admittedly, ugly, but that it works. And that somehow, it seemed to use the correct findByUsername() method to retrieve the roles as is relevant to the user, probably related to the 'mappedBy' clause. 'black magic'! There's lots more that I'd still need to find my way around JPA, Spring, Spring-data
edit2:
further update:
the original #JoinTable works as well.
But that the relations need to be specified as #ManyToMany
#ManyToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.MERGE
)
#JoinTable(name = "usersroles",
joinColumns = { #JoinColumn(name = "username") },
inverseJoinColumns = { #JoinColumn(name = "role") }
)
private List<Role> roles = new ArrayList<Role>();
This creates 2 column primary keys as expected for the 'users-roles' table
Thanks to #Roman
If Role only has two columns, eg user_id and role, the way to map this in jpa would be as following
#ElementCollection
#CollectionTable(name = "user_roles", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "role")
List<String> roles = new ArrayList<>();
Otherwise, jpa really requires each entity's identifier and join columns to be separate columns, so Role entity would have to have columns like id, user_id and role_name. Could look like this .:
class Role {
#Id
Long id;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id");
User user;
String roleName;
// Other fields
}
And in the User entity
#OneToMany(mappedBy = "user") // user is Field's name, not a column
List<Role> roles = new ArrayList<>();
Further reading

The DELETE statement conflicted with the REFERENCE constraint in dbo.interests

I am running into a problem deleting related entities from my database. I have a trading application where users can post trades and express their interests in other people's trades.
When a user deletes their account, all trades posted and interests expressed by this user should be removed from the database. However, the latter doesn't seem to work (I am also not sure if the first one works as I don't know in what order they get executed). I get the error:
The DELETE statement conflicted with the REFERENCE constraint "FKq9kr60l7n7h3yf82s44rkoe4g". The conflict occurred in database "dbi438161_i438161", table "dbo.interests", column 'user_id'.
Note: I get the same when I try to delete a trade but then the column is 'trade_id'
I do the same for the trades and roles of a user so I think it has to do with what is in my interest entity. I am using CascadeType.ALL annotation to let Hibernate remove related entities
Lists of related entities in user:
#ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name="user_roles",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "role_id") })
private List<Role> roles = new ArrayList<>();
#Transient
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy="user")
private List<Interest> interests = new ArrayList<>();
#Transient
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy="user")
private List<Trade> trades = new ArrayList<>();
Interest entity:
#Entity
#Table(name = "interests")
public class Interest {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int interestId;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
#ManyToOne
#JoinColumn(name = "trade_id", nullable = false)
private Trade trade;
private String comment;
public Interest(User user, Trade trade, String comment) {
this.user = user;
this.trade = trade;
this.comment = comment;
}
public Interest(){
}
}
For comparison, the trade entity:
#Entity
#Table(name = "trades")
public class Trade {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="trade_id")
private int tradeId;
#Column(name="wants")
private String wants;
#Column(name="offers")
private String offers;
#Column(name="date_last_modified")
private LocalDateTime lastModified;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
#Transient
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy="trade")
private List<Interest> interests = new ArrayList<>();
public Trade(String wants, String offers, User user){
this.wants = wants;
this.offers = offers;
this.user = user;
}
public Trade() {
}
}
Does anybody have an idea on what I am doing wrong here? Thanks in advance
Try to set orphanRemoval to true for the following associations:
#OneToMany(cascade=CascadeType.ALL, mappedBy="user", orphanRemoval = true)
private List<Interest> interests = new ArrayList<>();
#OneToMany(cascade=CascadeType.ALL, mappedBy="user", orphanRemoval = true)
private List<Trade> trades = new ArrayList<>();
As it is stated in the documentation:
If the child entity lifecycle is bound to its owning parent so that the child cannot exist without its parent, then we can annotate the association with the orphanRemoval attribute and dissociating the child will trigger a delete statement on the actual child table row as well.
Please also note that you should not use cascade=CascadeType.ALL for the #ManyToMany association as it explained in the documentation:
For #ManyToMany associations, the REMOVE entity state transition doesn’t make sense to be cascaded because it will propagate beyond the link table. Since the other side might be referenced by other entities on the parent-side, the automatic removal might end up in a ConstraintViolationException.

Remove the first entity and its relationship without removing the second entity - Spring Data JPA

I have two entities, one of UserEntity and the other RoleEntity, the user can have multiple roles and the role can be used by multiple users, my entities look like:
#Entity
public class UsersEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false)
private Long id;
//...
#ManyToMany(mappedBy = "users")
private Set<RolesEntity> roles;
//...
// Getters and setters
}
#Entity
public class RolesEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false)
private Integer id;
#NotNull
#Enumerated(EnumType.STRING)
#Column(length = 20)
private RoleEnum name;
#JoinTable(name = "user_roles", joinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "id", nullable = false)}, inverseJoinColumns = {
#JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)})
#ManyToMany
private List<UsersEntity> users;
}
Generally roles are fixed they don't change a lot. Now I have a service:
public void removeUser(Long id) {
if (userRepository.findById(id).isPresent()) {
userRepository.deleteById(id);
} else {
throw new IllegalArgumentException("User not found!");
}
}
My requirement is to remove only the user and not the roles related with this user, which mean remove the user and the relation ship. When I call the previews method I got.
org.postgresql.util.PSQLException: ERROR: update or delete on table "users" violates foreign key constraint "constraint_user_id" on table "user_roles"
Detail: Key (id)=(4) is still referenced from table "user_roles".
Is there any trick to solve this please?
You need to make any references to that UsersEntity to be null.
So basically what is the problem? While RolesEntity has a reference to that UsersEntity class you cannot delete that. The most trivial thing to do is to make a loop for each RolesEntity in your UsersEntity class and remove everything from it.
Then you can successfully delete that user from your db.
Check this out to get more info: How to remove entity with ManyToMany relationship in JPA (and corresponding join table rows)?
I solved my issue like this, I'm not sure if this is a best approach to solve this:
public void removeUser(Long id) {
Optional<UsersEntity> userById = usersRepository.findById(id);
if (userById.isPresent()) {
UsersEntity user = userById.get();
for (RolesEntity role : user.getRoles()) {
role.setUsers(null);
rolesRepository.save(role);
}
usersRepository.delete(user);
} else {
throw new IllegalArgumentException("User not found!");
}
}
I think you can do this by using CascadeType.REMOVE
#ManyToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
#JoinTable(
name = "users_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private List<Role> roles;

Hibernate MappedBy for Multiple columns

I am using Postgresql for my database and it contains a table called user and a table called friendship, which has 2 foreign keys userA_id and userB_id. I know how to use mappedBy to check for friendships based on userA_id but I am not sure how to check for userB_id. Is there a way to tell hibernate to check a user ID from user table with both of columns on friendship table?
EDIT: Here is the code I currently have.
#Entity
#Table(name = "users")
public class UserDB implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "userid", nullable=false)
public int userID; //not null
#OneToMany (targetEntity = FriendshipDB.class, mappedBy = "userA_ID", cascade = CascadeType.ALL, fetch=FetchType.EAGER)
//#OneToMany (targetEntity = FriendshipDB.class, mappedBy = "userB_ID", cascade = CascadeType.ALL, fetch=FetchType.EAGER)
public List<FriendshipDB> friends = new ArrayList<>();
}
#Entity
#Table(name = "friendships")
public class FriendshipDB implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "friendshipid", nullable = false)
private int friendshipID; //not null
#ManyToOne
#JoinColumn(name="usera_id")
private UserDB userA_ID; //not null
#ManyToOne
#JoinColumn(name = "userB_id")
private UserDB userB_ID;
}
I think this is very specific mapping but the only solution I know is to go with 2 association like this:
#OneToMany(mappedBy = "user1")
private Collection<User> usersByFirst;
#OneToMany(mappedBy = "user2")
private Collection<User> usersBySecond;

Jpa: delete dependents instead of updating them

In my application every customer can have several accounts. I have the following data structure (a lot omitted for brevity):
#Entity
#Table(name = "CUSTOMER")
public class Customer {
#Id
#Column(length = 36, name = "CUSTOMER_ID", nullable = false, unique = true)
private String id;
#OneToMany
#JoinColumn(name = "OWNER_ID", referencedColumnName = "CUSTOMER_ID")
private List<Account> accounts;
}
#Entity
#Table(name = "ACCOUNT")
public class Account {
#Id
#Column(length = 36, name = "ACCOUNT_ID", nullable = false, unique = true)
private String id;
#Column(name = "OWNER_ID", nullable = false)
private String ownerId;
}
If I use JPA to delete a Customer, such as
entityManager.remove(customer);
it tries to update the related ACCOUNT.OWNER_ID fields with null. OWNER_ID is not nullable, so it throws a JDBCException and rolls back the transaction.
What I need to achieve is that the related ACCOUNT rows get deleted (if any). How can I do that?
Thank you
Update: I tried it with
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
but it does not change the behavior: still tries to update with null.
I think you need to be using cascading in order to remove the child elements. Try this:
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "OWNER_ID", referencedColumnName = "CUSTOMER_ID")
private List<Account> accounts;
You should also reference the Customer in your account by a ManyToOne relationship and not the String id. I think this should solve your issue:
#Column(name = "OWNER_ID", nullable = false)
#ManyToOne
private Customer owner;

Categories