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.
Related
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;
}
There is already a solution - how to store fk instead the whole entity on many-to-one unidirectional:
#JoinColumn(name = "message_key")
#ManyToOne(targetEntity = Messages.class, fetch = FetchType.LAZY)
private Messages message;
#Column(name = "message_key", insertable = false, updatable = false)
private Long message_fk;
Two questions:
How to do the same for unidirectional many-to-many? Means I have a List<Message> messages and want to keep only List<Long> message_fks.
Will it ensure consistency if I will never use message object? I want to use only message_fk field.
I have problem and i could not find solution. I have bi-directional many to many anotation. I have this tables in DB (mariaDB):
item
section
item_section
Item part:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "item_section",
joinColumns = {
#JoinColumn(name = "item", nullable = false, updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "section", nullable = false, updatable = false)})
private Set<Section> sections;
Section part:
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "sections")
private Set<Item> items;
PROBLEM:
I create some Sections first. Than i want to create Item "within" this section. So i create instance of Item and add some existing Sections into set (Sections i get from DAO). If i persist the Item, records in item and item_section tables are created. But if i get some of affected section from DAO and iterate thru items set database changes are not here, Sections instances are in same state as before insert.
What did i wrong?
PS: i use eclipselink but i do not think it is interesting
SOLUTION
As #Chris said i called persist utimately but one side of direction already exist. No cascades was defined so during persist sections was not persisted nor merge. So best solution for my staless and JTA solution was use merge instead of persist on item and add MERGE cascade to sections collection...
Thanks everybody who came to help but especially to #Chris
It seems you are missing the attribute cascade=CascadeType.ALL. You should try to change
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "item_section",
joinColumns = {
#JoinColumn(name = "item", nullable = false, updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "section", nullable = false, updatable = false)})
private Set<Section> sections;
to
#ManyToMany(cascade=CascadeType.ALL,fetch = FetchType.LAZY)
#JoinTable(name = "item_section",
joinColumns = {
#JoinColumn(name = "item", nullable = false, updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "section", nullable = false, updatable = false)})
private Set<Section> sections;
then it works normally.
I found this mistake via the tutorial Hibernate Many-To-Many Bidirectional Mapping Example
Hope this help
Problem may be in incorrect using of eclipselink's shared cache which is enabled by default. Try to disable it by adding in persistence.xml following:
<shared-cache-mode>NONE</shared-cache-mode>
When you persist or merge Section provider flushes it to database and add Section instance to shared cache so it can be retrieved from memory on next requests. When you persist Item you update only one side of the relationship, but the other side does not get updated, and becomes out of sync. In JPA, as in Java in general, it is the responsibility of the application or the object model to maintain relationships. If your application adds to one side of a relationship, then it must add to the other side. Read more about caching in bi-direction mapping in this article.
I can offer following solutions of your problem:
Disable cache for whole application in persistence.xml
Manually refresh Section in DAO before retrieving it to user
Use cache correctly by updating both sides of relationship:
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.
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.