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

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

DZone 's Guide to

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.

· Java Zone ·
Free Resource

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:


Topics:
cloud ,jpa ,jwt ,microservices ,spring boot ,spring data jpa ,spring secruity ,spring security oauth ,tutorial

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}