Skip to content

This project implements simple CRUD using Spring Boot & Postgres database. The purpose of this project is to study and review Spring Boot framework.

Notifications You must be signed in to change notification settings

davide-ferrara/SpringBoot_CRUD_Postgre_Tutorial

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Spring-Boot CRUD Tutorial for beginners

This project provides basic skills to implement a simple RESTful API.

Author : Sungwoo (Jonathan) Kim

*The reference of this tutorial is from Amigoscode youtube channel.

What is Spring? Why do you use it?

Spring is a Java based web framework that provides various of configurations, security, and utilities to build an application. Spring-Boot, especially, makes easier and faster to connect and service a powerful web application. Also, it is one of the most popular framework currently used all over the world.

This project will show how to build a fast simple RESTful Web Service just to give you an idea of how the framework works. It is an hour course tutorial and here is some steps for the tutorial :

  • Run the Server
  • Display a simple phrase on the server
  • Construct a business architecture of Spring Framework
  • Create and Connect to Database
  • Implement CRUD using Database

Some fundamental concepts will be included in this tutorial, so wish you follow along through the end of this tutorial.

Now, let's get started!

Running the server

In order to start the Spring project, the easiest way is to initialize with spring initializer which is officially provided by spring.

spring initializr web page

Select just as the screenshot above : Maven Project - Java Language - 3.0.6 ver (any version is fine) - default Metadatas - Jar packaging - Java version 17 - Dependencies(Spring Web, Spring Data JPA, PosegreSQL Driver)

To give a little description of some options above :

  • Maven : Maven and Gradle are both tool that makes the build process easy. The main feature of Maven is that it uses pom.xml to manage a project's build. It is a classic type management tool used throughout the years, however, the trend is moving on to Gradle which is considered to be more flexible and faster than Maven.
  • Jar : Jar is a deploy file format that is acquired after building the project. You will basically use Jar when dealing with Spring-Boot projects.
  • Spring Data JPA : The feature of the dependency helps to easily manipulate the database. Basic CRUD queries are provided in libraries.

After generating the project, open with Spring IDE (Eclipse, VScode, Intellij). I used Intellij IDEA.

Once you open the project, find pom.xml. You will see all the dependencies needed for this project. In order to run the server we are going to comment the jpa dependency.

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

Then apply it to maven by :

Right click on demo Project file > Maven > Reload Project

And after all Run the application. And type https://localhost:8080 on your browser. If you see a Whitelabel Error Page, it means the server is running correctly.

Display a simple phrase on the server

Now we are going to make a Controller that handles the request of a user. Let's create a 'student' package and controller java file in the package.

src > main > java > com.example.demo > student > StudentController.java

Now that we have a Controller component, we will let the Controller to display Hello World on the web page instead of Whiltelabel Error page.

package com.example.demo.student;

@RestController
public class StudentController {
  @GetMapping("/")
  public String hello() {
    return "Hello World";
  }
}

Re-run the application, and refresh your web browser. You will see a nice little Hello world displayed on your web.

or a lot of times, we use List to interact with web. So let's try this example also.

@GetMapping("/")
public List<String> hello() {
  return List.of("Hello"+ "World");
}

Construct a business architecture of Spring Framework

This is the project overview that we will implement for this project. We will make database named student, and table named Student and implement CRUD(Create, Read, Update, Delete) by RESTful API. Here is an example of basic idea that will be done.

Project Overview

Before we start to learn the business architecture, we need to understand some fundamental concepts of Spring in order to understand fully.

MVC Pattern

MVC is a designing pattern consists of Model, View, and Controller. By splitting into 3 roles of composing element of an application, it is much easier to implement in such manner of business logics.

  • Model : Role that manages the interaction between Databases. All the manipulations pertinent to the Entity of Database is the role of Model.
  • View : Role of controlling the views of user interface. It displays the data that is acquired from Model.
  • Controller : Role of managing multiple Models and Views. It controls general processes of the Spring as a Control Tower.

Student Class

Now that we know MVC patterns, we have to keep in mind which logic the service belongs while working. First we will make a Student Entity which belongs to Model logic.

Entity class is an Object that maps with Database Table. It should contain exactly same fields with the columns of the table.

The features of Student includes :

  • Id
  • Name
  • Email
  • Date of Birth
  • Age

Now let's go to the project and make a Student class.

src > main > java > com.example.demo > student > Student.java

package com.example.demo.student;

public class Student {
    private Long id;
    private String name;
    private String email;
    private LocalDate dob;
    private Integer age;

    public Student() {
    }

    public Student(Long id, String name, String email, LocalDate dob, Integer age) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.dob = dob;
        this.age = age;
    }

    public Student(String name, String email, LocalDate dob, Integer age) {
        this.name = name;
        this.email = email;
        this.dob = dob;
        this.age = age;
    }

    //Getters and Setters
    public Long getId() {
        return id;
    }

    //...
}

As we have our Student Entity ready, let's check this Object works fine by changing the return value of the Controller.

@RestController
public class StudentController {
    @GetMapping("/")
    public List<Student> hello() {
        return List.of(new Student(
                1L,
                "Mariam",
                "[email protected]",
                LocalDate.of(2000, Month.JANUARY, 5),
                21
        ));
    }
}

Run the Application, and refresh your browser. If you follewed the steps right you will see an output like this on your browser.

[
  {
    "id": 1,
    "name": "Mariam",
    "email": "[email protected]",
    "dob": "2000-01-05",
    "age": 21
  }
]

Lastly, in order to build a Business logic we need make a service Layer. We will allocate the role to get student logic to service layer. In order to use service instance in Controller, Spring-Boot uses a method called Dependency Injection.

DI(Dependency Injection) : Spring providing utility of Injecting function of a dependent Object. It helps in flexibility on modification of an Object.

So here are some codes how Service Object is injected to the Controller. Note that it uses @Autowired annotation to implement Dependency Injection.

*Note that RectController's URL has changed.

package com.example.demo.student;

@RestController
@RequestMapping(path = "api/v1/student")
public class StudentController {
    private final StudentService studentService;

    @Autowired
    public StudentController(StudentService studentService) {
        this.studentService = studentService;
    }
    @GetMapping
    public List<Student> getStudent() {
        return studentService.getStudents();
    }
}
package com.example.demo.student;

@Service
public class StudentService {

    public List<Student> getStudents() {
        return List.of(new Student(
                1L,
                "Mariam",
                "[email protected]",
                LocalDate.of(2000, Month.JANUARY, 5),
                21
        ));
    }
}

After modifying and running the application, we can see the exact same result as before but with business logic included.

Create and Connect to Database

First we need to provide some basic data to our project. No matter what database you use, you will need to fill the application properties in order to connect with the database.

src > main > resources > application.properties

spring.datasource.url=jdbc:postgresql://localhost:5432/student
spring.datasource.username=
spring.datasource.password=
# Entity 정보를 이용해 자동으로 테이블 생성 / 삭제
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true

server.error.include-message=always

The data above shows some connection data via database, and some features related to databases used in this project. For example, hibernate ddl-auto helps to create/delete table of the declared Entity.

Next, download Postgres database from web. Execute the shell for Postgres and we will create student databse and give privilage to the database.

CREATE DATABASE student;
\du -- to find all users in postgre
GRANT ALL PRIVILEGES ON DATABASE "student" TO jonmac;
GRANT ALL PRIVILEGES ON DATABASE "student" TO postgres;
\c student -- connect to database student
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>

Then we will go to pom.xml to un-comment the jpa dependency, and reload the project again, and run the application. You will find that JPA is running and database has been connected by running logs of the application .

Now, let's map our Entity to the database simply by adding annotation to our Student class.

package com.example.demo.student;

import jakarta.persistence.*;

@Entity
@Table
public class Student {
  @Id
  @SequenceGenerator(
          name = "student_sequence",
          sequenceName = "student_sequence",
          allocationSize = 1
  )
  @GeneratedValue(
          strategy = GenerationType.SEQUENCE,
          generator = "student_sequence"
  )
  private Long id;
  private String name;
  private String email;
  private LocalDate dob;
  private Integer age;

  public Student() {
  }

  public Student(Long id, String name, String email, LocalDate dob, Integer age) {
    this.id = id;
    this.name = name;
    this.email = email;
    this.dob = dob;
    this.age = age;
  }

  public Student(String name, String email, LocalDate dob, Integer age) {
    this.name = name;
    this.email = email;
    this.dob = dob;
    this.age = age;
  }
  
  //...
}

Now that we have modified Student Entity. By running the application, it will automatically create the Student table in the database. And by stopping to application, it will automatically delete the Student table. Here are my examples of created tables in student database.

student=# \d
               List of relations
 Schema |       Name       |   Type   | Owner
--------+------------------+----------+--------
 public | student          | table    | jonmac
 public | student_sequence | sequence | jonmac
(2 rows)

student=# \d student
                      Table "public.student"
 Column |          Type          | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
 id     | bigint                 |           | not null |
 dob    | date                   |           |          |
 email  | character varying(255) |           |          |
 name   | character varying(255) |           |          |
Indexes:
    "student_pkey" PRIMARY KEY, btree (id)

So finally, we are ready with our Postgres database connected to our application. It was a long way until here, but we have one last but not the least section to go! Let's keep it up!

Implement CRUD using Database

As I mentioned of above, Spring Data JPA is a feature of the dependency helps to easily manipulate the database. We will directly implement this by using JPA Repository, and find out how power the JPA is.

Create an Interface file in directory below:

src > main > java > com.example.demo > student > StudentRepository.java

package com.example.demo.student;

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {

}

The Generic specified in JpaRepository is <Type of date, ID of the Table> thus,in this case, <Student, Long>. We are going to utilize this Repository in Service Layer, so let's inject dependency (DI) to Service logic, and use JPA Repository to find all students from the Student Table.

package com.example.demo.student;

@Service
public class StudentService {
    private final StudentRepository studentRepository;

  //Repository Dependecny  Injection
    @Autowired
    public StudentService(StudentRepository studentRepository) { 
        this.studentRepository = studentRepository;
    }

    public List<Student> getStudents() {
        // find All students in Table
        return studentRepository.findAll();
    }
}

As you can see above, you just need a simple findAll() method to find all students in the table. In order to test whether the service works fine, let's insert some students to the table in advance by using a configuration utility from Spring.

src > main > java > com.example.demo > student > StudentConfig.java

package com.example.demo.student;

import static java.time.Month.*;

@Configuration
public class StudentConfig {

    @Bean
    CommandLineRunner commandLineRunner(StudentRepository repository){
        return args -> {
            Student mariam = new Student(
                    "Mariam",
                    "[email protected]",
                    LocalDate.of(2000, JANUARY, 5),
                    21
            );
            Student alex = new Student(
                    "Alex",
                    "[email protected]",
                    LocalDate.of(2004, JANUARY, 5),
                    21
            );

            repository.saveAll(
                    List.of(mariam, alex)
            );
        };
    }
}

We can find two annotations in the java code above : Bean, Configuration. What are they?

@Bean : Essential composition of Spring. It is a POJO(Plain Old Java Object) which is literally designed to share one instance throughout the whole Spring Application.

@Configuration : Place where you need to add Bean manually.

The StudentConfig file will execute the bean as soon as the application starts running. Thus, two students, Mariam and Alex, is added to the Student Table by calling saveAll() method of StudentRepository.

If we run the Application, two rows of database has been added, and these two data has successfully displayed on the browser.

student=# SELECT * FROM student;
 id | age |    dob     |         email          |  name
----+-----+------------+------------------------+--------
  1 |  21 | 2000-01-05 | [email protected] | Mariam
  2 |  21 | 2004-01-05 | [email protected]         | Alex
(2 rows)
[
  {
    "id": 1,
    "name": "Mariam",
    "email": "[email protected]",
    "dob": "2000-01-05",
    "age": 21
  },
  {
    "id": 2,
    "name": "Alex",
    "email": "[email protected]",
    "dob": "2004-01-05",
    "age": 21
  }
]

Next, we actually do not need the age column since we can calculate the age by Data of Birth. We will use @Transient annotation to implement this.

@Transient : Specify the field does not belong to the Database.

By applying @Transient to age field in Student Entity, we will be able to manufacture the age field by using the data from database.

package com.example.demo.student;

import jakarta.persistence.*;

@Entity
@Table
public class Student {
    //...
  
    @Transient
    private Integer age;

    public Student() {
    }

    public Student(Long id, String name, String email, LocalDate dob) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.dob = dob;
    }

    public Student(String name, String email, LocalDate dob) {
        this.name = name;
        this.email = email;
        this.dob = dob;
    }

    //...

    public Integer getAge() {
        // Age calculation
        return Period.between(this.dob, LocalDate.now()).getYears();
    }

    //...
}

Thus now, we do not need to insert age value to add student. Now we are done with R(Read) from CRUD.

Now, let's make a logic for C(Create) the data. json format of Name, Email, and Date of Birth will be given as input of Post Request, and it will save to Database If the email not exist in the Database.

package com.example.demo.student;

@RestController
@RequestMapping(path = "api/v1/student")
public class StudentController {
  private final StudentService studentService;

  //...

  @PostMapping
  public void registerStudent(@RequestBody Student student) {
    studentService.addNewStudent(student);
  }
}

Add PostMapping logic in the Controller.

package com.example.demo.student;

public interface StudentRepository extends JpaRepository<Student, Long> {
    @Query("SELECT s FROM Student s WHERE s.email=?1")
    Optional<Student> findStudentByEmail(String email);
}

Add a new find Student By Email logic into Student Repository.

package com.example.demo.student;

@Service
public class StudentService {
    private final StudentRepository studentRepository;
    
    //...

    public void addNewStudent(Student student) {
        Optional<Student> studentOptional = studentRepository.findStudentByEmail(student.getEmail());
        if (studentOptional.isPresent()) {
            throw new IllegalStateException("email exist");
        }
        studentRepository.save(student);
    }
}

And add the business logic into Student Service. By using the utility of generated-request.http from Intellij, we can test the data is being added to the database.

###
POST http://localhost:8080/api/v1/student
Content-Type: application/json

{
  "name" : "Bilal",
  "email" : "[email protected]",
  "dob" : "1995-12-17"
}

Result :

[
  {
    "id": 1,
    "name": "Mariam",
    "email": "[email protected]",
    "dob": "2000-01-05",
    "age": 23
  },
  {
    "id": 2,
    "name": "Alex",
    "email": "[email protected]",
    "dob": "2004-01-05",
    "age": 19
  },
  {
    "id": 3,
    "name": "Bilal",
    "email": "[email protected]",
    "dob": "1995-12-17",
    "age": 27
  }
]

We successfully implemented C(Create) of CRUD.


Now we will make a logic to D(Delete) the data. This logic will be quite straight forward.

package com.example.demo.student;

@RestController
@RequestMapping(path = "api/v1/student")
public class StudentController {
    private final StudentService studentService;

    //...

    @DeleteMapping(path = "{studentId}")
    public void deleteStudent(@PathVariable("studentId") Long studentId) {
        studentService.deleteStudent(studentId);
    }
}

Add DeleteMapping to the Controller with the student variable to delete.

package com.example.demo.student;

@Service
public class StudentService {
    private final StudentRepository studentRepository;
    
    //...
  
    public void deleteStudent(Long studentId) {
        boolean exist = studentRepository.existsById(studentId);
        if (!exist) {
            throw new IllegalStateException("Student with ID " + studentId + " does not exist");
        }
        studentRepository.deleteById(studentId);
    }
}

Add business logic to Service. Check if ID exists in database, and delete it if exists. And here are the results.

###
POST http://localhost:8080/api/v1/student
Content-Type: application/json

{
  "name" : "Bilal",
  "email" : "[email protected]",
  "dob" : "1995-12-17"
}

###
DELETE http://localhost:8080/api/v1/student/1

Result :

[
  {
    "id": 2,
    "name": "Alex",
    "email": "[email protected]",
    "dob": "2004-01-05",
    "age": 19
  },
  {
    "id": 3,
    "name": "Bilal",
    "email": "[email protected]",
    "dob": "1995-12-17",
    "age": 27
  }
]

We can know that ID : 1 has successfully deleted!


Lastly, we will implement U(Update) of CRUD. We will PutMapping to update name or email (or both) by student ID.

package com.example.demo.student;

@RestController
@RequestMapping(path = "api/v1/student")
public class StudentController {
    private final StudentService studentService;

    //...
  
    @PutMapping(path = "{studentId}")
    public void updateStudent(@PathVariable("studentId") Long studentId,
                              @RequestParam(required = false) String name,
                              @RequestParam(required = false) String email){
        studentService.updateStudent(studentId, name, email);
    }
}

Three possible cases were verified during Service logic.

  1. Targeted ID must be included in Database.
  2. If name exists or has changed.
  3. If email exists or has changed.
package com.example.demo.student;

@Service
public class StudentService {
    private final StudentRepository studentRepository;
    
    //...

    @Transactional
    public void updateStudent(Long studentId, String name, String email) {
        Student student = studentRepository.findById(studentId)
                .orElseThrow(() -> new IllegalStateException("Student with ID " + studentId + " does not exist"));

        if (name != null && name.length() > 0 && !Objects.equals(name, student.getName())) {
            student.setName(name);
        }

        if (email != null && email.length() > 0 && !Objects.equals(email, student.getEmail())) {
            Optional<Student> studentOptional = studentRepository.findStudentByEmail(email);
            if (studentOptional.isPresent()) {
                throw new IllegalStateException("email exist");
            }
            student.setEmail(email);
        }
        
        studentRepository.save(student);
    }
}

@Transactional : It guarantees 'All or Nothing' policy. If a little error is found within the Transaction, it will roll back to the point as nothing had happened. On the other hand, it will save the result if and only if it has fully satisfied the safety regulation of saving the data.

And here are the results.

HTTP request :

###
PUT http://localhost:8080/api/v1/student/1?name=Maria&[email protected]
Content-Type: application/json

Result :

[
  {
    "id": 2,
    "name": "Alex",
    "email": "[email protected]",
    "dob": "2004-01-05",
    "age": 19
  },
  {
    "id": 1,
    "name": "Maria",
    "email": "[email protected]",
    "dob": "2000-01-05",
    "age": 23
  }
]

All in all, we finished our journey of implementing CRUD. It took so long to explore all the concepts pertinent to Spring-Boot, however I bet it will worth it.

Keep in mind the tutorial source of this practice was not made by me but was provided by Amigoscode youtube channel. I added some basic Spring concepts to make the guide a bit more clear.

About

This project implements simple CRUD using Spring Boot & Postgres database. The purpose of this project is to study and review Spring Boot framework.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 100.0%