I have 3 tables, User, Role, and User_role. User has a OneToMany relationship mapped by "user" with a CascadeType.Merge and user_role has 2 ManyToOne Relationships with cascadeTypes.All however the user_table never populates with data when running hibernate. Instead values are only populated in the user and role tables, but never the user_role table.
User Table Definition
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="Id", nullable=false, updatable = false)
private Long id;
private String username;
private String password;
private String firstName;
private String lastName;
private String email;
private String phone;
private boolean enabled = true;
#OneToMany(mappedBy = "user", cascade=CascadeType.MERGE, fetch =
FetchType.EAGER)
#JsonIgnore
private Set<UserRole> userRoles = new HashSet<>();
UserRole Table:
#Entity
#Table(name="user_role")
public class UserRole implements Serializable {
private static final long serialVersionUID = 890345L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long userRoleId;
#ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL,
optional=false)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL,
optional=false)
#JoinColumn(name = "role_id")
private Role role;
public UserRole () {}
public UserRole (User user, Role role) {
this.user = user;
this.role = role;
}
public long getUserRoleId() {
return userRoleId;
}
public void setUserRoleId(long userRoleId) {
this.userRoleId = userRoleId;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
Call To userRepository.save() in userServiceImpl that is called from a commandLine Runner.
#Service
public class UserServiceImpl implements UserService {
private static final Logger LOG = LoggerFactory.getLogger(UserSecurityService.class);
#Autowired
private UserRepository userRepository;
#Autowired
private RoleRepository roleRepository;
// Indicates a Database Transaction
#Transactional
public User createUser(User user, Set<UserRole> userRoles) {
User localUser = userRepository.findByUsername(user.getUsername());
if(localUser != null) {
LOG.info("User with username {} already exist. Nothing will be done. ", user.getUsername());
} else {
for (UserRole ur : userRoles) {
roleRepository.save(ur.getRole());
}
Set<UserRole> currentRoles =user.getUserRoles();
currentRoles.addAll(userRoles);
user.setUserRoles(currentRoles);
localUser = userRepository.save(user);
}
return localUser;
}
}
Main Class Run()
public void run(String... args) throws Exception {
User user1 = new User();
user1.setFirstName("John");
user1.setLastName("Adams");
user1.setUsername("j");
user1.setPassword(SecurityUtility.passwordEncoder().encode("p"));
user1.setEmail("JAdams#gmail.com");
Set<UserRole> userRoles = new HashSet<>();
Role role1 = new Role();
role1.setRoleId(1);
role1.setName("ROLE_USER");
userRoles.add(new UserRole(user1, role1));
userService.createUser(user1, userRoles);
}
CascadeType.MERGE will only cascade merge events. Persist events wont cascade so if you try to save a new user, the persist event will not cascade to User_role and no entries will be inserted into the user_role table.
Try to add the CascadeType.PERSIST or change to CascadeType.ALL in order to cascade save the record in the database.
#OneToMany(mappedBy = "user", cascade={CascadeType.MERGE, CascadeType.PERSIST}, fetch = FetchType.EAGER)
You can find out more about Cascade events in this answer: What do REFRESH and MERGE mean in terms of databases?
I was able to persist the data to the user_role associative table by implementing the JPA entity manager. A new EntityManager class was instantiated and the data was persisted through the javax.persistence .merge() method.
Related
I have an one to one relationship between the following 2 entities:
#Entity
#Table(name = "user")
public class User {
#Id
#Column(name="id")
private String id;
#Column
private String name;
#Column
private String email;
#Column
private String password;
#OneToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinColumn(name = "user_role_id", referencedColumnName = "id")
private UserRole userRole;
#Entity
#Table(name = "userRole")
public class UserRole {
#Id
#Column(name="id")
private String id;
#Column
private String description;
#OneToOne(mappedBy = "userRole")
private User user;
public UserRole() {
}
I also use EntityManagerFactory to create the tables in my local DB. I received this code and I have to follow it.
public class UserRepo {
private EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("ro.tutorial.lab.SD");
public void insertNewUser(User user) {
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
em.persist(user);
em.getTransaction().commit();
em.close();
}
There is a similar UserRoleRepo too.
My problem is when instantiating in main, I don't know how to get just the UserRole id for the FK in User. Instead, I get the whole instance of userRole and the error "Duplicate entry 'b36fcb4c-3904-4205-888b-9792f24d8b5c' for key 'userrole.PRIMARY' ".
public static void main(String[] args) {
UserRoleRepo userRoleRepo= new UserRoleRepo();
UserRole userRole1 = new UserRole();
userRole1.setId(UUID.randomUUID().toString());
System.out.println(userRole1);
userRole1.setDescription("admin");
userRoleRepo.insertNewUser(userRole1);
UserRole userRole2 = new UserRole();
userRole2.setId(UUID.randomUUID().toString());
System.out.println(userRole2);
userRole2.setDescription("client");
userRoleRepo.insertNewUser(userRole2);
UserRepo userRepo= new UserRepo();
User user = new User();
user.setId(UUID.randomUUID().toString());
user.setName("Todoran");
user.setEmail("todoran#utcluj.ro");
user.setPassword("mona");
user.setUserRole(userRole1); //////////it breaks here :(((((
System.out.println(user);
userRepo.insertNewUser(user);
}
It seemed that I got confused and the proper relationship is one to many between UserRole and User tables. Now it works fine.
My program has a user and a role configuration of security, two models that have between them a many-to-many relationship. When I am trying to save a new user and his m-to-m relationship in a separate table named "user_roles", I get the following error on the session.save() function:
org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions. Collection : [com.session.library.libraryofbooks.model.Role.employees#1]
Here is my User model code:
#Entity
#Table(name="user")
public class User {
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "user_role",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "role_id") }
)
private Set<Role> roles = new HashSet<>();
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "username", nullable = false)
private String username;
#Column(name = "password", nullable = false)
private String password;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "users")
private Set<Book> books = new HashSet<>();
public Set<Book> getBooks() {
return books;
}
public void setBooks(Set<Book> books) {
this.books = books;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
Role model:
#Entity
#Table(name="role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles")
private Set<User> employees = new HashSet<>();
}
UserRepository
#Transactional
#Repository
public class UserRepository {
#Autowired
private EntityManagerFactory entityManagerFactory;
public User findByUsername(String username) {
Session session = entityManagerFactory.unwrap(SessionFactory.class).openSession();
Criteria criteria = session.createCriteria(User.class);
User foundUser = (User) criteria.add(Restrictions.eq("username", username)).uniqueResult();
return foundUser;
}
public void saveUser(User user) {
Session session = entityManagerFactory.unwrap(SessionFactory.class).openSession();
session.save(user);
}
}
UserService save user function:
public void saveUser(User user) {
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
Role userRole = roleRepository.findByName("ROLE_ADMIN");
Set<Role> roleSet = new HashSet<>();
roleSet.add(userRole);
user.setRoles(roleSet);
userRepo.saveUser(user);
}
And the RoleRepository
#Repository("roleRepository")
public class RoleRepository {
#Autowired
private EntityManagerFactory entityManagerFactory;
public Role findByName(String name) {
Session session = entityManagerFactory.unwrap(SessionFactory.class).openSession();
Criteria criteria = session.createCriteria(Role.class);
Role foundRole = (Role) criteria.add(Restrictions.eq("name", name)).uniqueResult();
return foundRole;
}
}
Neither the user datas and the role and the user ids from the user_role table are saving, and I can't figure out what I am doing wrong.
You are using #Transactional but then you use EM to open sessions (and never close it). You should get the current session (SessionFactory.getCurrentSession()).
Anyway I would suggest you to use a pure JPA orm agnostic.
SessionFactory is Hibernate's but you could just use JPA layer and forget about the ORM under the hood.
If you go this way the modification would be:
#PersistenceContext
private EntityManager entityManager;
public Role findByName(String name) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Role> cq = cb.createQuery(Role.class);
Root<Role> from = criteriaQuery.from(Role.class);
Predicate condition = criteriaBuilder.equal(from.get("name"), name);
criteriaQuery.where(condition );
Query query = entityManager.createQuery(criteriaQuery);
Role foundRole = (Role) query.getSingleResult();
return foundRole;
}
a simplier approach:
public Role findByName(String name) {
TypedQuery<Role>query= entityManager.createQuery("Select r from Role r where name=:name",MyEntity.class);
query.
return query.setParameter("name", name).getSingleResult()
}
Here you are another approach (cleaner from my point of view) to implement data access using jpa:
https://www.concretepage.com/java/jpa/jpa-entitymanager-and-entitymanagerfactory-example-using-hibernate-with-persist-find-contains-detach-merge-and-remove
im trying to secure my endpoints with role based access control. I have implemented the whole structure as well as CustomUserDetailService, however im not sure how should i enforce these rules on enpoints, i was looking for some nice annotation based evaluation like #PreAuthorize(hasRole('role')). My structure looks follwoing:
Permission:
#Entity
public class Permission implements GrantedAuthority {
#Id
private Long id;
#Column(name = "NAME")
private String name;
#ManyToMany(mappedBy = "permissions", fetch = FetchType.LAZY)
private Collection<Role> roles;
#Override
public String getAuthority() {
return name;
}
Role:
#Entity
public class Role implements GrantedAuthority {
#Id #Column(name="ID" )
private Long id;
#Column(name="NAME", nullable=false , unique=false)
private String name;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "role_x_permission",
joinColumns = #JoinColumn(
name = "role_id"),
inverseJoinColumns = #JoinColumn(
name = "permission_id"))
private List<Permission> permissions;
#Override
public String getAuthority() {
return name;
}
User:
#Entity(name = "User")
#Table(name = "USERS")
#Data
public class User {
#Id
private Long id;
#Column(name="LOGIN" , nullable=true , unique=false)
private String login;
#Column(name="PASSWORD" , nullable=false , unique=false)
private String password;
#ManyToMany( fetch = FetchType.LAZY)
#JoinTable(name = "USER_ROLES",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
Now i have defined my CustomUserDetailsService:
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User applicationUser = userRepository.findByUsername(username);
if (applicationUser.getId() == null) {
throw new UsernameNotFoundException(username);
}
return new org.springframework.security.core.userdetails.User(applicationUser.getLogin(), applicationUser.getPassword(),
getAuthorities(applicationUser.getRoles()));
}
#Transactional
public Collection<? extends GrantedAuthority> getUserAuthorities(String username) {
User user = userRepository.findByUsername(username);
return getAuthorities(user.getRoles());
}
private Collection<? extends GrantedAuthority> getAuthorities(
Collection<Role> roles) {
return getGrantedAuthorities(getPermissions(roles));
}
private List<String> getPermissions(Collection<Role> roles) {
List<String> permissions = new ArrayList<>();
List<Permission> collection = new ArrayList<>();
for (Role role : roles) {
collection.addAll(role.getPermissions());
}
for (Permission item : collection) {
permissions.add(item.getName());
}
return permissions;
}
private List<GrantedAuthority> getGrantedAuthorities(List<String> permissions) {
List<GrantedAuthority> authorities = new ArrayList<>();
for (String permission : permissions) {
authorities.add(new SimpleGrantedAuthority(permission));
}
return authorities;
}
}
Then i'm trying to annotate my endpoint with #PreAuthorize
#PostMapping("/doSomething")
#PreAuthorize("hasRole('doSomething')")
public SomeEntity createComment(#RequestBody SomeEntity something) {
...
}
I have user with role of USER, this role doesn't have permission to doSomething, however it seems like #PreAuthorize("hasRole('doSomething')") is not working. I'm not sure what i have done wrong, coule you please point my mistake?
Also, since im using RBAC this hasRole is very missleading since access is permission based, not role based.
What would be correct way to authorize access to endpoint with RBAC approach?
You should use hasAuthority('doSomething') instead of hasRole('doSomething').
Role is just a permission with a prefix Role_.
So hasRole('XXX') is same as hasAuthority('ROLE_XXX')
I have a Spring project that uses JPA with Hibernate and MySQL database. Database includes three tables: Users, Roles, and many-to-many join table UserRoles.
Below are the corresponding classes for the tables.
ApplicationUser.java
#Entity
#Table(name = "APPLICATION_USER")
public class ApplicationUser extends ExtAuditInfo {
public static final Long SYSTEM_USERID = 1000L;
#Id
#Column(name = "APPLICATION_USER_ID")
private long applicationUserId;
#Column(name = "LOGIN_NAME")
private String loginName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "MIDDLE_NAME")
private String middleName;
#OneToMany(mappedBy = "id.applicationUser", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<UserRole> roles =new ArrayList<>();
//get and set methods
Role.java
#Entity
#Table(name = "ROLE")
#NamedQueries({
#NamedQuery(name = "Role.getRoleById", query = "select r from Role r where r.roleId =:roleId"))}
public class Role extends AuditInfo {
#Id
#Column(name="ROLE_ID")
private long roleId;
#Column(name="ACTIVE_FLAG")
private String activeFlag;
#Column(name="ROLE_NAME")
private String roleName;
#OneToMany(mappedBy = "id.role", fetch = FetchType.LAZY)
private List<UserRole> users = new ArrayList<>();
//get and set methods
UserRole.java
#Entity
#AssociationOverrides({
#AssociationOverride(name = "id.applicationUser",
joinColumns = #JoinColumn(name = "APPLICATION_USER_ID")),
#AssociationOverride(name = "id.role",
joinColumns = #JoinColumn(name = "ROLE_ID")) })
#Table(name = "USER_ROLE")
public class UserRole extends ExtAuditInfo implements Serializable{
#EmbeddedId
private UserRoleID id = new UserRoleID();
#Column(name="USER_ROLE_VER")
private long userRoleVer;
public UserRole(){
}
#Transient
public ApplicationUser getApplicationUser() {
return this.id.getApplicationUser();
}
public void setApplicationUser(ApplicationUser applicationUser) {
this.id.setApplicationUser(applicationUser);
}
public long getUserRoleVer() {
return this.userRoleVer;
}
public void setUserRoleVer(long userRoleVer) {
this.userRoleVer = userRoleVer;
}
#Transient
public Role getRole() { return this.id.getRole(); }
public void setRole(Role role) { this.id.setRole(role); }
}
UserRoleID.java
#Embeddable
public class UserRoleID implements Serializable {
#ManyToOne(cascade = CascadeType.ALL)
private ApplicationUser applicationUser;
#ManyToOne(cascade = CascadeType.ALL)
private Role role;
private static final long serialVersionUID = 1L;
public UserRoleID() {
}
public ApplicationUser getApplicationUser() {
return this.applicationUser;
}
public void setApplicationUser(ApplicationUser applicationUser) {
this.applicationUser = applicationUser;
}
public Role getRole() {
return this.role;
}
public void setRole(Role role) {
this.role = role;
}
}
Now, when I create a sample user with viewer role, the record is being inserted into the Application_user and User_Role tables, but when I try to update the role of the user it is adding a new role to the user instead of updating the existing role.
This is what I'm doing
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void updateRole(ApplicationUser appUser, long roleId){
EntityManager em=getEntityManager();
TypedQuery<Role> query = em.createNamedQuery("Role.getRoleById", Role.class);
query.setParameter("roleId",roleId);
Role r = query.getSingleResult();
UserRole userRole= appUser.getRole().get(0);
userRole.setRole(r);
em.merge(userRole);
em.flush();
em.detach(userRole);
}
Any idea, what to do to update the existing role instead of creating a new role in user_role table?
You are assigning new role to user, so a new record is added in user_role table, and old user_role entry is deleted. That's the right behavior.
So it's not you called "update the role of user".
Update:
You should delete role manually when many-to-many relationship.
appUser.getRoles().remove(userRole);
em.remove(userRole);
UserRole newUserRole = new UserRole();
newUserRole.setRole(r);
appUser.getRoles().add(newUserRole);
I have the following code:
groupModel.getUserFormGroups().clear();
for(MemberDTO member : group.getMembers()){
User u = userRepository.findByEmail(member.getEmail());
System.out.println(member.getEmail() + " " + groupModel.getName() + " " + member.getRole());
if(u == null){
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
groupModel.getUserFormGroups().add(new UserFormGroup(u, groupModel, UserFormGroupRole.ADMIN));
}
try{
groupRepository.save(groupModel);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
} catch (DataIntegrityViolationException e){
System.out.println(e.getMessage());
System.out.println(e.getClass());
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).build();
}
When I run this, the new UserFormGroups have an id and all the other fields are null. Is there something wrong with fully updating a ManyToOne relationship?
On the group entity I have the following OneToMany relation:
#OneToMany(targetEntity=UserFormGroup.class, cascade = { CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.EAGER, mappedBy = "formGroup", orphanRemoval=true)
private Set<UserFormGroup> userFormGroups = new HashSet<>();
And the UserFormGroup relation looks like this:
#Entity
#Table(uniqueConstraints={
#UniqueConstraint(columnNames = {"user", "form_group"})
})
public class UserFormGroup implements Serializable{
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private long id;
#ManyToOne
#JoinColumn(name = "user",referencedColumnName = "id")
private User user;
#ManyToOne
#JoinColumn(name = "form_group", referencedColumnName = "id")
private FormGroup formGroup;
#Column(name = "role")
private UserFormGroupRole role;
public UserFormGroup() {
}
public UserFormGroup(User user, FormGroup group, UserFormGroupRole role) {
this.user = user;
this.formGroup = group;
this.role = role;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public FormGroup getFormGroup() {
return formGroup;
}
public void setFormGroup(FormGroup formGroup) {
this.formGroup = formGroup;
}
public UserFormGroupRole getRole() {
return role;
}
public void setRole(UserFormGroupRole role) {
this.role = role;
}
}
Not 100% but in my opinion, the problem might be following:
The CrudRepository's implementation of the save method checks whether the object you are saving is a new or existing entity. If it is already an existing entity, it performs a merge operation. This would be the scenario that's happening in your case as the groupModel is an existing entity.
Now on the #OneToMany dependency, you only have these cascade options:
cascade = { CascadeType.PERSIST, CascadeType.REMOVE }
If you add CascadeType.MERGE the operation should be propagated to the UserFromGroup entities and persist them (the default behavior of merge when the entities are new ones).