DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Introduction to Spring Data JPA — Part 4 Bidirectional One-to-Many Relations
  • Testcontainers With Kotlin and Spring Data R2DBC
  • The First Annual Recap From JPA Buddy
  • Manage Hierarchical Data in MongoDB With Spring

Trending

  • Enforcing Architecture With ArchUnit in Java
  • GitHub Copilot's New AI Coding Agent Saves Developers Time – And Requires Their Oversight
  • MCP Servers: The Technical Debt That Is Coming
  • The Future of Java and AI: Coding in 2025
  1. DZone
  2. Data Engineering
  3. Databases
  4. Introduction to Spring Data JPA, Part 3: Unidirectional One to Many Relations

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.

By 
Vinu Sagar user avatar
Vinu Sagar
·
Apr. 14, 20 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
21.4K Views

Join the DZone community and get the full member experience.

Join For Free

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:


Database Relational database Spring Data Java (programming language) application Object (computer science) Annotation Data (computing)

Opinions expressed by DZone contributors are their own.

Related

  • Introduction to Spring Data JPA — Part 4 Bidirectional One-to-Many Relations
  • Testcontainers With Kotlin and Spring Data R2DBC
  • The First Annual Recap From JPA Buddy
  • Manage Hierarchical Data in MongoDB With Spring

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!