Spring Boot REST API One-to-Many CRUD Operations Example

In this tutorial, we will demonstrate how to set up a one-to-many relationship between Author and Book 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-onetomany-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 Author Entity Class

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

package com.example.entity;

import jakarta.persistence.*;
import java.util.HashSet;
import java.util.Set;

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

    private String name;

    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<Book> books = new HashSet<>();

    public Author() {}

    public Author(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Book> getBooks() {
        return books;
    }

    public void setBooks(Set<Book> books) {
        this.books = books;
    }

    public void addBook(Book book) {
        books.add(book);
        book.setAuthor(this);
    }

    public void removeBook(Book book) {
        books.remove(book);
        book.setAuthor(null);
    }

    @Override
    public String toString() {
        return "Author{id=" + id + ", name='" + name + '\'' + '}';
    }
}

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.
  • @OneToMany: Defines a one-to-many relationship with the Book entity.
  • mappedBy: Specifies the field in the Book entity that owns the relationship.
  • cascade: Specifies the cascade operations.
  • orphanRemoval: Specifies whether to remove orphaned entities.

Step 4: Create the Book Entity Class

Create a class named Book in the same package.

package com.example.entity;

import jakarta.persistence.*;

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

    private String title;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id")
    private Author author;

    public Book() {}

    public Book(String title) {
        this.title = title;
    }

    public Long getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

    @Override
    public String toString() {
        return "Book{id=" + id + ", title='" + title + '\'' + '}';
    }
}

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.
  • @ManyToOne: Defines a many-to-one relationship with the Author entity.
  • @JoinColumn: Specifies the foreign key column.

Step 5: Create Repository Interfaces

Create a package named com.example.repository and interfaces for Author and Book.

package com.example.repository;

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

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

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

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {}

Step 6: Create Service Classes

Create a package named com.example.service and service classes for Author and Book.

package com.example.service;

import com.example.entity.Author;
import com.example.entity.Book;
import com.example.repository.AuthorRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class AuthorService {
    @Autowired
    private AuthorRepository authorRepository;

    public Author save(Author author) {
        return authorRepository.save(author);
    }

    public List<Author> findAll() {
        return authorRepository.findAll();
    }

    public Author findById(Long id) {
        return authorRepository.findById(id).orElse(null);
    }

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

    public Author addBook(Long authorId, Book book) {
        Author author = findById(authorId);
        if (author != null) {
            author.addBook(book);
            return save(author);
        }
        return null;
    }

    public Author removeBook(Long authorId, Long bookId) {
        Author author = findById(authorId);
        if (author != null) {
            Book book = author.getBooks().stream().filter(b -> b.getId().equals(bookId)).findFirst().orElse(null);
            if (book != null) {
                author.removeBook(book);
                return save(author);
            }
        }
        return null;
    }
}
package com.example.service;

import com.example.entity.Book;
import com.example.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookService {
    @Autowired
    private BookRepository bookRepository;

    public Book save(Book book) {
        return bookRepository.save(book);
    }

    public List<Book> findAll() {
        return bookRepository.findAll();
    }

    public Book findById(Long id) {
        return bookRepository.findById(id).orElse(null);
    }

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

Step 7: Create Controller Classes

Create a package named com.example.controller and controller classes for Author and Book.

package com.example.controller;

import com.example.entity.Author;
import com.example.entity.Book;
import com.example.service.AuthorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/authors")
public class AuthorController {
    @Autowired
    private AuthorService authorService;

    @PostMapping
    public Author createAuthor(@RequestBody Author author) {
        return authorService.save(author);
    }

    @GetMapping
    public List<Author> getAllAuthors() {
        return authorService.findAll();
    }

    @GetMapping("/{id}")
    public Author getAuthorById(@PathVariable Long id) {
        return authorService.findById(id);
    }

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

    @PostMapping("/{authorId}/books")
    public Author addBook(@PathVariable Long authorId, @RequestBody Book book) {
        return authorService.addBook(authorId, book);
    }

    @DeleteMapping("/{authorId}/books/{bookId}")
    public Author removeBook(@PathVariable Long authorId, @PathVariable Long bookId) {
        return authorService.removeBook(authorId, bookId);
    }
}
package com.example.controller;

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

import java.util.List;

@RestController
@RequestMapping("/books")
public class BookController {
    @Autowired
    private BookService bookService;

    @PostMapping
    public Book createBook(@RequestBody Book book) {
        return bookService.save(book);
    }

    @GetMapping
    public List<Book> getAllBooks() {
        return bookService.findAll();
    }

    @GetMapping("/{id}")
    public Book getBookById(@PathVariable Long id) {
        return bookService.findById(id);
    }

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

Step 8: Create Main Application Class

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

package com.example;

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

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

Step 9: Run the Application

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

Testing the Endpoints

  1. Create an Author:

    • URL: POST /authors
    • Body:
      {
        "name": "George Orwell"
      }
      
  2. Create Books:

    • URL: POST /books
    • Body:
      {
        "title": "1984"
      }
      
    • Body:
      {
        "title": "Animal Farm"
      }
      
  3. Add Books to Author:

    • URL: POST /authors/{authorId}/books
    • Body:
      {
        "title": "1984"
      }
      
    • Body:
      {
        "title": "Animal Farm"
      }
      
  4. Get All Authors:

    • URL: GET /authors
  5. Get Author by ID:

    • URL: GET /authors/{id}
  6. Get All Books:

    • URL: GET /books
  7. Get Book by ID:

    • URL: GET /books/{id}
  8. Delete Author by ID:

    • URL: DELETE /authors/{id}
  9. Delete Book by ID:

    • URL: DELETE /books/{id}

Conclusion

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

Comments