hibernate envers audit even if there are no changes - spring

We are using hibernate envers to audit changes in entities.
We have following two entities
UserRole
public class UserRole {
private String id;
#Audited
#Column(name = "name")
private String name;
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToMany(mappedBy = "userRoles", cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#Audited
private Set<UserGroup> userGroups;
}
UserGroup
public class UserGroup {
private String id;
#Audited
#Column(name = "name")
private String name;
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(
name = "role_user_group_mapping",
joinColumns = {#JoinColumn(name = "user_group_id")},
inverseJoinColumns = {#JoinColumn(name = "user_role_id")}
)
#Audited
private Set<UserRole> userRoles;
}
Following is the scenario:
if we try to assign role to any of the existing user group, envers will record all other roles which are already assigned to the usergroup as modified, even if there were no changes in those roles.
eg.
suppose we have user group "Admin User Group"
and it has "Role1" and "Role2" already assigned to it.
Now if I try to assign new role "Role3" to this group, I will get extra audits for the existing roles "Role1" and "Role2" as modified but there were no changes in these roles.
Audit should be only for the user group "Admin User Group" since new role was assigned to it and for the "Role3" since new user group was assigned to this role.
How to avoid already existing roles ("Role1", "Role2") getting audited and only audit actually modified entities?

Related

ManyToMany relation use in service

Job entity
#Column(name = "name")
private String name;
#ManyToMany
#JoinTable(name = "user_job",
joinColumns = #JoinColumn(name = "job_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private List<User> user;
User entity
#Column(name = "email")
private String email;
#ManyToMany
#JoinTable(name = "user_job",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private Set<Role> roles;
Role entity
#Column(name = "name")
private String name;
#ManyToMany(mappedBy = "roles")
private Set<User> users;
Here we have a table user_job with 3 ids and I want to insert data in service layer. How I can do it and what repository I should implement or use existent like user/role/job?
class UserJobService{
public void setUserJob(User user, Job job, Role role){
}
}
The problem with #ManyToMany association is you can't delete a record directly from user_job table, using Hibernate. To delete the record, you need to load a user with all his jobs. So better to add UserJobEntity
#Entity
#Table(name = "USER_JOBS")
class UserJobEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "JOB_ID")
private Job job;
}
public UserJobEntity createUserJob(UserEntity user, JobEntity job) {
UserJobEntity userJob = new UserJobEntity();
userJob.setUser(user);
userJob.setJob(job);
return userJobRepository.save(userJob);
}
Probably you will want to add (user, job) unique constraint to user_jobs table.
Some advices
Use plurals for table names. user_jobs in place of user_job
Role is tabular data. So it shouldn't have a users List.
Don't use Set for associated collections. Definitely you will encounter "multiple bugs fetch exception" and this exception will help you to change queries. With Set you can have large cross products and even don't notice them.

Hibernate mapping user relation to entities

Let's se we have Hibernate entity User with basic fields such as username, password, roles etc..
Now we have an entity such as Car.
User has a OneToOne relationship with Car, cause he can own a car. But he also has besides this a OneToMany relationship to Car, because he also owns the cars of his children. But in the frontend I want to know which cars he owns for himself and which cars he owns for his children. The same applies to the relationship between User and motorbike (his own, his childrens, etc...)
How would the User entity class look like? Is it good to have the relationships mapped in an "Helper" entity such as UserData:
#Entity
#Data
#Table( name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username")
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 150)
private String username;
#NotBlank
#Size(max = 120)
private String password;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_DATA_ID")
private UserData userData;
UserData:
#Entity
#Data
#Table( name = "user_data")
public class UserData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "OWN_CAR_ID")
private Car ownCar;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "PARTNER_CAR_ID")
private Car partnerCar;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_children_cars",
joinColumns = #JoinColumn(name = "user_data_id"),
inverseJoinColumns = #JoinColumn(name = "car_id"))
private Set<Car> childrenCars = new HashSet<>();
public boolean addToChildrenCarSet(Car c) {
return childrenCars.add(c);
}
public UserData() {
}
}
As you ask for an opinion, I would say it gets unnecessary complicated if you use the intermediate entity user_data. :-) There is no real drawback to add more fields and keys into the user class - performance is probably also better then using the EAGER fetching. If performance is an issue, better optimize querys later on then splitting the table now.
Also the #ManyToMany I would avoid - better create the intermediate table and relations yourself. You can check out https://bootify.io and create your database schema there. There is no EAGER fetching and also no CascadeType.ALL (both only good ideas in special cases), you would probably add more problems with that then actual helping in any way.
So the addToChildrenCarSet method would end up in a #Service class, in a method with #Transactional, in my proposal.

How to store userid, roleid and privilegeid in single table using #JoinTable?

Requirement : Create table using #JoinColumn annotation in User entity class, which contain columns like, user_id, authority_id(means role), privilege_id.
Condition: Authority and Privileges also have one to many relationship.
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "pm_authority_privilege",
joinColumns = #JoinColumn(name = "authority_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "privilege_id", referencedColumnName = "id"))
private Set<Privilege> privileges = new HashSet<>();
As per above configuration i got privilege as part of authority json.
Issue : I'm confused what should be variable type if i take two value in 'inverseJoinColumns'.
When i try to do same thing using following way it gives error of transactional object because privilege is exist on two place.
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "pm_user_authority_privilege",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = {
#JoinColumn(name = "authority_id", referencedColumnName = "id")
// #JoinColumn(name = "privilege_id", referencedColumnName = "id")
})
private Set<Authority> authorities = new HashSet<>();
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "pm_user_authority_privilege",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "privilege_id", referencedColumnName = "id"))
private Set<Privilege> privileges = new HashSet<>();
Note : There is page which have no. of roles and all relevant privilege of those role.(handle using code define in issue)
And from that i select some of role and save them when user created.
{
"authorities": [
{
"authorityId": "ec071816-31e6-11ea-bae1-0242ac110005",
"authorityName": "MANAGER",
"description": null,
"privileges": [
{
"description": "",
"privilege": "CREATE_USER",
"privilegeId": "ba9a4952-4d53-42e9-94fe-8373d334819e"
},
{
"description": null,
"privilege": "SHOW_DATA",
"privilegeId": "ec06de1b-31e6-11ea-bae1-0242ac110005"
}
]
}
],
"email": "test#gmail.com",
"password": "test#123",
"phoneNo": "8575456595",
"status": true,
"userId": null,
"username": "test"
}
i found something. It is not up to the mark but it help solve your problem.
As per i understand you need two kind of relationship,
Authority & Privileges
User & Authority & Privileges (Privileges also exist in Authority object)
In such case you have to follow this,
Create one UserDTO which access user info and authority JSON (So privilege not show two times).
Based on that JSON create two entity and their object. One for User and another for UserAuthorityPrivilege.
UserAuthorityPrivilege is the table which contain your three required data and as obvious all three are required so define them as embedded key.
Give the relation between user and UserAuthorityPrivilege table
Embaded Class :
#Data
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
public class EmbeddedUserAccess implements Serializable {
#Column(name = "user_id") private UUID userId;
#Column(name = "authority") private String authority;
#Column(name = "privilege") private String privilege; }
Entity Class
#Entity
#Table(name = "pm_user_authority_privilege")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserAuthorityPrivilege {
#EmbeddedId
private EmbeddedUserAccess id;
#Null
#Column(name = "store_id", nullable = true)
private UUID storeId;
}
DTO:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserDTO {
private UUID userId;
private String username;
private String email;
private String phoneNumber;
private String password;
private Boolean status;
private Authority authority;
}
Fetch:
UserAuthorityPrivilege access = null;
EmbeddedUserAccess embedded = null;
Set<UserAuthorityPrivilege> accessibilities = new HashSet<>();
Authority authority = userDTO.getAuthority();
for (Privilege privilege : authority.getPrivileges()) {
access = new UserAuthorityPrivilege();
embedded = new EmbeddedUserAccess();
embedded.setUserId(user.getUserId());
embedded.setAuthority(authority.getAuthority());
embedded.setPrivilege(privilege.getPrivilege());
access.setId(embedded);
accessibilities.add(access);
}
You have to update code similerly.
I have one query, your user relate with single authority or multiple.
For more brief description add comment.

Found shared references to a collection many to many relation

I have this method:
#Override
public Movie createMovie(Movie movie) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
JwtUser user = (JwtUser)authentication.getPrincipal();
User current_user = userRepository.findOne(user.getId());
movieRepository.save(movie);
userRepository.save(new HashSet<User>(){{
add(new User(current_user, new HashSet<Movie>(){{
add(movie);
}}));
}});
return movieRepository.save(movie);
}
When I run my application and call that function I get this error:
Found shared references to a collection: com.movieseat.model.security.User.movies
In my User model I have:
#ManyToMany
#JoinTable(name = "user_movie",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "movie_id", referencedColumnName = "id")
)
private Set<Movie> movies;
And in my Movie model I have:
#ManyToMany(mappedBy = "movies", cascade = CascadeType.ALL)
private Set<User> users = new HashSet<>();
public Set<User> getUsers() {
return users;
}
What produces the error?
As I understand your code, you're trying to create a Movie in database and bind it to the current User. Correct me if I'm wrong.
At first, as you may learn from Hibernate User Guide, bidirectional #ManyToMany association should be defined and used differently.
A bidirectional #ManyToMany association has an owning and a mappedBy side. To preserve synchronicity between both sides, it’s good practice to provide helper methods for adding or removing child entities.
Secondly, you should not use CascadeType.ALL on #ManyToMany associations:
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.
For example, if #ManyToMany(cascade = CascadeType.ALL) was defined and
the first person would be deleted, Hibernate would throw an exception
because another person is still associated with the address that’s
being deleted.
So, we should move cascade to the owning side, change cascade type, provide helper methods to the User and update only the owning side (User) of the association in our business logic. Let's change the code.
User model:
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "user_movie",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "movie_id", referencedColumnName = "id")
)
private Set<Movie> movies = new HashSet<>();
public void addMovie(Movie movie) {
movies.add(movie);
movie.getUsers().add(this);
}
public void removeMovie(Movie movie) {
movies.remove(movie);
movie.getUsers().remove(this);
}
Movie model:
#ManyToMany(mappedBy = "movies")
private Set<User> users = new HashSet<>();
And business logic:
#Override
public Movie createMovie(Movie movie) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
JwtUser user = (JwtUser)authentication.getPrincipal();
User current_user = userRepository.findOne(user.getId());
current_user.addMovie(movie);
userRepository.save(current_user);
return movie;
}

JPARepository findAll() loop data

I have 3 entities:
SLUSER_INFO(SLUSER_INFO_ID, FULLNAME, FIRSTNAME, LASTNAME, ADDRESS, DOB)
SLUSER(SLUSER_ID, PASSWORD, USERNAME, SLUSER_INFO_ID)
SLROLE(SLROLE_ID, ROLENAME, SLUSER_ID)
In User entity:
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonView(SlUserView.Full.class)
private List<SlRole> roles;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "SLUSER_INFO_ID", referencedColumnName = "SLUSER_INFO_ID")
#JsonView(SlUserView.Full.class)
private SlUserInfo userInfo;
In Role entity:
#ManyToOne
#JoinColumn(name = "SLUSER_ID", referencedColumnName = "SLUSER_ID")
private SlUser user;
and User_info entity
#OneToOne(mappedBy = "userInfo")
private SlUser user;
I create the rest api to get all user by using JPARepository findAll
#Repository
public interface SlUserRepository extends JpaRepository<SlUser, Long> {
}
Controller
public ResponseEntity<Page<SlUser>> findAll(Pageable pageable) {
Page<SlUser> pageResult = usersRepository.findAll(pageable);
return new ResponseEntity<Page<SlUser>>(pageResult, HttpStatus.OK);
}
try to run the rest api, the results are duplicated data.
{"content":[{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":{"id":1,"username":"v7001","password":"admin","roles":[{"id":1,"roleName":"ADMIN"}],"userInfo":{"id":1,"fullName":"","firstName":"Jayce","lastName":"Sonar","address":"ad1","dob":null,"user":
Please give me advise
The error is because of the bi-directional relationship between User and Roles.
Use #JsonIgnore on roles in User entity.

Resources