Categories
Java

Minimal, fully running Spring Boot REST API with Java Hibernate and MySQL

In this tutorial, we explain how to run a minimal Spring Boot CRUD REST application, with Hibernate ORM and a MySQL database. We also write unit tests, to check the CRUD operations with the API. We do all this in less than 200 lines of code. This tutorial is useful to get started for any more complex larger Java SaaS project, that relies on a classical MVC design pattern.

1) Java project folder

On your local machine, create a Java project folder, called for instance:

hibernate

In the rest of this tutorial, we will explain where to place files inside this folder.

2) Customer DB model

In this demo app, we will have only one entity called Customer (with only 2 fields: id and name). We will persist the entity in database using JPA Hibernate. Create the file hibernate/src/main/java/com/Customer.java with content:

package com;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;

@Entity
@Table(name = "customers")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    private String 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;
    }
}

@GeneratedValue(strategy = GenerationType.IDENTITY) means that the id primary key will be auto-incremented.

3) The customer repository

The Hibernate repository is a layer where we perform CRUD operations on the backend database. To create it with Spring Boot, it is very easy. Simply, create the file hibernate/src/main/java/com/CustomerRepository.java with content:

package com;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}

4) The web controller

Create a file hibernate/src/main/java/com/CustomerController.java with content:

package com;

import javax.validation.Valid;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("")
public class CustomerController {

    @Autowired
    CustomerRepository customerRepository;

    @GetMapping("/customers")
    public List<Customer> getCustomers() {
        return customerRepository.findAll();
    }

    @PostMapping("/customers")
    public Customer createCustomer(@Valid @RequestBody Customer customer) {
        return customerRepository.save(customer);
    }

    @GetMapping("/customers/{id}")
    public Customer getCustomerById(@PathVariable(value = "id") Long customerId) {
        return customerRepository.findById(customerId).orElseThrow(NotFoundException::new);
    }

    @DeleteMapping("/customers/{id}")
    public ResponseEntity<?> deleteCustomer(@PathVariable(value = "id") Long customerId) {
        Customer customer = customerRepository.findById(customerId).orElseThrow(NotFoundException::new);
        customerRepository.delete(customer);

        return ResponseEntity.ok().build();
    }

    @ResponseStatus(value = HttpStatus.NOT_FOUND)
    private static class NotFoundException extends RuntimeException {
    }
}

This is the usual controller code, with the REST endpoints (GET, POST, DELETE).

5) The Spring Boot application main

Create the file hibernate/src/main/java/com/Application.java with following content:

package com;

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

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

6) The application properties file

We need a file where we specify the database connection information for Spring Boot. Create the file hibernate/src/main/resources/application.properties with content:

spring.datasource.url = <your-jdbcUrl>
spring.datasource.username = <your-dbUsername>
spring.datasource.password = <your-dbPassword>

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto = update

In the above file, replace <your-jdbcUrl>, <your-dbUsername> and <your-dbPassword> with your own values.

7) The pom.xml

Create a file hibernate/pom.xml with content:

<?xml version="1.0" encoding="UTF-8"?>
<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>hibernate</artifactId>
	<version>1.0.0</version>
	<packaging>jar</packaging>

	<name>hibernate</name>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>11</java.version>
		<maven.compiler.source>11</maven.compiler.source>
		<maven.compiler.target>11</maven.compiler.target>
		<spring.version>2.5.6</spring.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<version>${spring.version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<version>${spring.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-annotations</artifactId>
			<version>2.12.5</version>
		</dependency>
		<dependency>
			<groupId>jakarta.xml.bind</groupId>
			<artifactId>jakarta.xml.bind-api</artifactId>
			<version>2.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jaxb</groupId>
			<artifactId>jaxb-runtime</artifactId>
			<version>2.3.2</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.26</version>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>org.testcontainers</groupId>
			<artifactId>testcontainers</artifactId>
			<version>1.16.2</version>
		</dependency>
		<dependency>
			<groupId>org.testcontainers</groupId>
			<artifactId>mysql</artifactId>
			<version>1.16.2</version>
		</dependency>

		<dependency>
			<groupId>org.testcontainers</groupId>
			<artifactId>junit-jupiter</artifactId>
			<version>1.16.2</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.22.2</version>
			</plugin>
		</plugins>
	</build>
</project>

Congratulations ! You now have a fully working Spring Boot REST API with JPA Hibernate, that works with any database (fill the database connection properties with your own database values, as explained in step 6)). To start the API, simply run the Application.main() function from step 5).


8) End to end testing of the API

In this last part of the tutorial, we explain how to automatically test the API, using a MySQL database Testcontainer. We will check that the CRUD customer operations of the API work well.

Create the file /hibernate/src/test/java/com/ApplicationTests.java with content:

package com;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.*;
import org.springframework.test.context.*;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.junit.jupiter.*;

import static org.assertj.core.api.Assertions.assertThat;

@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ApplicationTests {

    @LocalServerPort
    private int port;

    @Container
    static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:5.5").withDatabaseName("database").withPassword("test").withPassword("test");

    @DynamicPropertySource
    static void mysqlProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", mysql::getJdbcUrl);
        registry.add("spring.datasource.password", mysql::getPassword);
        registry.add("spring.datasource.username", mysql::getUsername);
    }

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void contextLoads() throws JsonProcessingException {
        String url = "http://localhost:" + port + "/customers";

        // POST customer
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> request = new HttpEntity<>("{\"name\":\"a\"}", headers);
        String resp = restTemplate.postForObject(url, request, String.class);

        // extract ID from created customer
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode root = objectMapper.readTree(resp);
        long id = root.path("id").longValue();

        // GET by ID
        resp = restTemplate.getForObject(url + "/" + id, String.class);
        root = objectMapper.readTree(resp);
        String name = root.path("name").textValue();
        assertThat(name).isEqualTo("a");

        // LIST
        resp = restTemplate.getForObject(url, String.class);
        root = objectMapper.readTree(resp);
        assertThat(root.size()).isEqualTo(1);

        name = root.path(0).path("name").textValue();
        assertThat(name).isEqualTo("a");

        // DELETE
        restTemplate.delete(url + "/" + id, request, String.class);

        // LIST
        resp = restTemplate.getForObject(url, String.class);
        root = objectMapper.readTree(resp);
        assertThat(root.size()).isEqualTo(0);

        // GET by ID should respond 404 NOT FOUND
        int httpCode = restTemplate.getForEntity(url + "/" + id, String.class).getStatusCodeValue();
        assertThat(httpCode).isEqualTo(404);
    }
}

We override the static config application.properties with our own custom code that discovers the Testcontainers JDBC URL, by using the annotation @DynamicPropertySource.

You can now run the end to end test in your IDE like IntelliJ, or using command line, run:

mvn test

That’s it for this tutorial, thank you for reading ! If you have any question, please leave a reply below.

Leave a Reply

Your email address will not be published. Required fields are marked *