I am trying to setup and Many to Many relationship between users and roles. I am mostly using JPA Repositories but I also tried using and EntityManger.
I have the following in my main User object.
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "svcAuthUserRolev2", schema="dbo", joinColumns = {
#JoinColumn(name = "user_id", updatable=false,insertable=false, nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
updatable=false,insertable=false, nullable = false) })
private Set<AuthRoleEntity> roles;
And the following in my Roles object
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "roles")
private Set<AuthUserEntity> users;
No matter what I do if I make changes to the roles on a user when saving they are persisted and this is not what I would like. I want roles on the user object to be read only.
I'm not sure why updateable and insertable are not working, I haven't used those attributes much. One possible solution is to make AuthRoleEntity the owning entity of the many-to-many relationship. Just move the #JoinTable annotation to the AuthRoleEntity and put the mappedBy on the AuthUserEntity.
Related
I have 2 entities, one for a taskList and the other for users. The users can create tasks for themselves or their friends, so in the Tasks entity I have different columns referencing the User entity, one for createdBy another for createdFor and a last one for modifiedBy.
In the tasks entity I made the following #ManyToOne JPA relationships:
#ManyToOne(optional = false)
#JoinColumn(name = "created_by", nullable = false,
referencedColumnName = "id")
private User createdBy;
#ManyToOne
#JoinColumn(name = "created_for", nullable = false,
referencedColumnName = "id")
private User createdFor;
#ManyToOne
#JoinColumn(name = "modified_by", nullable = false,
referencedColumnName = "id")
private User modifiedBy;
But I am not sure how to do the linking in the Users side, I had the following #OneToMany relationship:
#OneToMany(mappedBy = "createdFor", cascade = CascadeType.ALL)
private List<Task> taskList;
But I am not sure how to proceed with the other relationships as I don't really need a taskList for the tasks that a user created, or what tasks did he modify.
You do not need to map all things in an Entity , and also do not need to configure every relationships as bi-directional.
So if you really do not need to access the Tasks that are created and modified from a given User , just ignore them and do not map them in User.
It is perfectly valid to configure a relationship as unidirectional. Your existing codes should already work perfectly.
I have 3 entities with ManyToMany relationships:
Role Entity:
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer roleID;
private String roleName;
private String description;
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)
#JoinTable(name = "role_permission", joinColumns = {#JoinColumn(name = "role_id")}, inverseJoinColumns = {#JoinColumn(name = "permission_id")})
private Set<Permission> permissions = new LinkedHashSet<Permission>();
}
Permission Entity:
#Entity
public class Permission {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int permissionID;
private String permissionName;
private String description;
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)
#JoinTable(name = "permission_functionality", joinColumns = {#JoinColumn(name = "permission_id")}, inverseJoinColumns = {#JoinColumn(name = "functionality_id")})
private Set<Functionality> functionalities = new LinkedHashSet<>();
}
Functionality Entity:
#Entity
public class Functionality {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
I did the following:
I have created 3 functionalities:
Functionality1, Functionality2, Functionality3
Then created 2 permissions:
Permission1 with Functionality1, Functionality2
Permission2 with Functionality2, Functionality3
Then created a role:
Role1 with Permission1 and Permission2
I am getting the following exception:
java.lang.IllegalStateException: Multiple representations of the same entity [com.persistence.entity.admin.Functionality#1] are being merged. Detached: [com.persistence.entity.admin.Functionality#4729256a]; Detached: [com.persistence.entity.admin.Functionality#56ed25db]
Fixed it by removing CascadeType.MERGE on Permission entity
The correct solution would have been to upgrade to hibernate 4.2.15 / 4.3.6 or above and add the following lines to your persistence.xml:
<property name="hibernate.event.merge.entity_copy_observer" value="allow"/>
Check your equals and hashCode method, ensure that it is consistent and correctly defined. For example I had copied and mistakenly pasted another-class when computing hashCode, this caused the object never to be equal with itself :(.
Like others who based their answers on HHH-9106 but, because I'm using Spring Boot with Java-based annotation, I had to use the following in application.properties:
spring.jpa.properties.hibernate.event.merge.entity_copy_observer=allow
I ran into the same problem too and solved it by add a few configs in application.yaml files.
jpa:
properties:
hibernate:
enable_lazy_load_no_trans: true
event:
merge:
entity_copy_observer: allow
See it here How to persist a new entity containing multiple identical instances of another unpersisted entity with spring-boot and JPA?
I could fix it by replacing
cascade = CascadeType.All
with
casecade={CascadeType.PERSIST,CascadeType.REMOVE}
For Hibernate see the workaround here HHH-9106.
In my case, moving the fetch operation and save operation in same #Transactional block solved the problem.
error occurs when we have multiple object of same time in HashSet.Possible due to incorrect hash function.Hashset check equality of object on the basis of hash function between two objects.
Way to debug
Just try to print hashset you will see multiple object of same type.
Solution::#
Use HashSet while defining one to many relationships.
Avoid using Lists.
Make sure your hash function should be correct.
I fixed this issue by removing cascade = CascadeType.ALL in your case (cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
My code source:
Before
#ManyToMany(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "link_module_parcour",
joinColumns = {#JoinColumn(name = "module_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "parcour_id", referencedColumnName = "id")})
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#BatchSize(size = 20)
private Set<Parcour> parcours = new HashSet<>();
After
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(
name = "link_module_parcour",
joinColumns = {#JoinColumn(name = "module_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "parcour_id", referencedColumnName = "id")})
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#BatchSize(size = 20)
private Set<Parcour> parcours = new HashSet<>();
**#LazyCollection(LazyCollectionOption.FALSE**
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "translate_id")
If you remove the cascade merge you won't be able to update the entity when updating the parent.
The case will be right if you don't want to update the child, but if you want to update the child when updating the parent removing merge will only not show the error but the issue will still exist.
What happened with me: I got the same exception but I need Cascade.merge. After searching I found that some of the values I was using when creating the entity for the first time are updated and removed from the code but in the database, they still exist and when removing them from the database also work as expected.
To clarify the case:
Let's say I have an enum('ACTIVE', 'NOT_ACTIVE', 'SUSPENDED') and the field using this enum is key, when updating the code and removing the NOT_ACTIVE we should alter the database table with the new values enum('ACTIVE', 'SUSPENDED')
I had similar issue and it is resolved when I replace CascadeType.All with {CascadeType.persist, CascadeType.remove}.
Let me know if it works.
We can also resolve that using CascadeType.DETACH . In this case, if we need to update something in Permission, we have to update Permession separately and not by updating the related entity!
Just a note to say I am using Hibernate Core 4.3.8 in a Spring MVC application, based on Spring Core 4.1.6. The workaround:
<property name="hibernate.event.merge.entity_copy_observer" value="allow"/>
Did not work for me. I needed to remove the CascadeType.MERGE in order to correctly populate an #ManyToMany. Not sure if newer versions of Hibernate have fixed this.
I have 3 entities with ManyToMany relationships:
Role Entity:
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer roleID;
private String roleName;
private String description;
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)
#JoinTable(name = "role_permission", joinColumns = {#JoinColumn(name = "role_id")}, inverseJoinColumns = {#JoinColumn(name = "permission_id")})
private Set<Permission> permissions = new LinkedHashSet<Permission>();
}
Permission Entity:
#Entity
public class Permission {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int permissionID;
private String permissionName;
private String description;
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)
#JoinTable(name = "permission_functionality", joinColumns = {#JoinColumn(name = "permission_id")}, inverseJoinColumns = {#JoinColumn(name = "functionality_id")})
private Set<Functionality> functionalities = new LinkedHashSet<>();
}
Functionality Entity:
#Entity
public class Functionality {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
I did the following:
I have created 3 functionalities:
Functionality1, Functionality2, Functionality3
Then created 2 permissions:
Permission1 with Functionality1, Functionality2
Permission2 with Functionality2, Functionality3
Then created a role:
Role1 with Permission1 and Permission2
I am getting the following exception:
java.lang.IllegalStateException: Multiple representations of the same entity [com.persistence.entity.admin.Functionality#1] are being merged. Detached: [com.persistence.entity.admin.Functionality#4729256a]; Detached: [com.persistence.entity.admin.Functionality#56ed25db]
Fixed it by removing CascadeType.MERGE on Permission entity
The correct solution would have been to upgrade to hibernate 4.2.15 / 4.3.6 or above and add the following lines to your persistence.xml:
<property name="hibernate.event.merge.entity_copy_observer" value="allow"/>
Check your equals and hashCode method, ensure that it is consistent and correctly defined. For example I had copied and mistakenly pasted another-class when computing hashCode, this caused the object never to be equal with itself :(.
Like others who based their answers on HHH-9106 but, because I'm using Spring Boot with Java-based annotation, I had to use the following in application.properties:
spring.jpa.properties.hibernate.event.merge.entity_copy_observer=allow
I ran into the same problem too and solved it by add a few configs in application.yaml files.
jpa:
properties:
hibernate:
enable_lazy_load_no_trans: true
event:
merge:
entity_copy_observer: allow
See it here How to persist a new entity containing multiple identical instances of another unpersisted entity with spring-boot and JPA?
I could fix it by replacing
cascade = CascadeType.All
with
casecade={CascadeType.PERSIST,CascadeType.REMOVE}
For Hibernate see the workaround here HHH-9106.
In my case, moving the fetch operation and save operation in same #Transactional block solved the problem.
error occurs when we have multiple object of same time in HashSet.Possible due to incorrect hash function.Hashset check equality of object on the basis of hash function between two objects.
Way to debug
Just try to print hashset you will see multiple object of same type.
Solution::#
Use HashSet while defining one to many relationships.
Avoid using Lists.
Make sure your hash function should be correct.
I fixed this issue by removing cascade = CascadeType.ALL in your case (cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
My code source:
Before
#ManyToMany(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "link_module_parcour",
joinColumns = {#JoinColumn(name = "module_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "parcour_id", referencedColumnName = "id")})
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#BatchSize(size = 20)
private Set<Parcour> parcours = new HashSet<>();
After
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(
name = "link_module_parcour",
joinColumns = {#JoinColumn(name = "module_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "parcour_id", referencedColumnName = "id")})
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#BatchSize(size = 20)
private Set<Parcour> parcours = new HashSet<>();
**#LazyCollection(LazyCollectionOption.FALSE**
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "translate_id")
If you remove the cascade merge you won't be able to update the entity when updating the parent.
The case will be right if you don't want to update the child, but if you want to update the child when updating the parent removing merge will only not show the error but the issue will still exist.
What happened with me: I got the same exception but I need Cascade.merge. After searching I found that some of the values I was using when creating the entity for the first time are updated and removed from the code but in the database, they still exist and when removing them from the database also work as expected.
To clarify the case:
Let's say I have an enum('ACTIVE', 'NOT_ACTIVE', 'SUSPENDED') and the field using this enum is key, when updating the code and removing the NOT_ACTIVE we should alter the database table with the new values enum('ACTIVE', 'SUSPENDED')
I had similar issue and it is resolved when I replace CascadeType.All with {CascadeType.persist, CascadeType.remove}.
Let me know if it works.
We can also resolve that using CascadeType.DETACH . In this case, if we need to update something in Permission, we have to update Permession separately and not by updating the related entity!
Just a note to say I am using Hibernate Core 4.3.8 in a Spring MVC application, based on Spring Core 4.1.6. The workaround:
<property name="hibernate.event.merge.entity_copy_observer" value="allow"/>
Did not work for me. I needed to remove the CascadeType.MERGE in order to correctly populate an #ManyToMany. Not sure if newer versions of Hibernate have fixed this.
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.
In my DB I have three table USER, PERMISSION and a junction table USER_PERMISSIONS. As you can see I have clear many-to-many relationship.
And here is the question. I'm using a Hibernate. Is it possible to use only two classes, User and Permissions without creating a junction class UserPermissions? I mean can I use some kind of annotations to include my junction table USER_PERMISSIONS?
Yes you can. You need to use the #JoinTable and #JoinColumn annotation. Example below:
#Entity
#Table(name = "USER")
public class User {
#Id
#Column(name = "ID")
private Long id;
#ManyToMany
#JoinTable(name = "USER_PERMISSIONS",
joinColumns = #JoinColumn(name = "USER_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "PERM_ID", referencedColumnName = "ID"))
private Collection<Permissions> permissions;
A full working example with bidirectionaly many to many can be found in here http://en.wikibooks.org/wiki/Java_Persistence/ManyToMany