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

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

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

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Introduction to Spring Data JPA, Part 3: Unidirectional 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

  • Agile and Quality Engineering: A Holistic Perspective
  • Apache Doris vs Elasticsearch: An In-Depth Comparative Analysis
  • A Guide to Developing Large Language Models Part 1: Pretraining
  • Stateless vs Stateful Stream Processing With Kafka Streams and Apache Flink
  1. DZone
  2. Data Engineering
  3. Databases
  4. Introduction to Spring Data JPA — Part 4 Bidirectional One-to-Many Relations

Introduction to Spring Data JPA — Part 4 Bidirectional One-to-Many Relations

In this article, explore a continued introduction to Spring Data JPA and look at bidirectional one-to-many relations.

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

Join the DZone community and get the full member experience.

Join For Free

In this article, we will discuss the following:

  • Bi-directional one-to-many relation
  •  @Join  column annotation
  •  @JsonIgnore  annotation
  •  @Transient  annotation

Let's look at the same use case of User and Role.

User and role


You can see that User is referring to the Role entity and the Role entity is referring to the User entity, so we call it bidirectional. Navigation is possible from both User and the Role.


Let's see how Hibernate creates the table for you by default.
Hibernate default table


You can see a foreign key reference to the ID of the t_role table in the t_user table. How does it translate to code? Please see below.


User Entity

Java
 




x
55


 
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
    @ManyToOne
15
    private Role role;
16
 
          
17
    public Long getId() {
18
        return id;
19
    }
20
    public void setId(Long id) {
21
        this.id = id;
22
    }
23
    public String getFirstName() {
24
        return firstName;
25
    }
26
    public void setFirstName(String firstName) {
27
        this.firstName = firstName;
28
    }
29
    public String getLastName() {
30
        return lastName;
31
    }
32
    public void setLastName(String lastName) {
33
        this.lastName = lastName;
34
    }
35
    public String getMobile() {
36
        return mobile;
37
    }
38
    public void setMobile(String mobile) {
39
        this.mobile = mobile;
40
    }
41
    public String getEmail() {
42
        return email;
43
    }
44
    public void setEmail(String email) {
45
        this.email = email;
46
    }
47
 
          
48
    public Role getRole() {
49
        return role;
50
    }
51
 
          
52
    public void setRole(Role role) {
53
        this.role = role;
54
    }
55
}



Java
 




x


 
1
@ManyToOne
2
private Role role;



The above is the change that has been brought in. Now, there are definitions on both sides of the relation.

Role Entity

Java
 




xxxxxxxxxx
1
40


 
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
}



RoleRepository

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
}



UserRepository

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
}



RoleService

We are using the same code as in the previous article (Part 2)

Java
 




xxxxxxxxxx
1
54


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



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
}



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



Now let's run the application.

Open Postman and send in a Post request create two users with the Role as ADMIN. Please find the JSON Object below.

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 should get the following error:

 save the transient instance before flushing 

Please find source code at https://github.com/gudpick/jpa-demo/tree/one-to-many-bidirectional-starter

Let us change the cascade type as following.

Java
 




xxxxxxxxxx
1


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



Please find source code at https://github.com/gudpick/jpa-demo/tree/add-cascade-type-all

Now, when you run the application and send in the request, you will see that both the tables are populated, but unfortunately, you find that the role_id is not getting updated. Now let's explicitly mention role_id using the @JoinColumn annotation. The @JoinColumn annotation helps us specify the column we'll use for joining an entity association or element collection.

Java
 




xxxxxxxxxx
1


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



Now run the application and send the request. You will see all the data gets populated correctly.

Please find source code at https://github.com/gudpick/jpa-demo/tree/one-to-many-bidirectional-starter

The READ operation:

Let us use @GetMapping to read the roles and users from the database.

  • Get User by Id
  • Read all Users
  • Get Role by Id 
  • Get all Roles

Now let's update our controllers.

UserController

Java
 




xxxxxxxxxx
1
29


 
1
package com.notyfyd.controller;
2
 
          
3
import com.notyfyd.entity.User;
4
import com.notyfyd.repository.UserRepository;
5
import org.springframework.web.bind.annotation.GetMapping;
6
import org.springframework.web.bind.annotation.PathVariable;
7
import org.springframework.web.bind.annotation.RestController;
8
 
          
9
import java.util.List;
10
 
          
11
@RestController
12
public class UserController {
13
    
14
    private UserRepository userRepository;
15
 
          
16
    public UserController(UserRepository userRepository) {
17
        this.userRepository = userRepository;
18
    }
19
    
20
    @GetMapping("/user/details/{id}")
21
    public User getUser(@PathVariable Long id) {
22
        if(userRepository.findById(id).isPresent())
23
            return userRepository.findById(id).get();
24
        else return  null;
25
    }
26
    @GetMapping("/user/all")
27
    public List<User> getUsers() {
28
        return userRepository.findAll();
29
    }


We are using the @GetMapping with the getUser() method which will fetch the user by it's Id, getUsers() method will get all the users. It uses the findAll method to accomplish the task. 

RoleController

Java
 




x


 
1
package com.notyfyd.controller;
2
 
          
3
import com.notyfyd.entity.Role;
4
import com.notyfyd.repository.RoleRepository;
5
import com.notyfyd.service.RoleService;
6
import org.springframework.http.ResponseEntity;
7
import org.springframework.web.bind.annotation.*;
8
 
          
9
import java.util.List;
10
 
          
11
 
          
12
@RestController
13
public class RoleController {
14
 
          
15
    private RoleService roleService;
16
    private RoleRepository roleRepository;
17
 
          
18
    public RoleController(RoleService roleService, RoleRepository roleRepository) {
19
        this.roleService = roleService;
20
        this.roleRepository = roleRepository;
21
    }
22
    
23
    @PostMapping("/role/create")
24
    public ResponseEntity<Object> createRole(@RequestBody Role role) {
25
        return  roleService.addRole(role);
26
    }
27
    @DeleteMapping("/role/delete/{id}")
28
    public ResponseEntity<Object> deleteRole(@PathVariable Long id) {
29
        return roleService.deleteRole(id);
30
    }
31
    @GetMapping("/role/details/{id}")
32
    public Role getRole(@PathVariable Long id) {
33
        if(roleRepository.findById(id).isPresent())
34
            return roleRepository.findById(id).get();
35
        else return null;
36
    }
37
    @GetMapping("/role/all")
38
    public List<Role> getRoles() {
39
        return roleRepository.findAll();
40
    }
41
}


Now let us run the application.

Create the Role with users as before using Postman.

Now let us try the Get requests.

Now you will get the stack overflow error as below


 java.lang.StackOverflowError: null 

Please find source code at https://github.com/gudpick/jpa-demo/tree/controllers-updated

This is because of circular reference. The user picks up the role and the role picks up the user. We can overcome this in many ways. We'll use @JsonIgnore to overcome the error. @JsonIgnore ignores a field both when reading JSON into Java objects and when writing Java objects into JSON.

So all we have to do is to add this annotation as below in the User entity. Using @JsonIgnore in the role entity will create errors while reading in the data. I encourage you to try it.

Java
 




xxxxxxxxxx
1


 
1
@JsonIgnore
2
@ManyToOne
3
private Role role;



Please find source code at https://github.com/gudpick/jpa-demo/tree/json-ignore

Now run the application and send the get request. You will see that everything is working. But let's not be satisfied with just this. When you see the JSON Object returned by the get user, you will find the following:

JSON
 




xxxxxxxxxx
1


 
1
{
2
    "id": 1,
3
    "firstName": "hello",
4
    "lastName": "world",
5
    "mobile": "9876435234",
6
    "email": "hello@mail.com"
7
}



Don't you think you deserve at least the name of the role? Now we will introduce the @Transient annotation. @Transient annotation in JPA or Hibernate is used to indicate that a field is not to be persisted or ignore fields to be saved in the database. Let's use this effectively. Define a field roleName and write getters and setters as below in the User entity. Annotate the roleName with @Transient.

Java
 




xxxxxxxxxx
1


 
1
@Transient
2
private String roleName;
3
public String getRoleName() {
4
    return getRole().getName();
5
}
6
public void setRoleName(String roleName) {
7
    this.roleName = roleName;
8
}



Now run the application. Send in the post request to create the Role with Users. Now let's send in the get request. You will get the below result:

JSON
 




xxxxxxxxxx
1


 
1
{
2
    "id": 1,
3
    "firstName": "hello",
4
    "lastName": "world",
5
    "mobile": "9876435234",
6
    "email": "hello@mail.com",
7
    "roleName": "ADMIN"
8
}



Please find the source code at https://github.com/gudpick/jpa-demo/tree/transient-annotation

Please find the video tutorials at:


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

Opinions expressed by DZone contributors are their own.

Related

  • Introduction to Spring Data JPA, Part 3: Unidirectional 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!