In the previous articles…

As we introduced in the first article, Spring is an excellent tool for Java application development. We also explored how to set up a basic project and create an initial, simple API.

In this article, we’ll connect the project to a database, specifically PostgreSQL, and explore how to persist and manipulate saved data.

PostgreSQL

PostgreSQL, originally known as “post-ingres,” was conceived by Michael Stonebraker in 1985. It serves as a robust alternative to other relational databases like MySQL. The key distinction of PostgreSQL lies in its support for defining custom data types (based on standard SQL types) and data type inheritance.
For detailed insights, refer to the official website.

Spring & PostgreSQL

Database Installation

First, ensure PostgreSQL is installed. Follow this guide for installation instructions.

Dependencies

To begin, add the necessary dependencies to your project:

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

With these dependencies, you now have access to all entity manipulation features provided by “spring-boot-starter-data-jpa” and all database communication interfaces provided by “postgresql.” There is no need to specify the version of the PostgreSQL driver: Spring Boot manages it and picks a compatible one.
For IntelliJ users, each time you modify the pom.xml file, click the dedicated icon to apply the changes.

PostgreSQL Configuration

With dependencies in place, you need to set the connection parameters your application will use to interface with the database. Open the “application.properties” file and add the following settings:

spring.datasource.url=jdbc:postgresql://localhost:5432/YOUR_DB_NAME
spring.datasource.username=YOUR_USERNAME
spring.datasource.password=YOUR_PASSWORD

There is no need to specify the driver: Spring Boot detects it automatically from the connection URL.

Before creating domain entities, add the following setting to auto-generate the schema for domain entities:

spring.jpa.hibernate.ddl-auto=update

Domain Entities

Now, let’s create a domain entity.
Within your project, create a new package named domain and add a file called User.java with the following code:

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

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String username;

    public User() {}

    public User(String username) {
        this.username = username;
    }

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

    public Long getId() {
        return id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUsername() {
        return username;
    }
}

Here are some of the annotations we used:

  • @Entity: Marks this class as a domain entity.
  • @Table(name = "users"): Sets the name of the database table. By default it is derived from the class name, but we customized it here since “User” is a keyword in Postgres.
  • @Id: Indicates the primary key field.
  • @GeneratedValue(strategy=GenerationType.IDENTITY): Specifies that the database should auto-generate the ID field, with Postgres creating a unique sequential identifier of type long.

Repository

Repositories are interfaces providing methods for managing (creating, reading, updating, etc.) database records.
In our case, implementations of these interfaces are provided by the “spring-boot-starter-data-jpa” dependency.

Create a package named repository and add the file UserRepository.java with the following content:

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

We’ll leave the interface as is for now, as we’re only using basic CRUD operations. We’ll explore custom functionalities for this interface later.

DTO

To create or update a user, the client will send data in the request body as JSON, for example {"username": "user1"}. To let Spring automatically convert that JSON into a Java object, we define a class describing its structure: a DTO (Data Transfer Object).

Create a package named dto and add the file UserRequest.java:

public class UserRequest {
    private String username;

    public UserRequest() {}

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

The attribute name must match the field name in the JSON. Without a DTO, declaring the parameter simply as a String, Spring would hand us the entire body as is, braces and quotes included.

Controller

Now it’s time to create the controller for managing users. In the main package, create a new file UserController.java and add the following code:

@RestController
@RequestMapping("/user")
public class UserController {

    private final UserRepository userRepository;

    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

This code creates a new API path under “/user.” Next, define the CRUD methods for the User entity by adding the following:

@PostMapping(value = "/register")
public ResponseEntity<User> register(@RequestBody UserRequest request) {
    User newUser = new User(request.getUsername());
    userRepository.save(newUser);
    return ResponseEntity.ok(newUser);
}

@GetMapping(value = "")
public ResponseEntity<List<User>> getAll() {
    List<User> users = userRepository.findAll();
    return ResponseEntity.ok(users);
}

@GetMapping(value = "/{userId}")
public ResponseEntity<User> getById(@PathVariable Long userId) {
    User user = userRepository.findById(userId).orElse(null);

    if (user == null) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(user);
}

@PutMapping(value = "/{userId}")
public ResponseEntity<User> updateById(@PathVariable Long userId, @RequestBody UserRequest request) {
    User user = userRepository.findById(userId).orElse(null);

    if (user == null) {
        return ResponseEntity.notFound().build();
    }

    user.setUsername(request.getUsername());
    user = userRepository.save(user);

    return ResponseEntity.ok(user);
}

@DeleteMapping(value = "/{userId}")
public ResponseEntity<User> deleteById(@PathVariable Long userId) {
    User user = userRepository.findById(userId).orElse(null);

    if (user == null) {
        return ResponseEntity.notFound().build();
    }

    userRepository.delete(user);
    return ResponseEntity.ok().build();
}

In this example, we introduced a new annotation and a new way to write the API URL. Consider the Get API example:

@GetMapping(value = "/{userId}")
public ResponseEntity<User> getById(@PathVariable Long userId) {
    User user = userRepository.findById(userId).orElse(null);

    if (user == null) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(user);
}

This API’s URL will look like “localhost:8080/user/1” since “/{userId}” tells Spring that the URL will contain a variable component. The @PathVariable annotation maps this variable to the userId parameter. In the register and update methods, the @RequestBody annotation applied to a UserRequest parameter tells Spring to convert the received JSON into our DTO.

Testing

To test the API, use Postman or an equivalent app.

  • Get user list:
    • Method: GET
    • URL: localhost:8080/user
    • Response: []
  • Create new user:
    • Method: POST
    • URL: localhost:8080/user/register
    • Body: {"username": "user1"}
    • Response:
{
    "id": 1,
    "username": "user1"
}
  • Get user by ID:
    • Method: GET
    • URL: localhost:8080/user/1
  • Update user:
    • Method: PUT
    • URL: localhost:8080/user/1
    • Body: {"username": "user2"}
  • Delete user:
    • Method: DELETE
    • URL: localhost:8080/user/1

Conclusion

In this second part, we connected to a database and learned how to persist and manipulate data. In the third and final part, we’ll implement authentication with JWT Token and Spring Security.
The complete code for this example is available at this link.