Building a RESTful API with Spring Boot: From Database to Endpoints

Spring Boot REST API Implemantation

Project Configuration

Spring Setup

The project is initialized using Spring Initializr at https://start.spring.io/. The following properties are configured in the application.properties file:

spring.datasource.url=jdbc:mysql://localhost:3306/personnel_directory
spring.datasource.username=admin
spring.datasource.password=securepassword
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect

Database Configuration

We use MySQL as our database. Here's the database schema:

CREATE DATABASE IF NOT EXISTS `personnel_directory`;
USE `personnel_directory`;

DROP TABLE IF EXISTS `worker`;

CREATE TABLE `worker` (
  `id` int NOT NULL AUTO_INCREMENT,
  `first_name` varchar(45) DEFAULT NULL,
  `last_name` varchar(45) DEFAULT NULL,
  `email` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

INSERT INTO `worker` VALUES
(1,'Michael','Johnson','michael@company.com'),
(2,'Sarah','Williams','sarah@company.com'),
(3,'David','Brown','david@company.com'),
(4,'Jennifer','Jones','jennifer@company.com'),
(5,'Robert','Garcia','robert@company.com');

Running MySQL with Docker

To set up MySQL using Docker:

# Pull the latest MySQL image
docker pull mysql:latest

# Run the container
docker run --name mysql-db -e MYSQL_ROOT_PASSWORD=securepassword -p 3306:3306 -d mysql:latest

# Check running containers
docker ps

# Verify port 3306 is in use
lsof -i :3306

# Copy SQL file to container
docker cp <sql_file.sql> mysql-db:/docker-entrypoint-initdb.d/</sql_file.sql>

Traditional CRUD Implementation

Entity Class

The entity class represents our database table:

@Data
@NoArgsConstructor
@RequiredArgsConstructor
@Entity
@Table(name = "worker")
public class Personnel {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int identificationNumber;

    @NonNull
    @Column(name = "first_name")
    private String givenName;

    @NonNull
    @Column(name = "last_name")
    private String familyName;

    @NonNull
    @Column(name = "email")
    private String electronicMail;
}

Data Access Layer

The repository interface defines data access methods:

public interface PersonnelRepository {
    List<personnel> retrieveAll();
    Personnel findById(int id);
    Personnel persist(Personnel personnel);
    void removeById(int id);
}</personnel>

The repository implementation:

@Repository
public class PersonnelRepositoryImpl implements PersonnelRepository {
    private final EntityManager entityManager;

    @Autowired
    public PersonnelRepositoryImpl(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Override
    public List<personnel> retrieveAll() {
        TypedQuery<personnel> query = entityManager.createQuery("from Personnel", Personnel.class);
        return query.getResultList();
    }

    @Override
    public Personnel findById(int id) {
        return entityManager.find(Personnel.class, id);
    }

    @Override
    public Personnel persist(Personnel personnel) {
        return entityManager.merge(personnel);
    }

    @Override
    public void removeById(int id) {
        Personnel personnel = this.findById(id);
        entityManager.remove(personnel);
    }
}</personnel></personnel>

REST Controller

The initial controller implementation:

@RestController
@RequestMapping("/api")
public class PersonnelController {
    private final PersonnelRepository personnelRepository;

    public PersonnelController(PersonnelRepository personnelRepository) {
        this.personnelRepository = personnelRepository;
    }

    @GetMapping("/workers")
    public List<personnel> retrieveAll() {
        return personnelRepository.retrieveAll();
    }
}</personnel>

Service Layer Implementation

The service layer implements business logic and separates concerns between the controller and data access layers.

Service Interface

public interface PersonnelService {
    List<personnel> findAll();
    Personnel locateById(int personnelId);
    Personnel addPersonnel(Personnel newPersonnel);
    Personnel updatePersonnel(Personnel personnel);
    void removePersonnel(int personnelId);
}</personnel>

Service Implementation

@Service
public class PersonnelServiceImpl implements PersonnelService {
    private final PersonnelRepository personnelRepository;

    public PersonnelServiceImpl(PersonnelRepository personnelRepository) {
        this.personnelRepository = personnelRepository;
    }

    @Override
    public List<personnel> findAll() {
        return personnelRepository.retrieveAll();
    }

    @Override
    public Personnel locateById(int personnelId) {
        Personnel found = personnelRepository.findById(personnelId);
        if (found == null) {
            throw new RuntimeException("Personnel with ID not found: " + personnelId);
        }
        return found;
    }

    @Override
    @Transactional
    public Personnel addPersonnel(Personnel newPersonnel) {
        newPersonnel.setIdentificationNumber(0);
        return personnelRepository.persist(newPersonnel);
    }

    @Override
    @Transactional
    public Personnel updatePersonnel(Personnel personnel) {
        return personnelRepository.persist(personnel);
    }

    @Override
    @Transactional
    public void removePersonnel(int personnelId) {
        Personnel found = personnelRepository.findById(personnelId);
        if (found == null) {
            throw new RuntimeException("Personnel with ID not found: " + personnelId);
        }
        personnelRepository.removeById(personnelId);
    }
}</personnel>

Updated Controller

@RestController
@RequestMapping("/api")
public class PersonnelController {
    private final PersonnelService personnelService;

    public PersonnelController(PersonnelService personnelService) {
        this.personnelService = personnelService;
    }

    @GetMapping("/workers")
    public List<personnel> findAll() {
        return personnelService.findAll();
    }

    @GetMapping("/workers/{personnelId}")
    public Personnel findById(@PathVariable int personnelId) {
        return personnelService.locateById(personnelId);
    }

    @PostMapping("/workers")
    public Personnel addPersonnel(@RequestBody Personnel newPersonnel) {
        return personnelService.addPersonnel(newPersonnel);
    }

    @PutMapping("/workers")
    public Personnel updatePersonnel(@RequestBody Personnel personnel) {
        return personnelService.updatePersonnel(personnel);
    }

    @DeleteMapping("/workers/{personnelId}")
    public String removePersonnel(@PathVariable int personnelId) {
        personnelService.removePersonnel(personnelId);
        return "Removed personnel with ID: " + personnelId;
    }
}</personnel>

Optimization with Spring Data

Spring Data reduces boilerplate code by providing common CRUD implementations.

Adding Dependencies

Add these dependencies to your pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Using Spring Data JPA

Replace the custom repository with Spring Datta JPA:

package com.example.demo.repository;

import com.example.demo.entity.Personnel;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PersonnelRepository extends JpaRepository<Personnel, Integer> {
}

Update the service implementation:

@Service
public class PersonnelServiceImpl implements PersonnelService {
    private final PersonnelRepository personnelRepository;

    public PersonnelServiceImpl(PersonnelRepository personnelRepository) {
        this.personnelRepository = personnelRepository;
    }

    @Override
    public List<Personnel> findAll() {
        return this.personnelRepository.findAll();
    }

    @Override
    public Personnel locateById(int personnelId) {
        Optional<Personnel> result = this.personnelRepository.findById(personnelId);
        if (result.isEmpty()) {
            throw new RuntimeException("Personnel not found with ID: " + personnelId);
        }
        return result.get();
    }

    @Override
    @Transactional
    public Personnel addPersonnel(Personnel personnel) {
        personnel.setIdentificationNumber(0);
        return this.personnelRepository.save(personnel);
    }

    @Override
    @Transactional
    public Personnel updatePersonnel(Personnel personnel) {
        return this.personnelRepository.save(personnel);
    }

    @Override
    @Transactional
    public void removePersonnel(int personnelId) {
        this.personnelRepository.deleteById(personnelId);
    }
}

Using Spring Data REST

With Spring Data REST, you can eliminate the service and controller layers entirely:

@RepositoryRestResource(path = "staff")
public interface PersonnelRepository extends JpaRepository<Personnel, Integer> {
}

Configuration Properties

Configure Spring Data REST in application.properties:

# Spring Data Rest properties
spring.data.rest.base-path=/api
spring.data.rest.default-page-size=50
spring.data.rest.sort-param=sort

The Spring Data REST implementation automatically exposes REST endpoints for all CRUD operations, including pagination, sorting, and filtering capabilities.

Tags: spring-boot rest-api jpa hibernate MySQL

Posted on Sun, 05 Jul 2026 16:26:11 +0000 by j3rmain3