Copy new user with associated roles - java

What I want to do is get data user and copy to a new user (create a new user). This is what I'm doing:
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "role_id")
private int roleId;
#Column(name = "role")
private String role;
public Role() {
}
}
#Entity
#Table(name = "usuario")
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#Column(name = "email")
private String email;
#Column(name = "password", nullable=false, length=60)
private String password;
#Column(name = "name", unique=true, nullable=false, length=6)
private String name;
#Column(name = "last_name")
private String lastName;
#Column(name = "active", nullable=false)
private int active;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
public Users() {
}
}
I get data from one existing user:
Optional<Users> user = usersRepository.findByName(name);
//create a new User to persist
Users newUser = new Users();
newUser.setName("new name");
newUser.setActive(1);
newUser.setEmail(user.get().getEmail());
newUser.setLastName(user.get().getLastName());
newUser.setPassword(user.get().getPassword());
Set<Role> roles = user.get().getRoles();
newUser.setRoles(roles);
usersRepository.save(newUser);
I get this message:
Found shared references to a collection: model.authentication.Users.roles;
nested exception is org.hibernate.HibernateException: Found shared references to a collection: model.authentication.Users.roles
UPDATE 1 (SOLVED)
Optional<Users> user = usersRepository.findByName(codalojamiento);
Users newUser = new Users();
newUser.setName("new name");
newUser.setActive(1);
newUser.setEmail(user.get().getEmail());
newUser.setLastName(user.get().getLastName());
newUser.setPassword(user.get().getPassword());
Set<Role> newRoles = new HashSet();
Set<Role> roles = user.get().getRoles();
for (Role r : roles) {
newRoles.add(r);
}
newUser.setRoles(newRoles);
usersRepository.save(newUser);
Any suggestion?
Thanks

There are multiple things that could go wrong:
Since you are linking a user to multiple roles, but at the same time, multiple users can have the same role, the relation should be #ManyToMany, not #OneToMany.
Secondly, if you insist on having a #OneToMany relationship, you are linking the new user to the roles of the existing user, so a solution might be to create new roles for that user that are identical to the roles of the first user
Personally, i would suggest using a #ManyToMany, and that should fix the problem

In JPA Find method also flushes the data. Here when you have retreived the user using find method it's moved to managed state.
Then you have taken the roles collection from user object and assigned it to newUser. Now basically you have two entities have same collection references which is not allowed.
Either you can detach user from persistence context before saving newUser or clone the roles collection before adding it to newUser.

Related

Spring Boot - combine nested resources for single API calls

Suppose you have two resources, User and Account. They are stored in separate tables but have a one-to-one relationship, and all API calls should work with them both together. For example a POST request to create a User with an Account would send this data:
{ "name" : "Joe Bloggs", "account" : { "title" : "My Account" }}
to /users rather than have multiple controllers with separate routes like users/1/account. This is because I need the User object to be just one, regardless of how it is stored internally.
Let's say I create these Entity classes
#Table(name = "user")
public class User {
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#NotNull
Account account;
#Column(name = "name")
String name;
}
#Table(name = "account")
public class Account {
#OneToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
#NotNull
User user;
#Column(name = "title")
String title;
}
The problem is when I make that POST request above, it throws an error because user_id is missing, since that's required for the join, but I cannot send the user_id because the User has not yet been created.
Is there a way to create both entities in a single API call?
Since it is a bi-directional relation, and one-to-one is a mandatory in this case, you should persist a user entity and only then persist an account. And one more thing isn't clear here is db schema. What are the pk's of entities? I coukd offer to use user.id as a single identity for both of tables. If so, entities would be as:
User(id, name), Account(user_id, title) and its entities are:
#Table(name = "account")
#Entity
public class Account {
#Id
#Column(name = "user_id", insertable = false, updatable = false)
private Long id;
#OneToOne(mappedBy = "account", fetch = FetchType.LAZY, optional = false)
#MapsId
private User user;
#Column(name = "title")
private String title;
}
#Table(name = "user")
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name = "id", referencedColumnName = "user_id")
private Account account;
#Column(name = "name")
private String name;
}
at the service layer you must save them consistently:
#Transactional
public void save(User userModel) {
Account account = user.getAccount();
user.setAccount(null);
userRepository.save(user);
account.setUser(user);
accountRepository.save(account);
}
it will be done within a single transaction. But you must save the user first, coz the user_id is a PK of the account table. #MapsId shows that user's id is used as an account's identity
Another case is when account's id is stored in the user table:
User(id, name, account_id), Account(id, title) and entities are:
#Table(name = "account")
#Entity
public class Account {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "account")
private User user;
#Column(name = "title")
private String title;
}
#Table(name = "user")
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "account_id", insertable = false, updatable = false)
private Long accountId;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name = "account_id", referencedColumnName = "id", unique = true)
private Account account;
#Column(name = "name")
private String name;
}
in this case an Account entity will be implisitly persisted while User entity saving:
#Transactional
public void save(User userModel) {
userRepository.save(user);
}
will cause an insertion into the both of tables. Since cascade and orphane are declared, for deletion would be enough to set null for the account reference:
user.setAccount(null);
userRepository.save(user);

How to check if username already exists in table?

I have the REST API app and when I create a user with the same username it creates all the time, but I want the username to be unique cause in real apps username is unique for each user, how can I provide a checking username in DB?
I want my app to throw some message when I create a user with a username that already exists, and of course, do not save a new user username that already exists
My user Entity:
#Table(name = "usr", uniqueConstraints=
#UniqueConstraint(columnNames={"username", "email"}))
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "role")
private Role role;
#Column(name = "email")
private String email;
#Column(name = "status")
private Status status;
// #OneToMany(mappedBy = "author")
// private List<Message> messages;
// #OneToMany(mappedBy = "author")
// private List<Comment> comments;
#OneToOne
#JoinColumn(name = "gender_id")
private Gender gender;
#OneToOne
#JoinColumn(name = "address_id")
private Address address;
#ManyToMany
#JoinTable(
name = "user_subscriptions",
joinColumns = #JoinColumn(name = "channel_id") ,
inverseJoinColumns = #JoinColumn(name = "subscriber_id"))
private Set<User> subscribers;
#ManyToMany
#JoinTable(
name = "user_subscriptions",
joinColumns = #JoinColumn(name = "subscriber_id") ,
inverseJoinColumns = #JoinColumn(name = "channel_id"))
private Set<User> subscriptions;
#OneToOne
#JoinColumn(name = "file_id")
private FileEntity fileEntity;
My user service:
#Transactional
public User save(UserRequest user) {
provideAllUserCheckingActionsForSave(user);
User userUntilSave = userMap.userRequestToUser(user,Role.USER,Status.ACTIVE);
userUntilSave.setPassword(passwordEncoder.encode(userUntilSave.getPassword()));
User saved = userRepo.save(userUntilSave);
log.info("User save method invoked");
return saved;
}
You can use #unique annotation to store the unique values in the database and if it throws the exception for not taking the unique value the exception should be handled.
Before create user,you can search user by username,if search result is empty,then create new user,if search result is not empty,deal the question.
You can add validation to the controller or you can simply do it as follows:- When a user wants to register, get the username and search in the database whether it already exists or not. If findByUsername(username) returns null then perform the next operation and register the user, else return some error message.

How to insert only in join table but not in reference table in JPA Hibernate

I have two entities User and Role. A user can have multiple roles and each role can be associated with multiple users. i.e., User-Role have many-many relationship
eg: users Ram, Rob, and John are admins
I am creating a join table between User and Role to store the information.
#Entity
#Table(name = "account")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#Column(unique = true)
private String username;
#Transient
#JsonIgnore
private String password;
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
#Column
private boolean deleted;
}
And Role.java
#Entity
#Table(name = "role")
#Data
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="role_id")
private int id;
#Column(name="role",unique=true)
private String role;
}
When this is executed in spring boot, the following tables are being created: account, user_role, role.
Since my number of roles are fixed. I inserted them manually
INSERT INTO `role` VALUES (1,'ADMIN');
INSERT INTO `role` VALUES (2,'USER');
Now from my java spring boot application, I try to insert a new entry in account table. An attempt is done to insert a new entry in Role table as well... I want to insert this only in account and user_role tables but not in role table...
Can anyone let me know how to do it?
UPDATE:
here is account insertion part(create account piece of code)
//Create account
public Account createAccount(AccountDto account) {
Account newAccount = new Account();
newAccount.setPassword(account.getPassword());
newAccount.setUsername(account.getUsername());
Set<Role> roles = new HashSet<Role>();
Role role = new Role();
role.setRole(account.getRole());
roles.add(role);
newAccount.setRoles(roles);
Account savedAccount = save(newAccount);
return savedAccount;
}
//Update Account
public Account updateAccount(String oldUserName, AccountDto accountDto) {
Account account = accountRepository.findByUsername(oldUserName);
if (account != null) {
Set<Role> roleset = new HashSet<Role>();
if(accountDto.getRole() != null && !accountDto.getRole().isEmpty()){
Role role = roleRepository.findByRole(accountDto.getRole());
if (role == null) {
roleset.add(role);
}
}
account.setRoles(roleset);
account.setUsername(accountDto.getUsername());
account = accountRepository.save(account);
return account;
} else
return null;
}
Try to complite CascadeType in Account class.
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.)
You need to add link for the Account Entity as well.
#ManyToMany(fetch = FetchType.EAGER,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "role")
private Set<Account> accounts = new Set<Account>();
You can find nice tutorial in here https://www.callicoder.com/hibernate-spring-boot-jpa-many-to-many-mapping-example/

How to create Hibernate mapping for the following scenario

How to create Hibernate mapping for the following scenario.
I have three tables, Users, Systems and System Assign.
There are three types of user -- Normal user, super user and admin.
Users Table has five columns -- user-ID , userName, password, email id and userType. userType will decide whether a user is super user or admin or normal user.
Systems table have some column but the most important is systemsID which will be used for mapping.
I have created one table System Assign to assign a system to a user with two columns user-ID and system_id (or it can be corrected if required). Also created these two as the foreign key of respective table.
The conditions of mapping are :
a user can have one or more system id on his name.
a system id can be assign to one or more users.
when a system_assign record is deleted from the UI it should only break the link between the user and system but user and system should be in database.
Also I have to make some database changes like this:
If a super user creates a user , this user will be under him
if a super user creates a system, system will be under him.
if a user is deleted then system should come under super user now.
I need to know how to create hibernate classes for this senario
#Entity
#Table(name = "SYSTEM_ASSIGN")
public class SAssign implements Serializable {
/**
*
*/
private static final long serialVersionUID = -1945028654484806943L;
#Id
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "USERSSO")
private Users user;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "SYSTEMID")
private Systems system;
public Users getUser() {
return user;
}
public void setUser(Users user) {
this.user = user;
}
public Systems getSystem() {
return system;
}
public void setSystem(Systems system) {
this.system = system;
}
}
I use a list of Role instead of userType and the #ManyToMany association for user and roles. Join tables for the #ManyToMany association will be created by Hibernate. You don't need to use persistent classes for it.
Classes with the mapping: usersystem
A test for mapping: UserSystemTest.java
public enum RoleEnum {
ADMIN, USER, SUPER_USER
}
#Entity
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue
#Column(name = "f_pid")
private Long pid;
#Column(name = "f_role")
#Enumerated(EnumType.STRING)
private RoleEnum role;
}
#Entity
#Table(name = "systems")
public class SomeSystem {
#Id
#GeneratedValue
#Column(name = "f_pid")
private Long pid;
#Column(name = "f_name")
private String name;
}
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue
#Column(name = "f_pid")
private Long pid;
#Column(name = "f_user_name")
private String userName;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "user_roles", joinColumns = { #JoinColumn(name = "fk_user") },
inverseJoinColumns = { #JoinColumn(name = "fk_role") })
private List<Role> roles = new ArrayList<Role>();
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "system_assign", joinColumns = { #JoinColumn(name = "fk_user") },
inverseJoinColumns = { #JoinColumn(name = "fk_system") })
private List<SomeSystem> systems = new ArrayList<SomeSystem>();
}

While deleting a user entity in hibernate the role get deleted as well

I have 3 tables:
user: (id,username,password)
role: (id,role)
user_roles: (user_id, role_id)
and the following two Hibernate entities:
User:
#Entity
#Table(name = "user")
public class User implements Serializable{
#Id
#GeneratedValue
private Long id;
private String userName;
private String password;
private boolean active = false;
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER )
#JoinTable(name = "user_roles", joinColumns = { #JoinColumn(name = "user_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "role_id", referencedColumnName = "id") })
private Set<Role> roles = new HashSet<Role>();
....
Role:
#Entity
#Table (name = "roles")
public class Role implements Serializable {
#Id #GeneratedValue
private Long id;
private String role;
....
When I delete now a User the corresponding role get deleted as well from the "role" table instead to delete just the user row and the user_roles relation. Even if other users are still related to that role. I use the following to delete the user.
#Transactional
public void deleteByName(String userName) {
User user = this.getByName(userName);
Session session = sessionFactory.getCurrentSession();
session.delete(user);
}
Anyone know why and how to solve this?
Thanks
This is the expected behaviour for the current mapping. It is configured by the cascade setting:
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER )
private Set<Role> roles = new HashSet<Role>();
If you do not want the roles to be removed when a user is, you will have to narrow down your cascade settings. Right now you are using ALL. This is equivalent to {PERSIST, REMOVE, REFRESH, MERGE, DETACH}. Decide on which ones you need and remove the others.
There is one thing I did not understand. You say that the roles are removed "Even if other users are still related to that role". This should not be possible.
The relationship of Role and User is a many-to-one, so there can be only one user attached to a role.

Categories

Resources