An API with Spring Boot

Author

Ludovic Deneuville

Spring

🚧

https://www.geeksforgeeks.org/introduction-to-spring-framework/ https://www.geeksforgeeks.org/spring/ https://www.geeksforgeeks.org/introduction-to-spring-boot/ https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/html/overview.html

https://gayerie.dev/docs/spring/index.html

Spring Framework

  • Open-source, lightweight framework
  • Simplifies Java development
  • Loosely coupled components
  • Promotes good design practices
  • Makes integration with other systems seamless
  • improving:
    • maintainability,
    • testability.
    • flexibility

Modules

  • Spring MVC: to build web applications
  • Spring Security: authentication, authorization…
  • Spring Data: simplifies database access
  • Spring Batch: handling large-scale batch processing
  • With Spring, you’re stepping into a realm where much of the ‘magic’ happens behind the scenes
  • many functionalities are implicitly handled
  • Spring’s power lies in its ability to handle a lot of the boilerplate code
  • you define your beans and dependencies, and Spring wires them together seamlessly

Key features

  • Inversion of Control (IoC)
  • Dependency Injection (DI)
  • Aspect-Oriented Programming (AOP)
  • Ioc
    • control flow of a program is inverted
    • Reduces the dependencies between components
    • making the codebase more modular
    • Promotes loose coupling
  • AOP: A module that encapsulates cross-cutting functionality
    • Wrapper for log, security

Manually creating dependency

Bread.java
public class Bread {
    private boolean isToasted;

    public void toast(){
        this.isToasted = true;
    }
}


Sandwich.java
public class Sandwich {
    private Bread bread;
    private ArrayList<String> ingredients;

    public Sandwich(Bread bread) {
        this.bread = bread;
    }
}

  1. Create a Bread object
  2. Create the sandwich object


Bread bread = new Bread();
Sandwich sandwich = new Sandwich(bread);

Dependency Injection

Bread.java
@Component
public class Bread {
    private boolean isToasted;

    public void toast(){
        this.isToasted = true;
    }
}


Sandwich.java
public class Sandwich {
    @Autowired
    private Bread bread;
    
    private ArrayList<String> ingredients;
}

What happens now?

  • Spring scans @Component classes
  • It automatically creates and injects a Bread instance into Sandwich
  • The @Autowired annotation tells Spring to resolve and inject the dependency
  • Component subclasses: @Service, @Repository, @Controller
  • Component: Bean

Bean principles

  • No args constructor
  • Managed by the Spring IoC container
  • Scope (Singleton, Prototype)
  • Lifecycle (creation, usage, destruction)
  • NoArgs: allows Spring to create the bean instance without having to worry about the parameters
  • Prototype: new instance when called

Annotations

What is Annotation?

  • Preceded by an @
  • Assigns extra metadata
  • Modify or examine behavior
  • Heavily used in Spring
  • Applied to class, method, interface, attribute
  • Use for:
    • documentation (Override, Deprecated)
    • Config
    • Runtime processing
  • Possible to create your own

Lombok

  • Island of Indonesia: Lombok, Java
  • boilerplate: repetitive code

Lombok Annotations

Auto-generates:

  • @Getter and @Setter
  • @AllArgsConstructor
  • @NoArgsConstructor

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@AllArgsConstructor
@Getter
public class Book {
    private String title;

    @Setter
    private String author;
}
  • Getter and Setter: Class or Attribute level

Layers

Reminder

  • Controller: API
  • Service: Business Logic
  • Repository: Data Access Object
  • Model: Entities
  • Controller:
    • Handles incoming HTTP requests from the client
    • Processes the request and sends a response
  • Repository: CRUD
  • entities (business_objects):
    • Maps to tables in the database using JPA/Spring Data
    • JPA: Java Persistence API

3-tier architecture:

  • classic software design pattern that separates an application into three logical layers
  • Presentation Tier (UI)
  • Application Tier (Business Logic)
  • Data Tier (Data Storage)

Key Benefits: maintainability scalability, security (layer separation), code reusability


Zoom on Business Layer

Workflow

  • A client sends an HTTPS request
  • The request is received by the Controller
  • If needed, the Controller calls the Service Layer
  • The Service Layer executes the business logic and interacts with the Repository Layer to perform data operations
  • The Repository Layer retrieves or modifies data in the database using JPA
  • The response is sent back to the client

Sequence diagram

  • On the GUI, you click on the book entitled ‘Java’
  • The front office asks the back office for informations

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant Repository
    participant Database

    Client->>Controller: GET /books?title=Java
    Controller->>Service: getBookByTitle("Java")
    Service->>Repository: findByTitle("Java")
    Repository->>Database: SELECT * FROM books WHERE title="Java"
    Database-->>Repository: Returns book data
    Repository-->>Service: Returns Book entity
    Service-->>Controller: Returns Book DTO
    Controller-->>Client: HTTP 200 OK (Book JSON)

HTTP status codes

HTTP Status Code Description
200 OK The request was successful.
400 Bad Request The server could not understand the request due to invalid syntax.
401 Unauthorized Authentication is required, and the client should authenticate itself.
403 Forbidden The client does not have access rights to the content.
404 Not Found The server can not find the requested resource.
500 Internal Server Error The server encountered an unexpected error.
501 Not Implemented The server does not support requested functionality.
502 Bad Gateway The server received an invalid response from the upstream server.
503 Service Unavailable The server is not ready to handle the request.
  • Client side: The problem is with your device or browser.
  • Server side: The problem is with the website you are trying to use.

Spring Boot

Why use Spring Boot?

  • Includes all Spring features
  • Simplify configuration
  • Allows developers to focus on business logic
  • Quick development of production-ready applications
  • Optimisation of dependency management

Features

  • Auto-Configuration
  • Creat REST APIs is easy
  • Embedded Tomcat Server

Embedded Tomcat Server:

  • allows you to run and test your application quickly
  • Self-Contained Applications: Spring Boot packages your application and the Tomcat server into a single executable JAR
  • Reduced Configuration

Starters

Dependencies:

  • spring-boot-starter-core
  • spring-boot-starter-data-jpa
  • spring-boot-starter-test
  • spring-boot-starter-web

Create a project

Properties

  • old: application.properties
  • new: yaml

application.yml

server:
  port: 9000

spring:
  application:
    name: running
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:bootapp;DB_CLOSE_DELAY=-1
    username: sa
    password: password

logging:
  level:
    root: ERROR
    fr:
      ensai: INFO
    org:
      springframework:
        boot:
          web:
            embedded:
              tomcat: INFO
  • Like python .env
  • Port (80: http, 443: https, 22: SSH, 21: FTP)
  • Database connection
  • Log setup
  • Let’s talk about YAML

YAML

  • YAML Ain’t Markup Language
  • Text format
  • Indentation-based syntax
  • Easily readable by humans
  • recursive acronym
  • JSON: efficient for machine-to-machine communication
  • YAML: configuration files (human readability)

YAML Strucure

  • Key-Value pairs
  • indentation: 2 spaces
  • CASE sensitive
  • text values: facultatives quotes
  • spaces: no tabs

YAML Examples

person:
  name: Charlotte         # str
  city: "Amiens"          # str
  age: 25                 # int
  height: 1.70            # float
  student: false          # bool
  birth_date: 1990-05-15  # date


hobbies: 
  - chess
  - hiking
  - landart
  • dict
  • list
    • other syntax: hobbies: ["chess", "hiking", "landart"]

Data Mapping

  • I would remind you that the aim is to send the data up to the GUI
  • We want to get data from database to our app

Why is it usefull?

  • Facilitates interaction between Java objects and database tables
  • ensure accurate data transfer between layers, databases
  • Object-Relational Mapping (ORM)
  • SQLAlchemy in Python
  • It was forbidden in IT Project

Database side

CREATE TABLE user (
    lastname     VARCHAR(255),
    firstname    VARCHAR(255),
    birth_date   DATE,
    gender       VARCHAR(10)
);

Java class

public class User {
    private String lastName;
    private String firstName;
    private LocalDate birthDate;
    private String gender;
  • Many similarities
  • A few differences:
    • User instead of user
    • birthDate vs birth_date

Hibernate

  • Simplify data interactions with relational databases
  • Maps Java classes to database tables
  • Provides database independence
  • Generates SQL queries for CRUD operations
  • inluded via spring-boot-starter-data-jpa
  • JPA: Java Persistence API
  • the default JPA implementation is Hibernate

Entity Bean

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "id_user")
    private Long id;

    private String name;

    @Column(name = "birth_date")
    private LocalDate birthDate;

    @Column(name = "gender", length = 10)
    private String gender;
}

Mapping

  • @Table(name = "user"): no need if same names
  • @Column(name = "birth_date"): no need if same names
  • @Id marks the id field as the primary key of the entity

Mapping is done, now let’s transfert data

Entity annotations

Annotation Description
@Entity Marks the class as a JPA entity.
@Table(name="tbname") Specifies the database table name.
@Id Marks a field as the primary key.
@GeneratedValue( .. ) Defines the auto-increment strategy for the PK.
@Column(name="colname", ...) Maps a field to a database column and allows configuration.
@Enumerated(EnumType.STRING) Maps an enum to a database column.
@Transient Marks a field as non-persistent (ignored by JPA).

@Column(name="col_name", nullable=false, unique=true, length=255)

Entity relationships

Annotation Description
@JoinColumn(name="FK_column") Specifies the foreign key column for relationships.
@ManyToOne Defines a many-to-one relationship between entities.
@OneToMany(mappedBy="fieldName") Defines a one-to-many relationship.
@ManyToMany Defines a many-to-many relationship.
@OneToOne Defines a one-to-one relationship.

ManyToOne

Database reminder:

  • PK, FK
  • Association table
  • .., 1..*

mappedBy = ‘club’ indicates that the relationship is managed by the club attribute in the User class

ManyToMany

  • To be used if the association table is basic
  • Otherwise create an entity for the association table with ManyToOne

Repository

A basic DAO

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

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
  • That’s all!
  • All CRUD methods are available
  • No need to implement the code
  • JpaRepository extends CrudRepository

Basic methods

Methods included automatically without having to implement them:

  • save(Entity e)
  • findById(Long id)
  • findAll()
  • count()
  • delete(Entity e)
  • Save: INSERT or UPDATE
  • existsById(), …

Native SQL Query

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    @Query(value = """
        SELECT * 
        FROM user u 
        WHERE u.last_name = :lastName
    """, nativeQuery = true)
    List<User> findByLastNameNative(@Param("lastName") String lastName);
}

JPQL

  • Java Persistence Query Language
  • Method name ➡️ Auto-generate the Query
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findByGenderOrderByFirstNameDesc(String gender);
}

Controller

API Rest

ApiRestController.java
@RestController
@RequestMapping("/api")
public class ApiRestController {

    @Autowired
    private UserService userService;

    /**
     * Get all users
     */
    @GetMapping("/users")
    public List<User> getAllUsers() {

        return userService.findAll();
    }
}

  • @RestController
    • handles RESTful web requests
    • methods in this class will return data directly (JSON)
  • @RequestMapping("/api"): Specifies the base URL
  • @GetMapping("/users"): Maps getAllUsers() method to endpoint /api/users

Use a PATH Parameter

ApiRestController.java
    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id_user) {
        User user = userService.findById(id_user);
        if (user == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(user);
    }

ResponseEntity:

  • Represents an entire HTTP response

ResponseEntity (not mandatory but):

  • It allows you to customize every aspect of the HTTP response
  • You can easily handle various response scenarios (200, 404…)
  • You can add custom headers to the response, which can be useful for caching, authentication

Alternative to PATH Param: Query Parameters

  • /products?category=electronics&sort=price&page=2

Create, Update, Delete

ApiRestController.java
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User createdUser = userService.save(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }

    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
        User existingUser = userService.findById(id);
        if (existingUser == null) {
            return ResponseEntity.notFound().build();
        }
        user.setId(id);
        User updatedUser = userService.save(user);
        return ResponseEntity.ok(updatedUser);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        User existingUser = userService.findById(id);
        if (existingUser == null) {
            return ResponseEntity.notFound().build();
        }
        userService.deleteById(id);
        return ResponseEntity.noContent().build();
    }
  • @RequestBody: JSON, Not part of the URL; sent in the HTTP request’s body
  • @RequestHeader("Authorization")
    • Key-value pairs in the HTTP request’s header
    • Authorization: Bearer <token>

Run application

Main

Application.java
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  • Main method simply running the application