{{announcement.body}}
{{announcement.title}}

Introduction to Spring Data JPA, Part 3: Unidirectional One to Many Relations

DZone 's Guide to

Introduction to Spring Data JPA, Part 3: Unidirectional One to Many Relations

In this tutorial, we take a look at unidirectional one-to-many realations and how to enable the removal of user records without removing role records.

· Java Zone ·
Free Resource

We will look into unidirectional one-to-many relations. We will use Cascade Types, a constructor-based dependency injection by removing the @Autowired annotation. We will accomplish this with a small implementation where we will handle the DELETE operation, understand  @PathVariable annotation, use Orphan removal, and the @Transaction  annotation.

Let us consider an organization. Here there will be many employees, each taking up different tasks. Let us call the employees users and classify their tasks into roles they play. Some may be performing managerial task, so we'll give them the role MANAGER. Some may be performing system administration tasks, so we'll give them the role ADMIN.  

So let us look at in this manner. There might be many MANAGER and many ADMIN roles in the same organization. Therefore, you can say they each have many users.

This relation can be called a One to Many relation. Let's look at the conditions.

  • Every user will have a role.
  • There can be many users with the same role.
  • When you add an user you should associate a role with the user.
  • When you delete a user the corresponding role need not be deleted since there might be other users associated with the role.
  • When you delete a role all users associated with the role should be deleted.

Modeling the Entities

Modeling the entities

Modeling the entities


When we look at the definition of the entity we can see that the role entity has a list of users and in the user entity, you do not have any definition of role. That is the reason why we call this relation unidirectional. If we had a definition of role in the user entity, we will call this relation bidirectional.

Now let's talk only about unidirectional one-to-many relations. Once you have an entity definition like this, Hibernate by default will create the tables that look like these below.

Hibernate tables

Hibernate tables


Let us go with the default creation and write code for both the role and user entities.

User Entity

Java
 




x
45


 
1
package com.notyfyd.entity;
2
import javax.persistence.*;
3
@Entity
4
@Table(name = "t_user")
5
public class User {
6
    @Id
7
    @GeneratedValue(strategy = GenerationType.IDENTITY)
8
    private Long id;
9
    private String firstName;
10
    private String lastName;
11
    private String mobile;
12
    @Column(unique = true)
13
    private String email;
14
 
          
15
    public Long getId() {
16
        return id;
17
    }
18
    public void setId(Long id) {
19
        this.id = id;
20
    }
21
    public String getFirstName() {
22
        return firstName;
23
    }
24
    public void setFirstName(String firstName) {
25
        this.firstName = firstName;
26
    }
27
    public String getLastName() {
28
        return lastName;
29
    }
30
    public void setLastName(String lastName) {
31
        this.lastName = lastName;
32
    }
33
    public String getMobile() {
34
        return mobile;
35
    }
36
    public void setMobile(String mobile) {
37
        this.mobile = mobile;
38
    }
39
    public String getEmail() {
40
        return email;
41
    }
42
    public void setEmail(String email) {
43
        this.email = email;
44
    }
45
 
          


 

Role Entity

Java
 




x



1
package com.notyfyd.entity;
2
import javax.persistence.*;
3
import java.util.List;
4
 
          
5
@Entity
6
@Table(name = "t_role")
7
public class Role {
8
    @Id
9
    @GeneratedValue(strategy = GenerationType.IDENTITY)
10
    private Long id;
11
    private String name;
12
    private String description;
13
 
          
14
    @OneToMany(targetEntity = User.class)
15
    private List<User> users;
16
    public Long getId() {
17
        return this.id;
18
    }
19
    public void setId(Long id) {
20
        this.id = id;
21
    }
22
    public String getName() {
23
        return this.name;
24
    }
25
    public void setName(String name) {
26
        this.name = name;
27
    }
28
    public String getDescription() {
29
        return this.description;
30
    }
31
    public void setDescription(String description) {
32
        this.description = description;
33
    }
34
    public List<User> getUsers() {
35
        return users;
36
    }
37
    public void setUsers(List<User> users) {
38
        this.users = users;
39
    }
40
}



What we notice here is the definition of users in the role entity.

@OneToMany(targetEntity = User.class)

    private List<User> users; 

whereas we do not find any definition for role in the user entity.

Role Repository

Java
 




xxxxxxxxxx
1
12


 
1
package com.notyfyd.repository;
2
 
          
3
import com.notyfyd.entity.Role;
4
import org.springframework.data.jpa.repository.JpaRepository;
5
import org.springframework.stereotype.Repository;
6
 
          
7
import java.util.Optional;
8
 
          
9
@Repository
10
public interface RoleRepository extends JpaRepository<Role, Long> {
11
    Optional<Role> findByName(String name);
12
}



User Repository

Java
 




xxxxxxxxxx
1
12


 
1
package com.notyfyd.repository;
2
 
          
3
import com.notyfyd.entity.User;
4
import org.springframework.data.jpa.repository.JpaRepository;
5
import org.springframework.stereotype.Repository;
6
 
          
7
import java.util.Optional;
8
 
          
9
@Repository
10
public interface UserRepository extends JpaRepository<User, Long> {
11
    Optional<User> findByEmail(String email);
12
}



Role Service

Java
 




xxxxxxxxxx
1
52


 
1
package com.notyfyd.service;
2
 
          
3
import com.notyfyd.entity.Role;
4
import com.notyfyd.repository.RoleRepository;
5
import com.notyfyd.repository.UserRepository;
6
import org.springframework.http.ResponseEntity;
7
import org.springframework.stereotype.Service;
8
 
          
9
@Service
10
public class RoleService {
11
 
          
12
    private RoleRepository roleRepository;
13
 
          
14
    private UserRepository userRepository;
15
 
          
16
    public RoleService(RoleRepository roleRepository, UserRepository userRepository) {
17
        this.roleRepository = roleRepository;
18
        this.userRepository = userRepository;
19
    }
20
 
          
21
    /**
22
     * Create a new role along with users
23
     */
24
 
          
25
    public ResponseEntity<Object> addRole(Role role) {
26
 
          
27
        Role newRole = new Role();
28
        newRole.setName(role.getName());
29
        newRole.setDescription(role.getDescription());
30
 
          
31
 
          
32
        newRole.setUsers(role.getUsers());
33
        Role savedRole = roleRepository.save(newRole);
34
        if (roleRepository.findById(savedRole.getId()).isPresent()) {
35
            return ResponseEntity.accepted().body("Successfully Created Role and Users");
36
        } else
37
            return ResponseEntity.unprocessableEntity().body("Failed to Create specified Role");
38
    }
39
 
          
40
    /**
41
     * Delete a specified role given the id
42
     */
43
    public ResponseEntity<Object> deleteRole(Long id) {
44
        if (roleRepository.findById(id).isPresent()) {
45
            roleRepository.deleteById(id);
46
            if (roleRepository.findById(id).isPresent()) {
47
                return ResponseEntity.unprocessableEntity().body("Failed to delete the specified record");
48
            } else return ResponseEntity.ok().body("Successfully deleted specified record");
49
        } else
50
            return ResponseEntity.unprocessableEntity().body("No Records Found");
51
    }
52
}


Now, in the  addRole method, which takes the Role type, we will create a new Role as  newRole. We will set the name, description, and list of users to the newRole from role. The method is saved and the role returned is assigned to savedRole. The existence of the saveRole object is checked and returns success if present and return failure if it is not present.

The deleteRole method takes the Long ID. The  findById method is used to check whether the role is present. If it is present it is deleted and returns "Successfully Deleted" after cross checking once more with the finder method; otherwise, it returns "No Records found."

RoleController 

Java
 




xxxxxxxxxx
1
26


 
1
package com.notyfyd.controller;
2
 
          
3
import com.notyfyd.entity.Role;
4
import com.notyfyd.service.RoleService;
5
import org.springframework.http.ResponseEntity;
6
import org.springframework.web.bind.annotation.*;
7
 
          
8
 
          
9
@RestController
10
public class RoleController {
11
 
          
12
    private RoleService roleService;
13
 
          
14
    public RoleController(RoleService roleService) {
15
        this.roleService = roleService;
16
    }
17
 
          
18
    @PostMapping("/role/create")
19
    public ResponseEntity<Object> createRole(@RequestBody Role role) {
20
        return  roleService.addRole(role);
21
    }
22
    @DeleteMapping("/role/delete/{id}")
23
    public ResponseEntity<Object> deleteRole(@PathVariable Long id) {
24
        return roleService.deleteRole(id);
25
    }
26
}


For the DELETE operation we are using the @DeleteMapping annotation and in the URI we are using flower brackets for the ID to be passed in. The @PathVariable annotation is used for the ID passed in the URI. It call the deleteRole method in the UserService. The CREATE operation uses the  @PostMapping as before and calls the addRole method in the UserService. Now you can see that I am not using the  @Autowired annotation; instead I am using a constructor. You can call it a constructor-based dependency injection.

application.properties

Properties files
 




xxxxxxxxxx
1


 
1
server.port=2003
2
spring.datasource.driver-class-name= org.postgresql.Driver
3
spring.datasource.url= jdbc:postgresql://192.168.64.6:30432/jpa-test
4
spring.datasource.username = postgres
5
spring.datasource.password = root
6
spring.jpa.show-sql=true
7
spring.jpa.hibernate.ddl-auto=create


Let us run the application and open Postman.

JSON Object

JSON
 




xxxxxxxxxx
1
18


 
1
{
2
    "name": "ADMIN",
3
    "description": "Administrator",
4
    "users": [
5
        {
6
            "firstName": "hello",
7
            "lastName":"world",
8
            "mobile": "9876435234",
9
            "email":"hello@mail.com"
10
        },
11
        {
12
            "firstName": "Hello Good Morning",
13
            "lastName":"world",
14
            "mobile": "9876435234",
15
            "email":"world@mail.com"
16
        }
17
        ]
18
}


You can see the way I have written the users. I am using the square brackets("[]") since it is an array of object and each user object is enclosed in flower brackets ("{}") separated by a comma.

Having done all this, when you send the request, you will get an error.

Plain Text
 




xxxxxxxxxx
1


 
1
Object references an unsaved transient instance - save the transient instance before flushing: com.notyfyd.entity.User 


This is because the user entity is not saved in the addRole method before we save the role. Let us look at two solutions to correct it. 

Please find the source code here.

Error Solutions

1. Let us write few lines of code to save the user before the role entity is saved. I will make changes to the addRole method.

Java
 




xxxxxxxxxx
1
21


 
1
public ResponseEntity<Object> addRole(Role role) {
2
 
          
3
        Role newRole = new Role();
4
        newRole.setName(role.getName());
5
        newRole.setDescription(role.getDescription());
6
        
7
        for(int i=0; i< role.getUsers().size(); i++){
8
            User savedUser = userRepository.save(role.getUsers().get(i));
9
            if(!userRepository.findById(savedUser.getId()).isPresent())
10
                return ResponseEntity.unprocessableEntity().body("Failed creating user and roles");
11
        }
12
 
          
13
 
          
14
        newRole.setUsers(role.getUsers());
15
        Role savedRole = roleRepository.save(newRole);
16
        if (roleRepository.findById(savedRole.getId()).isPresent()) {
17
            return ResponseEntity.accepted().body("Successfully Created Role and Users");
18
        } else
19
            return ResponseEntity.unprocessableEntity().body("Failed to Create specified Role");
20
    }
21
 
          


Java
 




xxxxxxxxxx
1


1
   for(int i=0; i< role.getUsers().size(); i++){ 
2
            User savedUser = userRepository.save(role.getUsers().get(i)); 
3
            if(!userRepository.findById(savedUser.getId()).isPresent())  
4
               return ResponseEntity.unprocessableEntity().body("Failed creating user and roles");   
5
      } 


This is code I have changed in the addRole method. Every user is fetched in the for loop. It is saved and if it fails, the application returns an error.

Now let us run the application. You will get a message saying "Successfully Created Role and Users."

If you check the database you can see both the t_user and t_role tables are populated correctly and in the mapping table you will find as below.

Populated table

Populated table


You can see that the role_id and user_id are mapped.

Please find the full source code here.

Now what will happen if the addRole method throws an exception after saving the user but before saving the role?

Let us make it throw an exception.

   if(true) throw new RuntimeException(); 

Add this line of code after the For loop. Once you run the application and send the request, you should get an exception:

 java.lang.RuntimeException: null 

But if you check the database you will find both the users are created, but no role is created and the mapping table will be empty.

The solution for this is to use the @Transactional annotation before the method.

Java
 




xxxxxxxxxx
1


1
import org.springframework.transaction.annotation.Transactional;


Java
 




xxxxxxxxxx
1


 
1
@Transactional
2
public ResponseEntity<Object> addRole(Role role)


The usage of this annotation  @Transactional causes the entire transaction to roll back if anything happens in between. Now if you run the application you will find that the users are not inserted and the the roles are not created. 

Please find the full source code here.

The second way of rectifying this is to use cascading.

  • Cascading: When we perform an action on the target entity, the same action will be applied to associated entities.
  • CascadeType.PERSIST: Propagates the persist operation from a parent to child entity. When the role is saved the user will also be saved along with it.
  • CascadeType.MERGE: If the source entity is merged, the merge is cascaded to the target of the association.
  • CascadeType.REFRESH: If the source entity is refreshed, the refresh is cascaded to the target of the association.
  • CascadeType.REMOVE: Cascade type remove removes all related entities when the source entity is deleted.
  • CascadeType.DETACH: Detached entity objects are objects in a special state in which they are not managed by any EntityManager but still represent objects in the database. Changes to detached entity objects are not stored in the database unless modified detached objects are merged back.
  • CascadeType.ALL: Propagates all operations from parent to child entity.


Let us use the Cascade - CascadeType.PERSIST 

Java
 




xxxxxxxxxx
1


 
1
@Id
2
@GeneratedValue(strategy = GenerationType.IDENTITY)
3
private Long id;
4
private String name;
5
private String description;
6
 
          
7
@OneToMany(targetEntity = User.class, cascade = CascadeType.PERSIST)
8
private List<User> users;


Now I am removing the code written to save the User in the  addRole  method. The  addRole  method will be as follows.

Java
 




xxxxxxxxxx
1
14


 
1
 @Transactional
2
    public ResponseEntity<Object> addRole(Role role) {
3
 
          
4
        Role newRole = new Role();
5
        newRole.setName(role.getName());
6
        newRole.setDescription(role.getDescription());
7
 
          
8
        newRole.setUsers(role.getUsers());
9
        Role savedRole = roleRepository.save(newRole);
10
        if (roleRepository.findById(savedRole.getId()).isPresent()) {
11
            return ResponseEntity.accepted().body("Successfully Created Role and Users");
12
        } else
13
            return ResponseEntity.unprocessableEntity().body("Failed to Create specified Role");
14
    }


Now let us run the application and send the request in Postman. You will find the users and roles are successfully created.

Delete

Now time to run the delete request.

Let us go to Postman and run localhost:2003/role/delete/1. Here "1" is the ID of the role to be deleted.

Deleting the record

Deleting the record


Now when we send the request, you will receive the message that it has successfully deleted the record. When you check the database the role is deleted. The relations in the mapping tables are removed but you will find the two users still in the database. This is not what you want. What you want is when the role is removed all users associated with the role also should be removed. 

Now there is something called  orphanRemoval which is false by default. You can set it to true and send the request again. Orphan Removal causes the child entity to be removed when it's no longer referenced from the parent entity.

Java
 




xxxxxxxxxx
1


 
1
@OneToMany(targetEntity = User.class, cascade = CascadeType.PERSIST, orphanRemoval = true)
2
private List<User> users;


You will now see that all roles and user are deleted.

Please find the source code here.

Now another way of doing it is to set CascadeType to ALL.

Java
 




xxxxxxxxxx
1


 
1
@OneToMany(targetEntity = User.class, cascade = CascadeType.ALL)
2
private List<User> users;


Now send in the delete request you will see that all the users and roles are deleted.

Please find the source code here.

Please find the video tutorials below:


Topics:
crud operations ,java ,jpa ,microservices ,one to many relations ,one to one ,spring boot ,spring data jpa ,spring security ,unidirectional

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}