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.
Related
I have a parent table called USER and a child table called USERDATA linked with OneToMany. When I go to use the save method, if the child record exists it is updated. I would like it not to be updated but not added. What am I doing wrong?
My classes:
#Entity
#Table(name="USER")
#IdClass(UserPK.class)
public class User implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name="USERID")
private String userId;
#Id
#Column(name="USERNUMBER")
private String userNumber;
private String name;
private String surname;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<UserData> userDatas;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Fetch(value = FetchMode.SUBSELECT)
private List<OtherData> otherDatas;
//getter and setter
}
UserData:
#Entity
#Table(name="USERDATA")
public class UserData implements Serializable{
private static final long serialVersionUID = 1L;
#Id
private String id;
private String City;
private String University;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "USERID", referencedColumnName = "USERID"),
#JoinColumn(name = "USERNUMBER", referencedColumnName = "USERNUMBER")
})
private User user;
//getter and setter
OtherData:
#Entity
#Table(name="OTHERDATA")
public class OtherData implements Serializable{
private static final long serialVersionUID = 1L;
#Id
private String id;
private String hobby;
private String religion;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "USERID", referencedColumnName = "USERID"),
#JoinColumn(name = "USERNUMBER", referencedColumnName = "USERNUMBER")
})
private User user;
//getter and setter
And my save:
session.save(obj); //obj is and User object
when I do the first insert everything is ok, when I do it again by changing the data of the primary key of user but not quelly of the primary key of userData or otherData, the data is updated. I don't want them updated.
You can not reuse the same Java object in this case. Also, changing the primary key should produce a big fat warning when flushing that object Hibernate.
If you want a new row to be inserted, you have to create a new object with new User(). Another option is to let Hibernate forget about the old object by using session.detach(user). The problem is, that Hibernate knows the object already and due to that, tries to update the existing row when you call save again.
Try setting the column like so:
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Column(insertable = false, updatable = false)
private List<UserData> userDatas;
I haven't tested it but it should block inserts and updates via the entity. Of course you will need separate queries to insert update UserData somehow.
BTW I suggest not to use FetchType.EAGER, but always use FetchType.LAZY. If you need the child data to be fetched, just adapt the query you are using to do a FETCH JOIN, see here for an example:
SELECT FROM User u LEFT JOIN FETCH u.userData d
Hi Fesilox Please write this example
#JoinColumn(name = "USERID", referencedColumnName = "USERID", nullable = false,
insertable=false, updatable=false)
I have a user class and role class and user role class . Now every time i am trying to add a new user with a set of role which is already existing it throws a Unique error which is correct . But ideally it should not try to save the new role if it already exists . Below i am adding all my tables and save method .
#Table(name = "t_user")
#Data
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "mobile_number")
private String mobileNumber;
#Column(name = "email")
private String email;
#Column(name = "first_name")
private String firstName;
#Size(max = 100)
#NotBlank(message = "Last name can not be empty")
#Column(name = "last_name")
private String lastName;
#Column(name = "is_archived")
private Boolean isArchived = false;
#Column(name = "qualification")
private String qualification;
#JsonIgnore
#Column(name="password")
private String password;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "t_user_role", joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "id")})
private Set<Role> roles = new HashSet<>();
}
#Data
#Table(name = "m_role")
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name="name")
private String name;
}
#Data
#Table(name = "t_user_role")
#Entity
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
}
method where i am saving the user:
User newUser = new User();
newUser.setFirstName(user.getFirstName());
newUser.setLastName(user.getLastName());
newUser.setEmail(user.getEmail());
newUser.setMobileNumber(user.getPassword());
newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
newUser.setRoles(user.getRoles());
return userRepository.save(newUser);
}
and below is the post request format to create the user:
{
"firstName":"first",
"lastName":"name",
"email":"email#gmail.com",
"mobileNumber":"1110122223",
"password":"1234567890",
"roles":[{
"name":"ADMIN"
}]
}
I do not want to insert the role if present which should be ideal . But this is the standard way i find while implementing spring security with roles
Your request is missing id of the role. As id is not present. Spring try to add a new role in role table.
{
"firstName":"first",
"lastName":"name",
"email":"email#gmail.com",
"mobileNumber":"1110122223",
"password":"1234567890",
"roles":[{
"id" : "" // ID must be present here.
"name":"ADMIN"
}]
}
Or from the role -> name, you can fetch Role entity/obj from the role table and set it in User object.
[Update 2]:
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#ToString.Exclude
private Set<Role> roles = new HashSet<>();
You need to change the cascade type from 'ALL' to 'DETACH'. See: ALL means if you save USER, ROLE will also get saved, if you delete USER, role should also get delete. This is not what we want. You only need to use 'ROLE', not manipulate the 'ROLE' tables record in any way.
On behalf of what I understand, your requirements are:
User entity
Role entity, with each user having multiple roles
If role is passed from client, you want to save the role only if it does not exist in your database, else you want to use the existing role (UPDATE: which as per comments and my opinion, is never an ideal thing to do)
In your case, I would suggest let Spring take care of the User->Roles relationship as follows:
public class User {
... all fields
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#ToString.Exclude
private Set<Role> roles = new HashSet<>();
}
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
In Role repository, you would want a method: Optional<Role> findByName(String Name);
In between the layers (preferably in the service layer), try this:
public Map<String, Object> addUser(User user) {
// perform validations
user.getRoles().forEach(role -> {
Role existing = roleRepository.findByName(role.getName())
.orElse(new Role(role.getName())); // using optional to create new role with name passed in from the client
if (existing.getId() != null) user.setRole(existing); // UPDATE: NOT IDEAL
});
... other tasks
userRepository.save(user); // this all saves the correct role
return yourResponseMap;
}
Other notes:
We generally prefer to keep fetches Lazy, instead of Eager. But there are cases when you may need Eager retrieval so it depends on you.
Letting Spring Data JPA handle third tables is better in terms of convenience in my opinion.
org.hibernate.PersistentObjectException: detached entity passed to persist occurs when you're trying to save the role passed in from client directly without loading it on your application (see the service layer method for 'add user').
Check this link, you might find it helpful.
I have this problem and I'd appreciate any help...
I have a User entity. A user has a Role (a many to one relationship). And a role has a list of permissions (the many to many relationshp I'm having trouble with).
In my code, I am selecting a single user out of the database, by username.
When I retrieve the user, the user's role is there inside of it. But inside the role, there should be a list of permissions. There should be 4 permissions, but every time, I am only getting one element back in the set.
I have queried the database correctly, and there are indeed 4 permissions attached to that role, so the problem isnt there.
UserEntity:
#Table(name = "users")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty
private String username;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "role_id")
private RoleEntity role;
}
RoleEntity:
#Table(name = "roles")
public class RoleEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty
private String name;
private String description;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "roles_permissions", joinColumns = #JoinColumn(name = "permission_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<PermissionEntity> permissions = new HashSet<>();
}
And here is my repository. I'm calling this method from a service, and am just getting back what I described above.
#Repository
public interface UserRepository extends CrudRepository<UserEntity, String> {
Optional<UserEntity> findByUsername(String username);
}
In my database, I have a table called 'roles_permissions' with the fields role_id and permission_id. There are four records in here. role_id is '1' for all of them, and its linked to permissions 1,2,3,4.
In the users table, there is a field for role_id. And the user that I'm selecting has this field populated with 1.
Okay, nevermind. I've been working on this for a while, and I figured it out as soon as I posted this!
In the #JoinTable annotation, I had "role_id" and "permission_id" the wrong way around!
I am trying to create a new User(entity1) - it has reference to a Group (entity2) via a link table Member (entity3)
A user has a Set of groups as a class variable.
When i create my user object i want to say this user will be a member of group n (there are pre defined users that are linked to by id (1,2,3,4,5,6...) each group has some associated data in the table.
Whenever I create my user object as follows;
User user = new User();
user.setActive(1);
user.setCrby("me");
user.setUsername("username");
user.setCrdate("2016-06-20 12:42:53.610");
user.setCrwsref("...");
user.setModby("...");
user.setModdate("2016-06-20 12:42:53.610");
user.setModswref("..");
user.setBackground("Y");
user.setPassword("password");
user.setFullName("me");
Group group = new Group();
group.setId(1);
Group group2 = new Group();
group2.setId(2);
Set<Group> sets = new HashSet<Group>();
sets.add(group);
sets.add(group2);
user.setGroups(sets);
userDao.addUser(user);
I keep getting errors telling me that certain columns cannot be null. What I actually want to happen here is not to be doing an insert in to the group table but associating a user to a line in the group table. Is there a particular way I can prevent the columns in the group table being modified? I think I need to modify the mappings between the link table - this is how much pojos link right now
User
#Entity
#Table(name = "user")
public class User
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "username")}, inverseJoinColumns = {#JoinColumn(name = "id")})
private Set<Group> groups = new HashSet<Group>(0);
Member link table
#Entity
#Table(name = "member")
public class Member implements Serializable
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Id
#Column(name = "sgpid")
private int sgpid;
#Column(name = "username")
private String memberUsername;
Group
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
What is happening is there is no association to the link Member table so ideally should User have a set of member objects rather than a set of groups?
Thanks - this was quite hard to explain so sorry if it is hard to understand
This is a typical case for the #ManyToMany annotation. See for example:
https://dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
The relationship from User to Group is essentially ManyToMany. You could model this is using the #ManyToMany annotation however one drawback with this approach is you cannot save additional information about the group in the join table such as 'date_joined'.
See: https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#ManyToMany
Using this approach you would not need the Join entity Member and the relationship on User would look like:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "member_id", referencedColumnName = "id")}, inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id")})
private Set<Group> groups = new HashSet<Group>(0);
The alternative to using #ManyToMany is to use a Join entity Member(ship) as you have done. This would allow you to save additional data about the relationship (by defining additional field mappings in the Join entity).
In this case the mappings would look like:
User:
public class User{
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
//if required, you can 'hide' the join entity from client code by
//encapsulating add remove operations etc.
public void addToGroup(Group group){
Membership membershup = new Membership();
membership.setUser(this);
membership.setGroup(group);
memberships.add(membership);
)
public Set<Groupp> getGroups(){
//iterate memberships and build collection of groups
}
}
Membership:
public class Membership{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private Member member;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
}
Group:
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
}
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);
}