AWS Open Source Blog

Using PostgreSQL with Spring Boot on AWS — Part 2

This is the second installment of a two-part tutorial by Björn Wilmsmann, Philip Riecks, and Tom Hombergs, authors of the book Stratospheric: From Zero to Production with Spring Boot and AWS. Björn, Philip, and Tom previously wrote the blog posts Getting started with Spring Boot on AWS Part 1 and Part 2.

In Using PostgreSQL with Spring Boot on AWS — Part 1, we explored how to deploy an RDBMS on Amazon RDS. In Part 2, we walk through using the database we deployed from a Spring Boot application.

Configuring the database in the application

To connect to the PostgreSQL database instance, our sample application needs a few additional dependencies:

dependencies {
  // ...
  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  runtimeOnly 'org.postgresql:postgresql'
  // ...
}

org.postgresql:postgresql contains the JDBC driver for connecting to the PostgreSQL database, whereas org.springframework.boot:spring-boot-starter-data-jpa provides us with Spring Data JPA (and its dependencies), a widely used library for accessing databases from within Spring Boot applications.

Once we’ve started the service stack, our Spring Boot application will automatically connect to the PostgreSQL database using the standard SPRING_DATASOURCE_URL, SPRING_DATASOURCE_USERNAME, and SPRING_DATASOURCE_PASSWORD parameters we set before.

Using the database for storing and retrieving todos

Now that we’ve prepared the surrounding infrastructure and our Spring Boot app is configured to use our new PostgreSQL database, we can finally put it to use in our application code.

If you’re familiar with using JPA in a Spring Boot application, you can skip this section and have a look at how to set up a local development environment for database connectivity.

The domain model of our application is structured around the Todo entity, as shown in this diagram:

The domain model of our application is structured around the "Todo" entity.

Figure 1: The domain model of our application is structured around the “Todo” entity.

Our application follows a package-by-feature structure. This means that the feature folders collaboration, person, registration, and todo contain the code artifacts related to each of those features. These packages include controllers, service interfaces (and their implementations), Spring Data JPA repositories, and data model classes.

We’ll have a closer look at some of the classes from the dev.stratospheric.todo package to examine how we use the newly created PostgreSQL database for our application.

We’ll focus on the Todo class and how it’s used. This class is annotated with the @Entity annotation from the javax.persistence package (the API provided by JPA / Jakarta Persistence). This annotation marks the class as a database entity. By default, the unqualified class name (as opposed to its fully qualified name, which would include the package name) is used as the entity’s database table name:

// ...

@Entity
public class Todo {

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

  @NotBlank
  @Size(max = 30)
  private String title;

  @Size(max = 100)
  private String description;

  private Priority priority;

  @NotNull
  @Future
  @DateTimeFormat(pattern = "yyyy-MM-dd")
  private LocalDate dueDate;

  @Enumerated(EnumType.STRING)
  private Status status;

  @ManyToOne
  @JoinColumn(name = "owner_id")
  private Person owner;

  // ...
}

Again, as with the entity name and the table name, the attribute names by default match the column names. Most of those attributes are annotated with one or more annotations. These annotations allow us to further specify rules and constraints that should apply to an attribute.

Primary key and object identity

The id attribute is annotated with @Id from the javax.persistence package marking this attribute as the entity’s unique identifier (or primary key).

The @GeneratedValue annotation (also from the javax.persistence package) denotes that the attribute’s value is generated automatically. The strategy = GenerationType.IDENTITY argument further specifies that this value is provided through an identity column in the database table.

Constraints and validation

Some columns are annotated with annotations from the package javax.validation.constraints, such as @NotBlank, @Size, or @Future. Such annotations allow us to define the rules—or constraints—we’d like to apply to each of the attributes and their value. For example, the todo’s title is supposed to contain a non-empty (not blank) string with 30 characters at most.

Storing and retrieving information

Now that we have defined our database entity, we can use it for storing and retrieving information. With Spring Data JPA, the abstraction for doing so is the JpaRepository interface from the org.springframework.data.jpa.repository package. This interface provides us with a set of methods for manipulating and retrieving data:

public interface JpaRepository extends PagingAndSortingRepository, QueryByExampleExecutor {

  @Override
  List findAll();

  @Override
  List findAll(Sort sort);

  @Override
  List findAllById(Iterable ids);

  @Override
   List saveAll(Iterable entities);

  // ...
}

To use this for a specific database entity, such as our Todo class, we must extend this interface with our own TodoRepository interface:

public interface TodoRepository extends JpaRepository {
  List findAllByOwnerEmailOrderByIdAsc(String email);
}

This interface specifies a JpaRepository that’s responsible for persisting Todo entities with a Long-typed ID. Moreover, we’ve added a findAllByOwnerEmailOrderByIdAsc() method that allows us to find all Todos whose owner has the email address given by the method’s email argument. Spring Data JPA uses a query generation mechanism that derives the required SQL query from the method name (refer to Defining Query Methods from the Spring Data JPA documentation for more information).

The TodoRepository then can be injected into other classes, like our TodoService, via Spring’s dependency injection:

// ...

@Service
public class TodoService {

  private final TodoRepository todoRepository;
  private final PersonRepository personRepository;

  public TodoService(
    TodoRepository todoRepository,
    PersonRepository personRepository) {
    this.todoRepository = todoRepository;
    this.personRepository = personRepository;
  }

  public Todo save(Todo todo) {
    // ...

    return todoRepository.save(todo);
  }
}

Spring will automatically inject the TodoRepository into the constructor of TodoService. With this dependency, we now can insert a new row into the database table by calling todoRepository.save().

The TodoService, in turn, is injected into the TodoController, again using constructor injection, where it is used for creating, retrieving, updating, and deleting Todos in various controller methods. These controller methods are mapped to HTTP paths within our application that can be accessed through HTTP GET (in case of the methods annotated with @GetMapping) and POST (for those methods annotated with @PostMapping) requests.

Finally, the Thymeleaf template in our application’s resources/templates folder, specifically dashboard.html, show.html, and edit.html, make use of these controller methods to allow the user to display and edit todos.

Enabling local development

One thing is still missing in our setup: In most cases, we wouldn’t want to wait until our entire AWS infrastructure has been redeployed through our nearly continuous deployment pipeline after a code change. Instead, we want to test changes locally.

That’s where a local database instance comes into play. We can use Docker to spin one up by adding a service to the docker-compose.yml file located in our application’s root directory:

  postgres:
    image: postgres:11.5
    ports:
      - 5432:5432
    environment:
      - POSTGRES_USER=stratospheric
      - POSTGRES_PASSWORD=stratospheric
      - POSTGRES_DB=stratospheric
      # ...

With these lines added to the docker-compose.yml file, we can now run docker-compose up from the command line in our application’s root directory to start a PostgreSQL instance alongside other services already defined in our docker-compose.yml.

Now, all we have to do is add these lines to a application-dev.yml properties file in our application’s src/main/resources folder:

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/stratospheric
    username: stratospheric
    password: stratospheric
    # ...

Because our build.gradle file already configures Gradle to use dev as Spring Profile (by using -Dspring.profiles.active=dev as a JVM argument), these settings will be picked up automatically when running ./gradlew bootrun:

// ...
bootRun {
  jvmArgs = [
    "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005",
    "-Dspring.profiles.active=dev",
  ]
}

Find the source code with detailed instructions on how to build and run this application on GitHub.

Conclusion

Relational databases are an essential element of web applications, and PostgreSQL is a popular open source choice for a DBMS to run such a database on. Amazon RDS allows us to manage relational databases in the cloud, which in turn allows us to both account for non-functional requirements, such as scalability, and to provision and manage database resources in a self-service, DevOps fashion.

In this two-part tutorial, we learned about provisioning and deploying the infrastructure required for running a PostgreSQL database on Amazon RDS and had a look at how to use this database in the context of a Spring Boot application.

Using databases is but one aspect of web applications, though. In our book Stratospheric: From Zero to Production with Spring Boot and AWS, we develop an entire web application that seamlessly integrates with other common AWS services, such as Amazon Simple Queue Service (Amazon SQS), Amazon Simple Storage Service (Amazon S3), Amazon Simple Notification Service (Amazon SNS), Amazon Cognito, and Amazon Elastic Container Service (Amazon ECS) using Spring Cloud AWS. We guide readers through the steps required for getting a Spring Boot application running on AWS. This book explains how to get a Spring Boot application running on AWS. Not only do we focus on developing the application with Spring Boot, but we also take a detailed look at getting it ready for production. This includes a nearly continuous deployment pipeline, infrastructure-as-code using CloudFormation and AWS CDK, deployment patterns, and monitoring.

Björn Wilmsmann width=”150″ height=”150″ />

Björn Wilmsmann

Björn Wilmsmann is an independent IT consultant who helps companies transform their business into a digital business. A longtime software entrepreneur, he’s interested in web apps and SaaS products. He designs and develops business solutions and enterprise applications for his clients. Apart from helping companies in matters of software quality and improving the availability of and access to information through APIs, Björn provides hands-on training in technologies such as Angular and Spring Boot.

Philip Riecks width=”150″ height=”150″ />

Philip Riecks

Under the slogan Testing Java Applications Made Simple, Philip provides recipes and tips & tricks to accelerate your testing success on both his blog and on YouTube. He is an independent IT consultant from Berlin and is working with Java, Kotlin, Spring Boot, and AWS on a daily basis.

Tom Hombergs width=”150″ height=”150″ />

Tom Hombergs

Tom is a senior software engineer at Atlassian in Sydney, working with AWS and Spring Boot at scale. He is running the successful software development blog reflectoring.io, regularly writing about Java, Spring, and AWS with the goal of explaining not only the “how” but the “why” of things. Tom is the author of Get Your Hands Dirty on Clean Architecture, which explores how to implement a hexagonal architecture with Spring Boot.

The content and opinions in this post are those of the third-party author and AWS is not responsible for the content or accuracy of this post.

Ricardo Sueiras

Ricardo Sueiras

Cloud Evangelist at AWS. Enjoy most things where technology, innovation and culture collide into sometimes brilliant outcomes. Passionate about diversity and education and helping to inspire the next generation of builders and inventors with Open Source.