When I am trying to delete Role from the roles table with the help of Hibernate using its id it occurs an exception:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`ewp`.`permissions`, CONSTRAINT `FK_sq51ihfrapwdr98uufenhcocg` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`))
Class User
...
#NotNull
#ManyToMany(fetch = FetchType.EAGER, targetEntity = Role.class)
#JoinTable(name = "permissions",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "role_id")})
private Set<Role> roles;
...
Class Role
...
#ManyToMany(fetch = FetchType.EAGER)
private List<User> users;
...
When I add to Role
#JoinTable(name = "permissions",
joinColumns = {#JoinColumn(name = "role_id")},
inverseJoinColumns = {#JoinColumn(name = "user_id")})
private List<User> users;
Roles deleted without exceptions.
But I think that it can work without mapping in Role.
You either need to determine which side of the Many-To-Many owns the relationship or make sure that you alter both sides prior to removing the associated Role objects.
As mentioned in the comments, you can imply ownership by adding a mappedBy attribute to either side of the relationship. By adding mappedBy="roles" to the Role entity, this will tell the persistence provider that the relationship is managed by the User object's roles property and thus to manipulate the relationship using its join-table mapping.
Related
I'm trying to link mysql tables (role, user, user_role).
UserModel entity contains a field 'roles'.
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_role", joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = { #JoinColumn(name = "role_id") })
private Set<Role> roles;
Role entity contains a field 'users'
#Transient
#ManyToMany(mappedBy = "roles")
private UserModel users;
When i save a user, new rows are automatically created in the table 'roles'. How to cancel duplicate roles?
You are cascading all operations from the UserModel over the roles attribute to entity Role. So whenever you persist a user, it persists its roles as well. Try removing the cascade = CascadeType.ALL or replace the cascading to something less "brute force".
DB tables (not quite SQL but you get the gist..)
definition (
id int, -- PK
type int,
label varchar(20) -- definition label can change over time
)
asset (
id int, -- PK
-- other asset fields...
)
property (
id int, -- PK
asset_id int, -- with FK to asset + on delete cascade
definition_id int, -- with FK to definition + on delete cascade
payload varchar(256)
)
A map of the definition id int to payload (Map<int,String>), like this below, works but that's not what I want.
[Asset class]
#ElementCollection
#CollectionTable(name = "properties",
joinColumns = {#JoinColumn(name = "asset_id", referencedColumnName = "id")})
#MapKeyColumn(name = "definition_id")
#Column(name = "payload")
Map<Integer,String> properties;
Instead, I'm trying to have the following map in my Asset class:
Map<Definition,String> properties;
but can't figure what to do. The #MapKeyJoinColumn annotation is intended to pull a relation out of the a entity-type map value so I cannot complete this below:
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "properties",
joinColumns = {#JoinColumn(name = "asset_id", referencedColumnName = "id")},
//inexistant - inverseJoinColumns = {#JoinColumn(name = "property_id", referencedColumnName = "id")})
#MapKeyJoinColumn(name = "definition_id")
Map<Definition,String> properties;
I want a value-type map value. My 'properties' table has the definition_id so I really just need jpa/hibernate to join a table for the entity key, not the value.
Any clue?
From a commenter, this works:
[Asset class]
#ElementCollection
#CollectionTable(name = "properties",
joinColumns = {#JoinColumn(name = "asset_id", referencedColumnName = "id")})
#MapKeyJoinColumn(name = "definition_id")
#Column(name = "payload")
Map<Definition,String> properties;
The non-intuitive answer was to force #MapKeyJoinColumn and the key Entity type, regardless of the #ElementCollection presence.
I have no idea if this is just a fluke with hibernate abilities, or if it is actually supported by JPA.
I am new with Spring jpa and I am trying to perform the deletion operation on spring jpa many to many database. My database has user and drug. I can delete an user and deletes also the rows associated to it from user_drug table, I can delete a drug that has no linking to the user_drug table, but i cannot delete a drug that is also in the user_drug table.
I had a look on this page but the solutions from there do not work for me.. How to remove entity with ManyToMany relationship in JPA (and corresponding join table rows)?
Here is my code for User entity:
#ManyToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name = "user_drug",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "drug_id", referencedColumnName = "id"))
private Set<Drug> drugs = new HashSet<>();
Here is the code for Drug entity:
#ManyToMany(mappedBy = "drugs", fetch=FetchType.EAGER)
private Set<User> users = new HashSet<>();
And here is the delet method from DrugServiceImpl:
public void delete(Drug drug)
{
drug.getUsers().clear();
drugRepository.delete(drug);
}
I have also printed the size of drug.getUsers() after clear operation and it is 0. Why isn't it delete the drug from database?
I've tried in many ways.. Can someone please send some help?
that is how the cascade works it deletes from the relation table only and in the ManyToMany the relation table here is the user_drug table
and for this cases there are many solution one of them is deleting the drug in the same transaction of the deleting user
but another solution that will save the effort that you create the ManyToMany table as entity and put cascade on the drug object in the relation table
like this
But this in the User entity
#ManyToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="user")
private Set<UserDrug> drugs = new HashSet<>();
the UserDrug entity
public class UserDrug {
#ManyToOne
#JoinColumn(name="user_id")
User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="drug_id")
Drug drug;
}
The entity Construction
has following map:
#Column(name = "paper_FK")
#ManyToMany
#JoinTable(inverseForeignKey = #ForeignKey(name = "construction_FK"), joinColumns = #JoinColumn(name = "construction_FK", referencedColumnName = "construction_FK"), name = "ConstructionPaperTracks", inverseJoinColumns = #JoinColumn(name = "paper_FK"))
private HashMap<Integer, Paper> tracks_field = new HashMap<Integer, Paper>();
Due to my database design guidelines the table ConstructionPaperTracks
should have the columns construction_FK, position and paper_FK.
JPA works with construction_id, position and paper_id.
How can i specify the column names?
best regards
Heiko
I am not sure I understand "JPA works with construction_id, position and paper_id."
Anyway, I believe the mapping will be as below:
#Entity
public class Construction{
#Id
#Column(name = "construction_id")
//specify a generation strategy
private Long id;
#ManyToMany
#JoinTable(name = "ConstructionPaperTracks",
joinColumns = #JoinColumn(name = "construction_FK"),
inverseJoinColumns = #JoinColumn(name = "paper_FK"))
#MapKeyColumn(name = "position")
private HashMap<Integer, Paper> paper;
}
You need to specify the #MapKeyColumn, the documentation for which states that:
If the map key is for a ManyToMany entity relationship or for a
OneToMany entity relationship using a join table, the map key column
is in a join table
For the joinColumn and inverseJoinColumn the referencedColumn names will default to the primary key column of the referenced tables (construction_id, paper_id) so you don't have to specify these.
I have 2 entities - User and Role which have following relations: User has a manytomany relation to itself and a manytomany relation with the Role entity.
#Entity
public class UserEntity implements Serializable {
#Id
#Column(length = 12, columnDefinition = "BINARY(12)", name = "Id", unique = true)
private byte[] id;
#Column(name = "Login", unique = true, nullable = false)
private String login;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "User_Role",
joinColumns = { #JoinColumn(name = "UserLogin", referencedColumnName = "Login") },
inverseJoinColumns = { #JoinColumn(name = "RoleId", referencedColumnName = "Id") })
private Set<RoleEntity> roles;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "User_User",
joinColumns = { #JoinColumn(name = "UserParent") },
inverseJoinColumns = { #JoinColumn(name = "UserChild") })
private Collection<UserEntity> children;
...
}
and Role:
public class RoleEntity implements Serializable{
#Id
#Column(name = "Id", unique = true, nullable = false)
private String id;
...
}
The strange thing about the setup of DB is that the User_User relation is based on the binary Id keys
create table if not exists User_User (
UserParent binary,
UserChild binary
);
and the user-role is based on varchars
create table if not exists KNUser_UserRole (
UserLogin varchar,
RoleId varchar,
);
Now, when it runs, the user-user relationship work well. However, when I try to access the collection returned for roles, I get a ClassCastException:
java.lang.ClassCastException: **.entity.UserEntity cannot be cast to [B
at org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor.extractHashCode(PrimitiveByteArrayTypeDescriptor.java:41)
at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:201)
at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:205)
at org.hibernate.engine.spi.EntityKey.generateHashCode(EntityKey.java:114)
at org.hibernate.engine.spi.EntityKey.<init>(EntityKey.java:79)
at org.hibernate.internal.AbstractSessionImpl.generateEntityKey(AbstractSessionImpl.java:240)
at org.hibernate.engine.internal.StatefulPersistenceContext.getCollectionOwner(StatefulPersistenceContext.java:740)
at org.hibernate.loader.Loader.readCollectionElement(Loader.java:1181)
at org.hibernate.loader.Loader.readCollectionElements(Loader.java:800)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:651)
at org.hibernate.loader.Loader.doQuery(Loader.java:856)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:289)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
at org.hibernate.loader.Loader.loadCollection(Loader.java:2175)
at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:61)
at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:622)
at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:82)
at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:1606)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:379)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:112)
at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:180)
It looks like the UserEntity is being cast to some binary(?) thing. However, the first relation between users themselves works fine, but the one with another table is wrong.
I am using different columns of different types to join tables. Is it allowed to do it this way?
Another strange thing is that when I switch the #Id annotation to be on the login field, the roles work fine, no issue, but then of course the self-join PersistentBag key is the Login instead of Id, which breaks the relation and no results are retrieved. But the conversion from UserEntity to the "[B" is not done.
Also if I leave things as in example and change the Id type to String (and the DB to varchar) it also starts working (of course not consistently with the User_User table).
What am I doing wrong? What is the reason for getting the classcastexception in this case? Why it work when I change the byte[] to String? Please let me know if you have any ideas. I do not want to change the DB design cause this would lead to lots migration and compatibility issues for clients already using the DB.
Just a note: the #Id has to be on the Id binary field as otherwise I wouldn't be able to make a self-join (I was unable to point twice to a column not being a primary key see: Is Hibernate ManyToMany self-join possible for non-key columns? getting mappingException).
Cheers
Adam
the referred column in your join table must be unique entry, here if you put #Id on login field then it works fine,but when you change it to different other than #Id column you cant be sure about the entries will be unique.what you can do is,
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "User_Role",
joinColumns = { #JoinColumn(name = "UserLogin", referencedColumnName = "Id") },
inverseJoinColumns = { #JoinColumn(name = "RoleId", referencedColumnName = "Id") })
private Set<RoleEntity> roles;
I think it should work.