Child table is not mapping in OneToMany relationship in JPA - spring

I am trying to establish One to many relationship between User and Role.
One user can have many role.
Here is the code for User class
#Entity
public class User {
#Id
private int id;
private String name;
private String password;
private String email;
private String phoneNo;
#OneToMany(
targetEntity = Role.class,
mappedBy = "user",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY
)
private Set<Role> roles;
// Getters, setters and Constructor
Code for the Role class
#Entity
public class Role {
#Id
#GeneratedValue
private int roleId;
private String role;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
// Getters, setters and Constructor
POST request on Postman is
{
"id":101,
"name": "rahul",
"password": "456",
"email": "rahul#gmail.com",
"phoneNo": "1234561234",
"role": [{
"role":"USER"
}]
}
Code on Configuration part
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http)throws Exception
{
http.csrf().disable();
}
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
}
Code On Controller part
#RestController
public class AdminController {
#Autowired
UserRepository userRepo;
#Autowired
BCryptPasswordEncoder encryptPassword;
#PostMapping("/admin/add")
public String addUserByAdmin(#RequestBody User user)
{
String pass = user.getPassword();
String encrypt = encryptPassword.encode(pass);
user.setPassword(encrypt);
userRepo.save(user);
return "User added Successfully";
}
}
Role table connection to database through Jpa
public interface RoleRepository extends JpaRepository<Role, Integer> {
}
User table connection to database through Jpa
public interface UserRepository extends JpaRepository<User,Integer>{
}
Here problem is User table is mapped properly but Role table is not getting mapped.
roleId role user_id
NULL NULL NULL
Where I am wrong ? Could anyone help me ?

On your controller method below, have you tried debugging the incoming request User object?
Anyways, I have below points here:
First, looking into your request body, the field for your roles is named role while your User object has a field roles thus, I'm pretty sure it is null during your processing since it will not be deserialized there due to mismatch field names. Try changing your request body to something like this:
{
"id":101,
"name": "rahul",
"password": "456",
"email": "rahul#gmail.com",
"phoneNo": "1234561234",
"roles": [{
"role":"USER"
}]
}
Second, if you check your database, the roles will be persisted however your foreign key user_id is null. This is expected. The cascade you did on the User object will only means that (since you use CascadeType.ALL) once you save the User object the save operation will also be cascaded to the Role object however, JPA still needs to know the relationship thus you have to set the user for each role object. Hence, you can update your controller method to something below:
#PostMapping("/admin/add")
public String addUserByAdmin(#RequestBody User user)
{
String pass = user.getPassword();
String encrypt = encryptPassword.encode(pass);
user.setPassword(encrypt);
// JPA needs to know this relationship...
user.getRoles().forEach(role -> role.setUser(user));
userRepo.save(user);
return "User added Successfully";
}
Now you can try and see that your expected behavior should now be happening.
Additional recommendations:
Why are we passing ID field on the user request? You can just remove that from your request body and use below to auto-generate your IDs to avoid Unique index or primary key violation exceptions on all of your entities:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
You can also remove the targetEntity = Role.class on the mapping as it is only used for generics and for your case clearly you are not using generics for Set. Update your User object for roles mapping:
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Role> roles;
Lastly, it is better if you can wrap your incoming payload to a DTO since you would not want to expose your entity/model to your API but I am thinking this is just for your test environment.

You need to flush the changes to the database when using save(), try this instead:
userRepo.saveAndFlush(user);

Related

Confused why getting a User from Repository fixed "failed to lazily initialize a collection of role" compared to using SecurityContextHolder

My goal was to pass a List of Businesses to the model from the controller to display it in a view and I have succeeded, but have a bit of confusion.
When I initially tried using:
public User getCurrentAuthenticatedUser() {
UserDetailsImpl user = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return user.getUser();
}
#GetMapping("")
public String list(Model model) {
model.addAttribute("businesses", userService.getCurrentAuthenticatedUser().getBusinesses());
return "business/list";
}
I got this error: "failed to lazily initialize a collection of role: com.xyz.User.businesses could not initialize proxy - no Session"
Then I tried:
#GetMapping("")
public String list(Model model) {
int userId = userService.getCurrentAuthenticatedUser().getId();
User user = userService.getById(userId); // gets User using Spring Data JPA UserRepository
List<Business> businesses = user.getBusinesses();
model.addAttribute("businesses", businesses);
return "business/list";
}
And this worked perfectly fine.
What was the issue using the first method. It seemed more simple rather than calling a User from the UserRepository. I've seen some posts that say you should use EAGER fetching, but that's just seems like a bandaid solution.
From the beginner's understanding: Since fetch type is LAZY the businesses don't exist yet in the User but are fetched on demand later on so there shouldn't be an issue.
Edit: After more thought I remembered that with basic Hibernate you would have to create Transactions and commit transactions. I'm assuming that User is not within a Transaction that's why I can't get businesses using the 1st method.
What would be a better solution to fetch the current Authenticated user? And that user's attributes such as a list of businesses.
Model Classes:
Business:
#Entity
#Table(name = "businesses")
public class Business {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private LocalDate date;
#ManyToOne(cascade={CascadeType.MERGE})
#JoinColumn(name="user_id")
private User user;
public Business() {
}
public Business(String name, String description, LocalDate date, User user) {
...
}
public Business(Long id, String name, String description, LocalDate date, User user) {
...
}
... getters/setters
}
USER:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private boolean enabled;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable( name = "users_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
#OneToMany(fetch = FetchType.LAZY, mappedBy="user", cascade={CascadeType.MERGE})
private List<Business> businesses;
... getters/setters
}

How to add new fields / values to an existing entity in Spring Data JPA

How can I add new fields or values or properties to an existing entity and store them in the same table?
Customer entity already exists and have fields as
- id
- name
- lastName
Want to add contactNumber (as seen in the api structure below) to this existing Customer entity. Don't want a new entity for contactNumber
The expected request body is as below:
{
"id": "int auto generated",
"name": "String",
"lasName": "String",
"contactNumber":
{
"mobile":"long",
"office":"long",
"home":"long"
}
}
How can this be achieved ? Checked some blogs related to mapstruct but not getting proper solution.
You can use #Embeddable :
#Embeddable
public class ContactNumber {
private Long mobile;
private Long office;
private Long home;
// getters, setters...
}
Customer Entity:
#Entity
public class Customer {
#Id
private Long id;
private String name;
private String lastName;
#Embedded
private ContactNumber contactNumber;
// getters, setters...
}
With this mapping three columns(mobile, office, home) will be added to the Customer table.
You can simply save the Customer with the request body in the question using (#RequestBody Customer customer) parameter:
#PostMapping(value="/customers")
public void saveCustomers(#RequestBody Customer customer) {
customerRepository.save(customer);
}
For more information take a look at here.

Issue in persisting nested comments using Spring Hibernate

I am trying to create a simple CRUD application using Spring Boot with User, UserEntity, Post, Comment entities.
-> UserEntity is super class of Comment and Post.
-> Each comment has a ManyToOne relationship to a UserEntity (which can be a Post or another Comment)
UserEntity
   |
   #ManyToOne
   createdBy - refers to user table (id)
   |
--------------------
|        |
|        |
Post    Comment
        |
        #ManytoOne
          UserEntity - refers to PK(entity_id) of user_entity table as comment can be on post or reply to another comment
On trying to save a comment on post from the CommentService class,
//Controller
#PostMapping(path = "api/v1/addComment")
public void addComment(#RequestBody Comment comment){ commentService.addCommentOnPost(comment); }
//Service
public void addCommentOnEntity(Comment comment){ commentRepos.save(comment); }
the foreign key in comment table (parent_entity_id) referring to entity_id in user_entity table is not getting updated. The value is blank.
On the other hand UserEntity has a manytoone relationship with User -- createdBy -- which is updating foriegn key user_id in user_entity table properly
Can someone guide me what could be wrong, I have been trying since yesterday night but no luck. Have checked some other answers but could not get an answer for this case.
User.java
#Entity
#Table(name="[user]")
public class User {
#Id
#SequenceGenerator(name="student_sequence",
sequenceName = "student_sequence",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "student_sequence")
private long id;
private String name;
private String email;
private int age;
private LocalDate DOB;
//Setters and Getters and default constructor
}
UserEntity.java
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class UserEntity {
#Id
#SequenceGenerator(sequenceName = "entity_sequence", name="entity_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "post_sequence")
private long entityId;
private char entityType;
private LocalDate createdOn;
private LocalDate modifiedOn;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User createdBy;
//Setters and Getters and default constructor
}
Post.java
#Entity
public class Post extends UserEntity{
private String postHeading;
private String postBody;
//Setters and Getters and default constructor
}
Comment.java
#Entity
public class Comment extends UserEntity{
private String comment;
#ManyToOne
#JoinColumn(name="parent_entity_id", referencedColumnName = "entityId")
private UserEntity parentEntity;
//Setters and Getters and default constructor
}
and their repositories
#NoRepositoryBean
public interface UserEntityBaseRepos<T extends UserEntity> extends JpaRepository<T, Long>{
Optional<List<T>> findByCreatedBy_Id(Long user_id);
Optional<List<T>> findByEntityId(Long entity_id);
}
#Repository
public interface UserRespository extends JpaRepository<User, Long> {
Optional<User> findUserByEmail(String email);
Optional<User> findUserByName(String name);
}
#Repository
public interface PostRepos extends UserEntityBaseRepos<Post>, JpaRepository<Post, Long> {
}
#Repository
public interface CommentRepos extends UserEntityBaseRepos<Comment>, JpaRepository<Comment, Long> {
}
Json for postComment service
{
"entityType" : "C",
"createdOn" : "2020-02-05",
"createdBy" : {
"id" : 1
},
"comment": "I am the comment",
"parentEntity" : {
"entityId" : 1
}
}
//User with id = 1 and UserEntity(Post) with entityId = 1 available in database.
Here createdBy.id (user id) is getting updated in the user_entity table, but userEntity.entityId is not getting updated in the comment table
You have very complex entity relationships, it seems to me...
Anyway, I found that you added a generator property to the UserEntity entity with a post_sequence value, but I can't find any relationship to the Post entity in your database. This is probably the reason of the breakdown. You have to connect UserEntity to Post as shown on your diagram or change the generator value.
I was able to solve the problem. The issue was in the following piece of code in Comment concrete class
#ManyToOne
#JoinColumn(name="parent_entity_id", referencedColumnName = "entityId")
private UserEntity parentEntity;
and this Json input
"parentEntity" : {
"entityId" : 1
}
It seems the parentEntity in json input was not being parsed. This was solved on placing JsonProperty("parentEntity") above parentEntity in the Json input was being parsed correctly.
However there was another issue. The parentEntity was not being deserialized to UserEntity as UserEntity is an abstract class. I had to use JacksonPolymorphicDeserialization by introducing a new field parentType("P" for post, "C" for comment) along with some Annotations like below to deserialize parentEntity to corresponding concrete class object.
public class Comment extends UserEntity{
private String comment;
#Transient
#JsonProperty("parentType")
private char parentType;
#ManyToOne
#JoinColumn(name="parent_entity_id", referencedColumnName = "entity_id", foreignKey = #ForeignKey(value=ConstraintMode.CONSTRAINT))
#JsonProperty("parentEntity")
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME , property = "parentType", include = JsonTypeInfo.As.EXTERNAL_PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = Comment.class, name = "C"),
#JsonSubTypes.Type(value = Post.class, name = "P")
})
private UserEntity parentEntity;
reference - Jackson Polymorphic Deserialization via field. I am not really sure how this works. Will try to make sense of it and update the answer.
If anyone knows a better way to deserialize json, do mention it in the comments or as a new answer.

Spring Security UserDetails and username

In my current implementation I have a User entity that implements org.springframework.security.core.userdetails.UserDetails interface.
#Entity
#Table(name = "users")
public class User extends BaseEntity implements UserDetails {
private static final long serialVersionUID = 8884184875433252086L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String username;
private String password;
....
During the OAuth2 authorization I manually create a new User object, populate its fields and store in my database.
According to the UserDetails contract - UserDetails.getUsername() method can't return null but I have no values retrieved from Social Networks that can be used as username.
What value in this case should be returned in the User.getUsername() method ?
Is it okay to return something like this ?
#Override
public String getUsername() {
return String.valueOf(id);
}
If you need to save this entity before you have a valid value for the name then I think that's a problem with the design. Having said that User.getUsername() is mainly used for display purposes, so I doubt it matters what the actual value is, as long as it can be matched to something in an authentication.

Spring Boot + JPA + Lazy bi-directional collection testing

I'm new to JPA and Spring Data and I've faced problem with testing my application. I have two entities in my Spring boot application:
#Getter
#Setter
#Entity
public class User extends SomeBasicEntity {
#NotNull
#Column(unique = true)
private String login;
#NotNull
private String password;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL)
#JsonManagedReference
private List<Role> roles;
}
#Getter
#Setter
#Entity
#Table(name = "ROLE")
public class Role extends SomeBasicEntity {
#NotNull
private String name;
#ManyToOne
#NotNull
#JsonBackReference
private User user;
}
I have implemented dedicated JPA repositories for them (as JpaRepository).
I have also implemented transactional facade for the management:
#Service
#Transactional
public class Facade {
#Autowired
private UserRepository userRepository;
#Autowired
private RoleRepository roleRepository;
public User addNewUser(User user) {
return userRepository.save(user);
}
public Role addNewRole(Role role, User user) {
role.setUser(user);
return roleRepository.save(role);
}
public User findUserByLogin(String login) {
User user = userRepository.findByLogin(login);
if (user != null) {
return user;
} else {
throw new FacadeRuntimeException("User " + login + " does not exists");
}
}
}
Finally, I've created RestController for using the facade (I've hardcoded login name value just for test purposes):
#RestController
#RequestMapping(value = "/users")
public class UserController {
#Autowired
private Facade facade;
#RequestMapping(value = "/login", method = RequestMethod.GET)
public User getUserByLogin() {
User user = facade.findUserByLogin("jahu");
return user;
}
}
And now when I run the application, use facade methods to create user and role (code is irrelevant so I dont paste it here) and check REST response on localhost/users/login there is properly serialized User object WITH list of roles (with single role I've created actually).
Question 1: Why roles are present in Json object whereas I didn't explictly call getRoles on the user object? With lazy fetch type I believe collection should be retrived only when getter is called (maybe JSON serializer is calling the method?)
Nevertheless, I have second problem as I want to test my facade with jUnit (on h2 db), however in test method the roles on User object are always null (even if I explicltly call getter):
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = FacadeApplication.class)
#Transactional
public class FacadeTest {
private static final String USER_LOGIN = "jahu";
private static final String ROLE_NAME = "role name";
#Autowired
Facade sut;
#Before
public void setUp() throws Exception {
User user = new User();
user.setLogin(USER_LOGIN);
user.setPassword("jahu");
user = sut.addNewUser(user);
Role role = new Role();
role.setName(ROLE_NAME);
sut.addNewRole(role, user);
}
#Test
public void shouldFindUserRoles() {
User user = sut.findUserByLogin(USER_LOGIN);
assertThat(user).isNotNull();
assertThat(user.getLogin()).isEqualTo(USER_LOGIN);
List<Role> roles = user.getRoles(); // HERE I call the getter
assertThat(roles).isNotNull().isNotEmpty();
}
}
Question 2: Why can I not access roles in test context even though I am using the same method as in RestController (where roles are always obtained)? Without it I am not able to fully test my app and it concerns me very much.
Note: entity names are just for description of the problem, so please don't suggest that I should remodel my entities in order to assign Role to User.

Resources