Spring Boot and Hibernate One-to-One CRUD REST API Tutorial

In this tutorial, we will demonstrate how to set up a one-to-one relationship between User and UserProfile entities using Spring Boot and Hibernate, and expose CRUD operations through a REST API.

Prerequisites

  1. Java Development Kit (JDK) 11 or higher: Ensure JDK is installed and configured on your system.
  2. Integrated Development Environment (IDE): IntelliJ IDEA, Eclipse, or any other IDE.
  3. Maven: Ensure Maven is installed and configured on your system.

Step 1: Create a Spring Boot Project

  1. Open your IDE and create a new Spring Boot project.
  2. Use Spring Initializr or manually create the pom.xml file to include Spring Boot and other required dependencies.
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>spring-boot-one-to-one-example</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.0</version>
        <relativePath/>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Explanation

  • spring-boot-starter-data-jpa: Includes Spring Data JPA with Hibernate.
  • spring-boot-starter-web: Includes Spring MVC for building web applications.
  • h2: An in-memory database for testing purposes.

Step 2: Configure the Application Properties

Configure the application.properties file to set up the H2 database.

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

Step 3: Create the User Entity Class

Create a package named com.example.entity and a class named User.

package com.example.entity;

import jakarta.persistence.*;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String email;

    @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
    private UserProfile userProfile;

    public User() {}

    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public UserProfile getUserProfile() {
        return userProfile;
    }

    public void setUserProfile(UserProfile userProfile) {
        if (userProfile == null) {
            if (this.userProfile != null) {
                this.userProfile.setUser(null);
            }
        } else {
            userProfile.setUser(this);
        }
        this.userProfile = userProfile;
    }

    @Override
    public String toString() {
        return "User{id=" + id + ", username='" + username + '\'' + ", email='" + email + '\'' + '}';
    }
}

Explanation

  • @Entity: Marks the class as an entity.
  • @Id: Marks the field as the primary key.
  • @GeneratedValue: Specifies the strategy for generating values for the primary key.
  • @OneToOne: Defines a one-to-one relationship with the UserProfile entity.
  • mappedBy: Specifies the field in the UserProfile entity that owns the relationship.
  • cascade: Specifies the cascade operations.
  • fetch: Specifies the fetch type (lazy loading).
  • optional: Indicates whether the relationship is optional.

Step 4: Create the UserProfile Entity Class

Create a class named UserProfile in the same package.

package com.example.entity;

import jakarta.persistence.*;

@Entity
public class UserProfile {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String address;
    private String phoneNumber;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    public UserProfile() {}

    public UserProfile(String address, String phoneNumber) {
        this.address = address;
        this.phoneNumber = phoneNumber;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public String toString() {
        return "UserProfile{id=" + id + ", address='" + address + '\'' + ", phoneNumber='" + phoneNumber + '\'' + '}';
    }
}

Explanation

  • @Entity: Marks the class as an entity.
  • @Id: Marks the field as the primary key.
  • @GeneratedValue: Specifies the strategy for generating values for the primary key.
  • @OneToOne: Defines a one-to-one relationship with the User entity.
  • @JoinColumn: Specifies the foreign key column.

Step 5: Create Repository Interfaces

Create a package named com.example.repository and interfaces for User and UserProfile.

package com.example.repository;

import com.example.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {}
package com.example.repository;

import com.example.entity.UserProfile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserProfileRepository extends JpaRepository<UserProfile, Long> {}

Step 6: Create Service Classes

Create a package named com.example.service and service classes for User and UserProfile.

package com.example.service;

import com.example.entity.User;
import com.example.entity.UserProfile;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public User save(User user) {
        return userRepository.save(user);
    }

    public List<User> findAll() {
        return userRepository.findAll();
    }

    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    public void deleteById(Long id) {
        userRepository.deleteById(id);
    }
}
package com.example.service;

import com.example.entity.UserProfile;
import com.example.repository.UserProfileRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserProfileService {
    @Autowired
    private UserProfileRepository userProfileRepository;

    public UserProfile save(UserProfile userProfile) {
        return userProfileRepository.save(userProfile);
    }

    public List<UserProfile> findAll() {
        return userProfileRepository.findAll();
    }

    public UserProfile findById(Long id) {
        return userProfileRepository.findById(id).orElse(null);
    }

    public void deleteById(Long id) {
        userProfileRepository.deleteById(id);
    }
}

Step 7: Create Controller Classes

Create a package named com.example.controller and controller classes for User and UserProfile.

package com.example

.controller;

import com.example.entity.User;
import com.example.entity.UserProfile;
import com.example.service.UserService;
import com.example.service.UserProfileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserService userService;

    @Autowired
    private UserProfileService userProfileService;

    @PostMapping
    public User createUser(@RequestBody User user) {
        if (user.getUserProfile() != null) {
            user.getUserProfile().setUser(user);
        }
        return userService.save(user);
    }

    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }

    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.findById(id);
    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteById(id);
    }

    @PostMapping("/{userId}/profile")
    public User addUserProfile(@PathVariable Long userId, @RequestBody UserProfile userProfile) {
        User user = userService.findById(userId);
        if (user != null) {
            userProfile.setUser(user);
            user.setUserProfile(userProfile);
            userService.save(user);
            return user;
        }
        return null;
    }

    @DeleteMapping("/{userId}/profile")
    public User removeUserProfile(@PathVariable Long userId) {
        User user = userService.findById(userId);
        if (user != null && user.getUserProfile() != null) {
            userProfileService.deleteById(user.getUserProfile().getId());
            user.setUserProfile(null);
            return userService.save(user);
        }
        return null;
    }
}
package com.example.controller;

import com.example.entity.UserProfile;
import com.example.service.UserProfileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/profiles")
public class UserProfileController {
    @Autowired
    private UserProfileService userProfileService;

    @PostMapping
    public UserProfile createUserProfile(@RequestBody UserProfile userProfile) {
        return userProfileService.save(userProfile);
    }

    @GetMapping
    public List<UserProfile> getAllUserProfiles() {
        return userProfileService.findAll();
    }

    @GetMapping("/{id}")
    public UserProfile getUserProfileById(@PathVariable Long id) {
        return userProfileService.findById(id);
    }

    @DeleteMapping("/{id}")
    public void deleteUserProfile(@PathVariable Long id) {
        userProfileService.deleteById(id);
    }
}

Step 8: Create Main Application Class

Create a package named com.example and a class named SpringBootOneToOneExampleApplication.

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootOneToOneExampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootOneToOneExampleApplication.class, args);
    }
}

Step 9: Run the Application

  1. Run the SpringBootOneToOneExampleApplication class.
  2. Use an API client (e.g., Postman) or a web browser to test the endpoints.

Testing the Endpoints

  1. Create a User:

    • URL: POST /users
    • Body:
      {
        "username": "john_doe",
        "email": "john@example.com",
        "userProfile": {
          "address": "123 Main St",
          "phoneNumber": "123-456-7890"
        }
      }
      
  2. Create User Profile:

    • URL: POST /profiles
    • Body:
      {
        "address": "123 Main St",
        "phoneNumber": "123-456-7890"
      }
      
  3. Get All Users:

    • URL: GET /users
  4. Get User by ID:

    • URL: GET /users/{id}
  5. Delete User by ID:

    • URL: DELETE /users/{id}
  6. Add User Profile to User:

    • URL: POST /users/{userId}/profile
    • Body:
      {
        "address": "123 Main St",
        "phoneNumber": "123-456-7890"
      }
      
  7. Remove User Profile from User:

    • URL: DELETE /users/{userId}/profile
  8. Get All User Profiles:

    • URL: GET /profiles
  9. Get User Profile by ID:

    • URL: GET /profiles/{id}
  10. Delete User Profile by ID:

  • URL: DELETE /profiles/{id}

Conclusion

You have successfully created an example using Spring Boot and Hibernate to demonstrate a one-to-one relationship between User and UserProfile entities. This tutorial covered setting up a Spring Boot project, configuring Hibernate, creating entity classes with a one-to-one relationship, and performing CRUD operations through RESTful endpoints.

Comments