Prevent Concurrent Modification. Eclipselink (OptimisticLockException) - jpa

Problem:
Two Admins -> Admin1 and Admin2 Simultaneously modifying UserA from different terminals.
There is a many-to-many relation between User and UserGroup entity.
Here what is happening is when Admin1 assigns Group1 to UserA, the information is saved.
And when Admin2 from different terminal assigns Group2 to UserA, the information saved but overwrites the changes made by Admin1. Means when I check in DB, UserA is assigned to Group2 while Admin2 is not aware of changes made by Admin1.
Is this Optimistic Situation?
If this is optimistic situation then why this is not throwing OptimisticLockException?
How would I notify Admin2 about the changes made by Admin1.
Using Eclipselink as below:
#ManyToMany
#JoinTable(name = "user_group", joinColumns = { #JoinColumn(name = "user_group_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "user_id", referencedColumnName = "id") })
private List<User> userList;
#ManyToMany
#JoinTable(name = "user_group",joinColumns = { #JoinColumn(name = "user_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "user_group_id", referencedColumnName = "id") })
private List<UserGroup> userGroupList;
#Version annotation is marked over version column which is updated on each operation.
Let know if you require more information from my side.

When group1 is assigned to user1, save last modified timestamp. Next time when the request comes to get the user1 details make sure you provide last modified date along with other details. When admin1 modifies the request ask him to provide the same last modified date which you supplied during get operation. Compare the last modified date s same as what you provided to admin1 before saving the requestrequest. Basically here last modified date is kind of token. You store a token, provide the same to user accessing the data and then when user modifies the data and sends it for persist operation make sure token in the database matches the one user has sent back to you else it means that the record was modifed and the token is changed in databse and dont allow the user to persist bad data.
Hope this helps.

Related

JHipster 4.14.4 : Registering a user with additional information

Following the steps described here I get an org.hibernate.cfg.RecoverableException: Unable to find column with logical name: id in org.hibernate.mapping.Table(user_extra) and its related supertables and secondary tables. It doesn't matter, if I change the entries in the liquibase changelogs regarding "user_extra.user_id". Even if I erase the liquibase changelogs (without the initial_schema) and/or remove the h2-database ( with ./gradlew clean) - every time I get the error.
The link mentioned above is close to the tipp on the jhipster.tech documentation, so I think I am missing something, but I am searching since hours for my failure(s)...
How does jhipster know to map the jhi_user.id to user_extra.user_id (OneToOne) if there is no explicit declaration for this in UserExtra.json? Is the declaration in the domain object UserExtra.java with
#OneToOne
#MapsId
private User user;
enough to trigger it?
Can somebody give me a hint, where Spring Data JPA maps the user_id from the user_extra-table to the #Id Long id of the domain object UserExtra?
My code generated with JHipster 4.14.4 is on github.
Please clarify my questions above, if you like!
Nevertheless I have found my mistakes. Unfortunately, there are no additional ManyToMany-relationships to UserExtra mentioned in the jhipster.tech-tipp - I had to change the user_extra.id to user_extra.user_id not only in the liquibase-changelogs, but also in the domain classes, which are linked ManyToMany to Spring MVC domain class UserExtra!
I faced the same issue, and Based on Jochen Gebsattel's answer, this is what I did:
Update the 202xxxxx_added_entity_constraints_UserExtra.xml
where I had
referencedColumnNames="id"
referencedTableName="user_extra"
I replaced by
referencedColumnNames="user_id"
referencedTableName="user_extra"
user_id being the name of the column containing the foreign key, in my user_extra entity/table
Update the UserExtra class (here again, replace id by user_id)
Where I had many to many relations with other entities, for example
#JoinTable(name = "user_extra_store",
joinColumns = #JoinColumn(name = "user_extra_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "store_id", referencedColumnName = "id"))
I replaced with
#JoinTable(name = "user_extra_store",
joinColumns = #JoinColumn(name = "user_extra_id", referencedColumnName = "user_id"),
inverseJoinColumns = #JoinColumn(name = "store_id", referencedColumnName = "id"))
Note: If you created a new liquibase changelog file in step 1 (instead of update), don't forget to reference it in the master.xml

JPA recursive entity StackOverflowError

I have a User entity generated in Netbeans from an existing database table. The table has a column lastUpdatedByUser that is a User entity. Most of the tables in this database have a lastUpdatedByUser column and queries against those entities correctly return a user object as part of the result.
Ex. Retrieve FROM ProductionTable WHERE date = 'someDate' has a lastUpdatedByUser object that shows who last updated the table row and the rest of their user attributes.
If the productionTable data is edited in the web-app and submitted I need to update the lastUpdatedByUser column.
Users userUpdating = usersService.selectUserEntityByUserId(userId);
Users userEntity = usersFacade.findSingleWithNamedQuery("Users.findByUserId", parameters);
SELECT u FROM Users u WHERE u.userId = :userId
returns a User object that contains a lastUpdatedByUser that is a User object that contains a lastUpdatedByUser that is a User object that contains a lastUpdatedByUser object.... (I have no clue how many there are, and twenty rows of these adds up)
After I persist this
productionEntity.setLastUpdatedByUser(userUpdating);
I get Json StackOverflowError in the next request for the updated entity
gson.toJson(updatedProductionEntity)
The Users entity definition:
#OneToMany(mappedBy = "lastUpdatedByUser")
private Collection<Users> usersCollection;
#JoinColumn(name = "LastUpdatedByUser", referencedColumnName = "UserId")
#ManyToOne
private Users lastUpdatedByUser;
#OneToMany(mappedBy = "lastUpdatedByUser")
private Collection<Production> productionCollection;
How can edit that such that I continue to get a user object as part of other entities like Production, but only a single lastUpdatedByUser object for a User entity?
Thanks for any insight.
I'm guessing this is my issue:
#JoinColumn(name = "LastUpdatedByUser", referencedColumnName = "UserId")
as I found a FK in the Users table to its own UserId
Love refactoring
================================
Drop that FK from the Users table and regenerate the entity in Netbeans and I get
private Integer lastUpdatedByUser;
like it should be
instead of
private Users lastUpdatedByUser;
Now I get to edit all the entities that have valid FKs into the Users table and code and...
Thanks for listening.

Bidirectional Many To Many : how to add a user to a group?

hi I have a #ManyToMany relationship (Users and Groups)
in the Group entity I have :
#ManyToMany(fetch=FetchType.EAGER , cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "group_user",
joinColumns = #JoinColumn(name = "gid"),
inverseJoinColumns = #JoinColumn(name = "uid"))
#JsonIgnore
private List<User> users;
in the User entity I have :
#ManyToMany(mappedBy = "users",fetch=FetchType.LAZY , cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JsonIgnore
private List<Group> groups;
the table "group_user" has been created . I can get the list of users in a group and the list of groups to which a user belongs using these queries :
#Query("select g From Group g join g.users u where u.id=?1")
Iterable<Group> getAllGroupsofUser(long idUser);
#Query("select u From User u join u.groups g where g.id=?1")
Iterable<User> getAllUsersingroup(long idGroup);
I want to add a user to a group so I did this
User user=userRepository.findOne(1);
Group group=groupRepository.findOne(3);
group.getUsers().add(user);
groupRepository.save(group);
this works . it adds a new line in my table group_user.
Actually it insert all the new list of users ( it's a problem ) How to fix that ?
Also I need to do the same when I change fetch=FetchType.EAGER to fetch=FetchType.LAZY in my Group entity .
I'm still waiting for an answer ! I need a better solution for adding a user to a group without having to insert all the list again

How to successfully do delete/remove operation in ManyToMany relationship?

I'm using ManyToMany with JPA annotation, I need your valuable suggestions.
(Assume Person and Address. Same Address is referred to more person (living at same address)). I have to delete a person from that address.
Person p1 = new Person();
Person p2 = new Person();
Address add1 = new Address();
p1.add(add1);
p2.add(add1);
As well doing
add1.add(p1) ;
add1.add(p2) ;
THen on merge or persist iit mapped appropriately.
p1 - add1
p2 - add1
I have to delete p2 alone , when i did
p2.removeAddress(add1)
removeAddress(add1) {
addColelction.remove(add1) }
What happens is it deleted the entry for address and again by Hibernate jpa provider again tries to persist at Address entity and says "deleted entity passed to persist " and henc transaction roll back happens.
My correction on the question. The mapping exist as
In Script side :
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REFRESH})
#JoinTable(name = "XXXX", joinColumns = { #JoinColumn(name = "X1_ID", nullable = false, updatable = false) }, inverseJoinColumns = { #JoinColumn(name = "X1_ID", nullable = false, updatable = false) })
#org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Collection<Parser> parsers;
In Parser side
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "parsers")
private Collection<Script> scripts;
The data saved as
Script1 - Parser1
Script2 - Parser1
Our data model is Object A has oneTomany to B , B has oneTomany to Script objects.
Say collection of A has (B1,B2,.....)
B1 has (Script1)
B2 has (Script2)
When we want to delete that B2 object (we do just EM.merge(A)), we want the particular B2 from the collection has to be deleted and the related the Script2 has to be deleted. Script2 delete should remove the intermediate entry alone but should not delete the Parser.
But Parser1 gets deleted and Transaction gets rolled back saying ''deleted entity passed to persist
Please share your ideas.
You mention you only want the join table cleared up, but you have both DELETE_ORPHAN and cascade all set on the Script->Parser mapping. The first setting seems to be a hibernate option equivalent to JPA's orphan removal, which will cause any entities de-referenced from the collection to be deleted in the database. This will cause Address1 in the example to be deleted.
The cascade all option will force the remove operation to be cascaded to any entities still referenced by Person/Script when remove is called on Person/Script. In the first example this will cause Address2 to be removed/deleted from the database.
If you want address 1 or address2 to survive, then remove the appropriate setting. As mentioned in comments, you will also want to clean up your references, as the survivors will be left referencing a deleted Person/Script entity which may cause problems in your application.

Many To Many JPA Annotations more than two tables

Currently I have three tables.
User, Role and Institution.
As you know user and roles has many to many relation. (One user can have multiple roles, one role can have multiple users).
Create a many to many annotations as follows.
#In User Table.
#ManyToMany
#JoinTable(name = "USER_ROLES",
joinColumns = {#JoinColumn(name = "USER_ID")},
inverseJoinColumns = {#JoinColumn(name = "ROLE_ID")}
)
private Set<Role> roles = new HashSet<Role>();
#In Role Table.
#ManyToMany
#JoinTable(name = "USER_ROLES",
joinColumns = {#JoinColumn(name = "ROLE__ID")},
inverseJoinColumns = {#JoinColumn(name = "USER_ID")}
)
private Set<User> users = new HashSet<User>();
But later I have realized that a user can have a role in one institution and a different role in another Institution or the same in different institutions. How can I set that relation?
First of all, your simple many to many mapping is wrong. One side of the bidirectional association must be the inverse side, using the mappedBy attribute. For example:
#ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<User>();
Now to answer your question, you just need one more entity. Let's call it Participation. A participation contains a role of a given user in a given institution. So, you have the following locical associations:
user - OneToMany - participation
participation - ManyToOne - role
participation - ManyToOne - institution