I'm trying to add a functionality to my webapp with user registration. Webapp is based on spring boot, hibernate and mysql database, frontend is in angular. Generally, the user creation procedure is working correctly, user data is correctly send from frontend to backend via json and saved to the database in shop_user table (with all the user data, such as name, surname, address etc.), but it DOESN'T have role column.
I also have table 'role', which should be:
id name
1 USER
2 ADMIN
and joined table user_role, which consists of user_id from table shop_user and role id from table role, so it should look like this:
id_user id_role
1 2
2 1
3 1
When user is being created on the website, it is hard-coded to set the role by default to USER. This seems to work quite well as it adds a new row in shop_user, and it adds a row to user_role, but... it also creates a new row in 'role' table.
so in the end 'role' table looks like this:
id name
1 ADMIN
2 USER
3 USER
4 USER
5 USER
99 USER
`
while this is not a blocking bug that stops application from working, it is not 'as it should work' unfortunately... as the table should only consist of two role rows (and possibly additional ones, in the future), but not multiplicated for each user!
here's the flawed code of user:
User
#Entity
#Table(name = "shop_user")
public class User extends AbstractEntity {
#Column
private String firstName;
#Column
private String lastName;
#Column
private String addressLine;
#Column
private String city;
#Column
private String country;
#Column
private String zipCode;
#Column
private String phoneNumber;
#Column
private String email;
#Column
private String password;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_role",
joinColumns = #JoinColumn(name = "id_user", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "id_role", referencedColumnName = "id"),
uniqueConstraints = {#UniqueConstraint(columnNames = {"id_user", "id_role"})})
private List<Role> roles;
public User() {
}
public User(User user) {
setId(user.getId());
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
this.addressLine = user.getAddressLine();
this.city = user.getCity();
this.country = user.getCountry();
this.zipCode = user.getZipCode();
this.phoneNumber = user.getPhoneNumber();
this.email = user.getEmail();
this.password = user.getPassword();
this.roles= user.getRoles();
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getAddressLine() {
return addressLine;
}
public void setAddressLine(String addressLine) {
this.addressLine = addressLine;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Role implementation:
Role
#Entity
#Table(name = "role")
public class Role extends AbstractEntity {
#Column
private String name;
#ManyToMany(mappedBy = "roles", cascade = CascadeType.PERSIST)
private List<User> users;
public Role(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
Abstract entity:
AbstractEntity
#MappedSuperclass
public abstract class AbstractEntity implements Persistable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public void setId(Long id) {
this.id = id;
}
#Override
public Long getId() {
return id;
}
#Override
public boolean isNew() {
return id == null;
}
}
User service:
UserServiceImpl
#Service
public class UserServiceImpl extends AbstractServiceImpl<User, UserDTO> implements UserService {
private final UserRepository userRepository;
private final UserConverter userConverter;
public UserServiceImpl(UserRepository userRepository, UserConverter
userConverter) {
this.userRepository = userRepository;
this.userConverter = userConverter;
}
#Override
protected JpaRepository<User, Long> getRepository() {
return userRepository;
}
#Override
protected Converter<User, UserDTO> getConverter() {
return userConverter;
}
#Override
#Transactional
public User registerUser(User user) {
List<Role> roles = new LinkedList<>();
roles.add(new Role("USER"));
user.setRoles(roles);
return userRepository.save(user);
}}
I am nearly sure that this comes to the relations mapping in Hibernate and object creation, but can't quite figure it out...
Any help will be appreciated, thank you!
The issue is here:
#Override
#Transactional
public User registerUser(User user) {
List<Role> roles = new LinkedList<>();
roles.add(new Role("USER"));
user.setRoles(roles);
return userRepository.save(user);
}}
Since the relationship User -> Role is cascade persist, the (new) role new Role("USER") is also persisted and you ended up with a new Role for each user instead of reusing the existing one.
The solution is to check the existence of a Role with name = USER. If doesn't exist, insert it. Otherwise add the existent one to the roles collection.
Related
I have a two tables: User and Wallet
User have: id
Wallet have: userId, walletName
Now, I want to avoid that so user cant have two wallets with same name.
I create something but it applies to all users, so if user with id 1 create a wallet with name walletSaving no-one can create wallet with that name, while I want to avoid that, I want to create something to check whether the user with id 1 have already wallet with that name.
So far I have this:
if (walletRepository.existsByWalletName(walletRequest.getWalletName())) {
return ResponseEntity.badRequest().body(new MessageResponse("You already have wallet with that name, choose another!"));
}
After some help I tried with something like this:
#PostMapping("/user/{user_id}/wallets")
public ResponseEntity<?> createWallet(#PathVariable(value = "user_id") Long user_id,
#RequestBody Wallet walletRequest, User user) {
if (walletRepository.existsByUserIdAndWalletName(user.getId(), walletRequest.getWalletName())) {
return ResponseEntity.badRequest()
.body(new MessageResponse("You already have wallet with that name, choose another!"));
}
Its still creating wallets with same name.
Just to provide more info.
User entity
#Entity
#Table(name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username"),
#UniqueConstraint(columnNames = "email")
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty
#Size(min = 3, max = 20)
private String username;
#NotEmpty
#Size(max = 50)
#Email
private String email;
#NotEmpty
#Size(min = 6)
private String password;
#NotEmpty(message = "Please, insert a first name")
private String firstName;
#NotEmpty(message = "Please, insert a last name")
private String lastName;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "user_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
public User() {
}
public User(String username, String email, String password, String firstName, String lastName) {
this.username = username;
this.email = email;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
Wallet entity
#Entity
#Table(name = "wallet")
public class Wallet {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty(message = "Please, insert a wallet name")
private String walletName;
private double initialBalance;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private User user;
public Wallet() {
}
public Wallet(String walletName, double initialBalance) {
this.walletName = walletName;
this.initialBalance = initialBalance;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getWalletName() {
return walletName;
}
public void setWalletName(String walletName) {
this.walletName = walletName;
}
public double getInitialBalance() {
return initialBalance;
}
public void setInitialBalance(double initialBalance) {
this.initialBalance = initialBalance;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
Wallet Repository
boolean existsByUserIdAndWalletName(Long userId, String walletName);
You need to include the userId in your query:
final long userId = getUserId(); // get the currently authenticated user's id
if (walletRepository.existsByUserIdAndWalletName(userId, walletRequest.getWalletName())) {
return ResponseEntity.badRequest()
.body(new MessageResponse("You already have wallet with that name, choose another!"));
}
existsByUserIdAndWalletName(userId, walletName);
or
findByUserIdAndWalletName(userId, walletName);
If id of the user and walletName is provided, it will check only for that user if it has that wallet name or not, and if in that situation returns something (true or result>0), that will mean that for that specific user the wallet name is already taken.
https://www.baeldung.com/spring-data-derived-queries#multiple-condition-expressions
I am using Spring boot-I have 3 classes User,Role and UserRole.I have pesisted both role object and user object but i get error that role object is not persisted.The mappings- between User and UserRole is OneToMany ,between Role and UserRole OneToMany.In the UserServiceImpl class i have persisted Role object roleRepository.save(userRole.getRole());
Error is-
Caused by: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.bookstore.domain.security.UserRole.role -> com.bookstore.domain.security.Role
#Entity
public class User implements UserDetails,Serializable {
private static final long serialVersionUID=157954L;
#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;
#OneToMany(mappedBy = "user",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JsonIgnore
private Set<UserRole> userRoles=new HashSet<UserRole>();
public Long getId() {
return id;
}
public void setId(Long 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 String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities=new HashSet<GrantedAuthority>();
userRoles.forEach(userRole->{
authorities.add(new Authority(userRole.getRole().getRoleName()));
});
return authorities;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return enabled;
}
#Override
public String getUsername() {
return userName;
}
}
#Entity
public class Role implements Serializable{
private static final long serialVersionUID=68678L;
#Id
private Long roleId;
private String roleName;
#OneToMany(mappedBy = "role",cascade = CascadeType.ALL,fetch = FetchType.LAZY)
#JsonIgnore
private Set<UserRole> userRoles=new HashSet<UserRole>();
public Role() {
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
}
#Entity
public class UserRole implements Serializable {
private static final long serialVersionUID=456874L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userRoleId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "roleId")
private Role role;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "userId")
private User user;
public UserRole(User user,Role role) {
this.role = role;
this.user = user;
}
public UserRole() {
super();
}
public Long getUserRoleId() {
return userRoleId;
}
public void setUserRoleId(Long userRoleId) {
this.userRoleId = userRoleId;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
#Service
public class UserServiceImpl implements UserService {
private static final Logger LOG=LoggerFactory.getLogger(UserServiceImpl.class);
#Autowired
UserRepository userRepository;
#Autowired
RoleRepository roleRepository;
#Transactional
#Override
public User CreateUser(User user, Set<UserRole> userRoles) {
User localUser=userRepository.findByUserName(user.getUserName());
if(localUser!=null) {
LOG.warn("Username {} already exists",user.getUserName());
}
else {
for(UserRole userRole:userRoles) {
roleRepository.save(userRole.getRole());
LOG.error("inside for {}",userRole.getRole().getRoleName());
}
user.getUserRoles().addAll(userRoles);
localUser=userRepository.save(user);
}
return localUser;
}
}
#SpringBootApplication
public class BookStoreApplication implements CommandLineRunner {
#Autowired
UserService userService;
public static void main(String[] args) {
SpringApplication.run(BookStoreApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
User user1=new User();
user1.setUserName("test");
user1.setPassword(SecurityUtility.passwordEncoder().encode("test"));
user1.setEmail("test#test.com");
user1.setEnabled(true);
user1.setFirstName("testFirstName");
user1.setLastName("testLastName");
user1.setPhone("123456789");
Role role1=new Role();
role1.setRoleId((long)1);
role1.setRoleName("ROLE_USER");
UserRole userRole1=new
UserRole(user1,role1);
Set<UserRole> userRoles1=new HashSet<UserRole>();
userRoles1.add(userRole1);
userService.CreateUser(user1, userRoles1);
User user2=new User();
user2.setUserName("admin");
user2.setPassword(SecurityUtility.passwordEncoder().encode("admin"));
user2.setEmail("admin#admin.com");
user2.setEnabled(true);
user2.setFirstName("adminFirstName");
user2.setLastName("adminLastName");
user2.setPhone("223456789");
Role role2=new Role();
role2.setRoleId((long) 2);
role2.setRoleName("ROLE_ADMIN");
UserRole userRole2=new UserRole(user2,role2);
Set<UserRole> userRoles2=new HashSet<UserRole>();
userRoles2.add(userRole2);
userService.CreateUser(user2, userRoles2);
}
}
Couple of issues here.
The first (and the question) issue and the reason why you are getting a "Transient state error" is because you are trying to save an entity with entities attached to it that are NOT yet managed by hibernate.
Have a read of: Entity Lifecycle Model in Hibernate
Caused by: org.hibernate.TransientPropertyValueException:
object references an unsaved transient instance -
save the transient instance before flushing :
com.bookstore.domain.security.UserRole.role
->
com.bookstore.domain.security.Role
So you are somewhere trying to save a UserRole with a Role that is not yet managed.
When you call new on an entity, it is in a Transient state. Hibernate doesn't know how to handle it. It doesn't have a database ID and is not part of the context for hibernate to manage (make the relevent queries etc).
To make an entity managed you need to save it via the repo.
I.e. roleRepo.save(role)
You will then notice it then has an Id and is now managed by hibernate.
#Service
public class UserServiceImpl implements UserService {
#Transactional
#Override
public User CreateUser(User user, Set<UserRole> userRoles) {
User localUser = userRepository.findByUserName(user.getUserName());
if (localUser != null) {
LOG.warn("Username {} already exists", user.getUserName());
} else {
// For each transient UserRole passed in, save the Role.
// Role is now managed.
for (UserRole userRole : userRoles) {
roleRepository.save(userRole.getRole());
LOG.error("inside for {}", userRole.getRole().getRoleName());
}
user.getUserRoles().addAll(userRoles);
localUser = userRepository.save(user);
}
return localUser;
}
}
This service above doesn't maybe do what you expect.
You are getting the Roles and saving them.
You then don't replace the Role in UserRole with the managed one back from the repo.
Maybe this would work?
for(UserRole userRole:userRoles) {
//The role is now managed.
Role managedRole = roleRepository.save(userRole.getRole());
//Replace the transient role in the UserRole with a managed Role.
userRole.setRole(managedRole);
}
So when it goes on to save the User:
user.getUserRoles().addAll(userRoles);
localUser = userRepository.save(user);
The UserRoles (which are still transient) have a managed Role at least.
The Cascade.ALL should do what you expect (I am unsure mind!) and save the transient UserRoles because Cascade.ALL will save the children UserRoles.
https://www.baeldung.com/jpa-cascade-types
=============================
The second issue, not causing the problem in question, but you may want to go have a think about:
At the moment you have:
User 1 : M UserRole
UserRole M : 1 Role
1 User has many UserRoles.
1 Role has many UserRoles.
The modelling here just smells off.
Usually you'd have some Role entities/database entries that can be related to a user via ManyToMany relationship.
User signs up, is given the "USER" role in their Set<Role> userRoles rather than creating a new Role for each user with "USER" as a field.
So a user has a relationship to a role via a "join table" UserRole.
Spring can already create a join table for you. You do not really need the UserRole entity in your code as it stands as it just holds a FK_User and FK_Role.
Basically, you want:
User M:M Role
I.e. a user can have many roles.
Simply use the #ManyToMany annotation for a Many:Many relationship between user and roles.
To add a role you search the database for
Role findByRoleName
And add that managed entity to the user's roles and then persist the user.
ManyToMany Baeldung
I have a authentication issue with mysql via spring boot rest api.
I want to create a user from the service myself using 'saveUser' method and access this user information and rest api. Without using any mvc structure (html,js,css ex.), i want to create a user using the service and log in with that user to rest api.I mentioned all the units of the project below.
I'd appreciate it if you helped.
User
#Data
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#Column(name = "email")
#Email
#NotEmpty
private String email;
#Column(name = "password")
#Length(min = 5)
#NotEmpty
private String password;
#Column(name = "name")
#NotEmpty
private String name;
#Column(name = "last_name")
#NotEmpty
private String lastName;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
//G&S
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
Role
#Data
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "role_id")
private int id;
#Column(name = "role")
private String role;
//G&S
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}
UserRepository
public interface UserRepository extends JpaRepository<User, Integer>{
User findByEmail(String email);
List<User> findByName(String name);
List<User> findByLastName(String lastName);
}
RoleRepository
public interface RoleRepository extends JpaRepository<Role, Integer>{
Role findByRole(String role);
}
Service
#org.springframework.stereotype.Service
public class UserService {
#Autowired
UserRepository userRepository;
#Autowired
RoleRepository roleRepository;
#Autowired
public UserService(UserRepository userRepository,
RoleRepository roleRepository) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
}
//CREATE USER
public User saveUser(User user) {
user.setPassword("mutlu1234");
Role userRole = roleRepository.findByRole("ADMIN");
user.setRoles(new HashSet<Role>(Arrays.asList(userRole)));
return userRepository.save(user);
}
}
MainController
#Controller
public class MainController {
#Autowired
UserService userService;
#RequestMapping(value="/", method = RequestMethod.GET)
public String createUser() {
User mutlu = new User();
userService.saveUser(mutlu);
return "Created";
}
#RequestMapping(value = "/private/{accountNumber}")
public String getPrivateAccountData(#PathVariable final int accountNumber){
return "Private account lined:"+accountNumber;
}
}
Config
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private DataSource dataSource;
#Override
public void configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity.authorizeRequests().antMatchers("/private/*").hasRole("ADMIN").and().formLogin();
}
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.
jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select email, password from users where email=?")
.authoritiesByUsernameQuery("select u.email, r.role from users u inner join user_role ur on(u.user_id=ur.user_id) inner join role r on(ur.role_id=r.role_id) where u.email=?")
.dataSource(dataSource);
}
}
data.sql in the resources
REPLACE INTO `role` VALUES (1,'ADMIN');
REPLACE INTO `role` VALUES (2,'USER');
ApplicationProperties
server.port=8090
spring.datasource.url=jdbc:mysql://localhost:3306/auth?useUnicode=true&characterEncoding=UTF-8&useLegacyDatetimeCode=false&serverTimezone=Turkey
spring.datasource.username=root
spring.datasource.password=
spring.datasource.initialization-mode=always
spring.jpa.hibernate.ddl-auto=none
I think the get method on the "/" is the issue. It should be a post method for data to be persisted to the database. Try changing it to a post method.
#Entity
#NamedQuery(name="User.findAll", query="SELECT u FROM User u")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
private String email;
private String password;
private String username;
#Transient
private String passwordConfirm;
//bi-directional many-to-one association to Role
#ManyToOne(fetch=FetchType.LAZY ,cascade = {CascadeType.PERSIST})
private Role role;
public User() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public Role getRole() {
return this.role;
}
public void setRole(Role role) {
this.role = role;
}
#Transient
public String getPasswordConfirm() {
return passwordConfirm;
}
public void setPasswordConfirm(String passwordConfirm) {
this.passwordConfirm = passwordConfirm;
}
}
#Entity
#NamedQuery(name="Role.findAll", query="SELECT r FROM Role r")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
private String name;
//bi-directional many-to-one association to User
#OneToMany(fetch=FetchType.LAZY, mappedBy="role", cascade = {CascadeType.PERSIST})
private List<User> users;
public Role() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public List<User> getUsers() {
return this.users;
}
public void setUsers(List<User> users) {
this.users = users;
}
public User addUser(User user) {
getUsers().add(user);
user.setRole(this);
return user;
}
public User removeUser(User user) {
getUsers().remove(user);
user.setRole(null);
return user;
}
}
public interface UserRepository extends CrudRepository <User, Long> {
public UserDto getUserDetailsById(Long userId) throws commonException {
//ArrayList<UserDto> arr = new ArrayList<>();
User user = userRepository.`findOne`(userId);
UserDto userDto = new UserDto();
userDto.setId(user.getId());
userDto.setUsername(user.getUsername());
userDto.setEmail(user.getEmail());
userDto.setPassword(user.getPassword());
userDto.setRoleId(user.getRole().getId());
userDto.setRoleName(user.getRole().getName());
// arr.add(userDto);
return userDto;
}
find by Id is not getting role details by using user object.lazy initialization is not happening.When I use to get user ID from user object, I can not get role details.
userDto.setRoleId(user.getRole().getId()); is having null value
In your User entity, you have configured the Role collection to load lazily. Therefore when you first call user.getRole(), you get a proxy object. If you want to call methods on the proxy object that need to fetch data, you should initialize the proxy. There is a technique to initialize it. Try the following:
Before the following line of code
userDto.setRoleId(user.getRole().getId());
add the following;
user.getRole().size();
For situations where you know that you need a particular association, its generally recommended that you specify that the association be join-fetched.
If the association is optional, you'd use something like:
FROM User u LEFT JOIN FETCH u.role WHERE u.id = :userId
If the association is not-optional, you could improve the above by specifying an inner join like:
FROM User u JOIN FETCH u.role WHERE u.id = :userId
With Spring data, you can use the #Query annotation on methods and specify the JPQL/HQL above to suit your needs.
I have a problem with Rest response when I use ManyToMany annotation.
Problem is this answer:
Problem accessing /json2/1. Reason:
Server Error
Caused by:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.Tomek.entity.User.roles, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.Tomek.entity.Role["users"]->org.hibernate.collection.internal.PersistentBag[0]->com.Tomek.entity.User["roles"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.Tomek.entity.User.roles, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.Tomek.entity.Role["users"]->org.hibernate.collection.internal.PersistentBag[0]->com.Tomek.entity.User["roles"])
Without ManyToMany annotation(like in the Model class Role) I response JSON format
[{"id":1,"name":"ROLE_USER"},{"id":2,"name":"ROLE_ADMIN"}]
RestController
#Controller
public class RestController {
#Autowired
private UserService userService;
#Autowired
private BlogService blogService;
#Autowired
private RoleService roleService;
#RequestMapping("/json")
public String JsonLink(Model model){
model.addAttribute("result", blogService.findAll());
return "json";
}
#RequestMapping(value = "/json2/{id}", method = RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody List<Role> ShowJson(#PathVariable int id) {
Hibernate.initialize(roleService.findAll());
List<Role> role = roleService.findAll();
System.out.println(role.toString());
return role;
}
Model Role (comment #ManyToMany)
#Entity
#JsonAutoDetect
public class Role {
#Id
#GeneratedValue
private Integer id;
private String name;
/*#ManyToMany(fetch = FetchType.EAGER,mappedBy = "roles")
private List<User> users;
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}*/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Role(String name) {
this.name = name;
}
public Role() {
}
#Override
public String toString() {
return "Role [id=" + id + ", name=" + name + "]";
}
}
Model User
#Entity
#JsonAutoDetect
public class User {
#Id
#GeneratedValue
private Integer id;
private String name;
private String email;
private String password;
private boolean enabled;
#ManyToMany
#JoinTable
private List<Role> roles;
#OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
private List<Blog> blogs;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public List<Blog> getBlogs() {
return blogs;
}
public void setBlogs(List<Blog> blogs) {
this.blogs = blogs;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Service
#Service
public class RoleService {
#Autowired
private RoleRepository roleRepository;
public List<Role> findAll(){
return roleRepository.findAll();
}
JSP
<c:forEach items="${result}" var="item">
json
</c:forEach>
The problem is with serializing property roles of User entity. When you load your entity in RoleService and then return result to controller the hibernate session is ended. You are not able to load it outside hibernate session.
You cannot load these property eagerly too. Then there would be a chance to load large tree of objects.
In my opinion to solve your proplem you have to create 3 REST services and 3 normal service method which would load flat data structures:
/roles/{id} - it loads single role (without users property)
/roles/{id}/users - it loads all users that have role with given id (without roles property)
/users/{id}/roles - it loads roles for user with given id
Additionaly you have to annotate your collection properties (roles, users) with annotation #JsonIgnore to ignore them during serialization to json.