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);
Related
The issue is that the enum values are not being saved in the database, so whenever I register new user it returns user with 0 role size even though I have all the right configurations, so came to the root cause which is enum values of ERole not being saved in the database and the Role table is empty.
ERole enum:
public enum ERole {
ROLE_USER,
ROLE_MODERATOR,
ROLE_ADMIN
}
Role entity:
#EqualsAndHashCode
#NoArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "roles")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Enumerated(EnumType.STRING)
#Column(length = 20)
private ERole role;
#ManyToMany(mappedBy="roles")
private List<User> users = new ArrayList<>();
public Role(ERole role) {
this.role = role;
}
}
User entity:
#Getter
#Setter
#NoArgsConstructor
#EqualsAndHashCode
#Entity
#Table(name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "name"),
})
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String pass;
#JsonIgnoreProperties("users")
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "role_id") })
private List<Role> roles = new ArrayList<>();
public User(String name, String pass) {
this.name = name;
this.pass = pass;
}
}
As you see below in the diagram Role entity has the role column with ERole type
I have seen the oter similar threads where it is suggested to use the #Enumerated(EnumType.STRING) which I've been using in the first place.
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.
I make method (acceptUseroffermapping) in a REST-controller (UserOfferController) in which I want to delete record in the DB (UserOfferMapping table). But the problem is that record not deleted and relation also saved after I run this method.
I have also UserOfferMapping class which maps to User class. In UserOfferController I manipulate with UserOfferMapping: creating, selecting records from DB and also trying to delete records but have fail.
UserOfferController.java:
/*...*/
#POST
#RequestMapping("/acceptUserOfferMapping")
public void acceptUseroffermapping(#RequestBody Map<String,
String> body) throws ParseException {
String userId = body.get("userId");
String offerId = body.get("offerId");
Optional<User> user = userRepository.findById(Integer.parseInt(userId));
UserOfferMapping mapping = userOfferMappingRepository.getById(Integer.parseInt(userId));
user.get().getUserOfferMapping().remove(mapping);
userRepository.save(user.get());
userOfferMappingRepository.deleteById(Integer.parseInt(offerId));
}
/*...*/
User.java:
/*some imports*/
#Entity
#Table(name = "User")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
/* ...
* a lot of fields
* ...
*/
// Important section which describes all Role Project and Skill mapping
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<UserUserrolemapping> userrolemapings = new HashSet<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<Userprojectmapping> userprojectmappings = new HashSet<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<UserOfferMapping> userOfferMapping = new HashSet<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#OrderBy
private Set<Userskillmapping> userskillmappings = new HashSet<>();
/* ...
* a lot of fields too
* ...
*/
/* getter and setters */
}
UserOfferMappingRepository.java:
public interface UserOfferMappingRepository extends JpaRepository<UserOfferMapping, Integer> {
public List<UserOfferMapping> getAllByUser(Optional<User> user);
public UserOfferMapping getUserOfferMappingByUserAndProjectAndUserRole(User user, Userproject userproject, Userrole userrole);
public UserOfferMapping getById(int id);
public void deleteById(int id);
}
UserOfferMapping.java:
#Entity
public class UserOfferMapping {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne
#JoinColumn(name = "userid")
#JsonBackReference
private User user;
#ManyToOne
#JoinColumn(name = "roleid")
private Userrole userRole;
#ManyToOne
#JoinColumn(name = "projectid")
private Userproject project;
#Column(name = "fromdate", nullable = true)
private Date fromdate;
#Column(name = "todate", nullable = true)
private Date todate;
#Column(name = "chance")
private int chance;
#Column(name = "percent")
private int percent;
public int getId() {
return id;
}
public User getUser() {
return user;
}
public Userrole getUserRole() {
return userRole;
}
public Userproject getProject() {
return project;
}
public Date getFromdate() {
return fromdate;
}
public int getChance() {
return chance;
}
public int getPercent() {
return percent;
}
public void setId(int id) {
this.id = id;
}
public void setUser(User user) {
this.user = user;
}
public void setUserRole(Userrole userRole) {
this.userRole = userRole;
}
public void setProject(Userproject project) {
this.project = project;
}
public void setFromdate(Date fromdate) {
this.fromdate = fromdate;
}
public void setChance(int chance) {
this.chance = chance;
}
public void setPercent(int percent) {
this.percent = percent;
}
public void setTodate(Date todate) {
this.todate = todate;
}
public Date getTodate() {
return todate;
}
}
Can you try to use this
public interface UserOfferMappingRepository extends JpaRepository<UserOfferMapping, Integer> {
public List<UserOfferMapping> getAllByUser(Optional<User> user);
public UserOfferMapping getUserOfferMappingByUserAndProjectAndUserRole(User user, Userproject userproject, Userrole userrole);
public UserOfferMapping getById(int id);
// public void deleteById(int id);
#Modifying(clearAutomatically = true)
#Query(value = "Delete from UserOfferMapping c WHERE c.id=:id")
public void deleteById(#Param("id") int id);
}
So, you have bidirectional entity association.
Try to add mapping.setUser(null); before userRepository.save.
Persisting and deleting objects requires a transaction in JPA so that is why you have to define #Transactional annotation before the method in Repository for example `
#Transactional
public void deleteById(#Param("id") int id);
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.
I have a some problem.
I have 3 tables:
users (id, name, ..)
roles (id, name)
user-role (user_id, role_id)
When I do many-to-many relationship and do save() I have 3 inserts.
User:
#Entity
#Table(name = "user")
public class User implements Serializable {
public static final String UK_EMAIL = "uk_email";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(
name = "system_user_role",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "role_id", referencedColumnName = id")}
)
private List<SystemRole> userRole;
public List<SystemRole> getUserRole() {
return userRole;
}
SystemRole;
#Entity
#Table(name = "system_role")
public class SystemRole implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column
private String name;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "system_user_role",
joinColumns = {#JoinColumn(name = "role_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")}
)
private List<User> users;
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
PLEASE, tell me, can I insert data into 2 tables only, only into (User and user_role?
I have roles list and I need not add a new role when I create a new user.
So, when I do:
SystemRole role1 = systemRoleService.findOne("ROLE_ADMIN");
userForm.setUserRole(Lists.newArrayList(role1));
....
final User saved = userRepository.save(user);
....
I get an error:
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist:...
If i do:
#Service("userService")
#Transactional
public class UserServiceImpl implements UserDetailsService, ResourceService<User> {
private final static Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);
#Autowired
private UserDAO userRepository;
#Autowired
private SystemRoleDAO systemRoleRepository;
#Override
#Transactional(rollbackFor = ResourceException.class)
public User create(User user) throws ResourceException {
try {
SystemRole role1 = systemRoleRepository.findOne(6l);
user.setUserRole(Lists.newArrayList(role1));
user.setId(62l); // !!! if set user ID - it works CORRECT
final User saved = userRepository.save(user);
return saved;
} catch (DataIntegrityViolationException ex) {
...
UserDAO:
#Repository
public interface UserDAO extends JpaRepository<User, Long> {
...
SystemRoleDAO:
#Repository
public interface SystemRoleDAO extends JpaRepository<SystemRole, Long> {
It works, but I have 3 inserts.
When I create a new user, I need to select a role from list, add it to the user and save the new user.
Many thanks.
#Entity
#Table(name = "user")
public class User implements Serializable {
public static final String UK_EMAIL = "uk_email";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) //Make CascadeType.Detached this should solver your problem
#JoinTable(
name = "system_user_role",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "role_id", referencedColumnName = id")}
)
private List<SystemRole> userRole;
public List<SystemRole> getUserRole() {
return userRole;
}
I guess that your code inside the userRepository.save() method is calling
entityManager.persist(user);
Instead you should be calling
entityManager.merge(user);
BY doing so, instead of inserting a new Role, Hibernate will check if a Role with the same ID already exists and if this is case, that Role will be attached to the User entity (provided that the cascade type includes Merge operation).
The reason why you need to call merge is that you are using a Role entity which has been loaded (via systemRoleService.findOne("ROLE_ADMIN")) and then detached from persistence context, so this entity is detached when you try to save the User. If Role had not been detached, you could call persist() instead of merge() on the User entity.