Skip to main content

Boosting Spring Boot Performance, Implementing Second Level Cache with Redis

· 6 min read
Huseyin BABAL
Software Developer

Introduction

Caching is a fundamental technique to improve the performance of applications by storing frequently accessed data in memory, thereby reducing the need for repeated database queries. In Spring Boot, caching is often implemented at two levels: the first level cache and the second level cache.

First Level and Second Level Cache

First Level Cache: This is the cache associated with the Hibernate session. It is enabled by default and works only within the scope of a session, meaning that the data cached is only available within a single transaction.

Second Level Cache: This is a more sophisticated cache that works at the session factory level. It is shared among all sessions, making it possible to cache data across transactions. This type of cache can significantly reduce database load and improve application performance.

In this blog post, we will focus on integrating the second level cache in a Spring Boot application using Redis.

Spring Starter Projects and Database Integration

For our Spring Boot application, we will use the following starter projects:

  • Spring Data JPA: For ORM and database interactions.
  • PostgreSQL: As our relational database.
  • Redis: For caching purposes.

We will also leverage two SaaS solutions:

  • Rapidapp for PostgreSQL: To quickly set up and manage our PostgreSQL database.
tip

Create a free database in Rapidapp Starter in seconds here

  • Upstash Redis: A managed Redis service optimized for low-latency data caching.
tip

Create a free Redis database in Upstash here

Spring Boot Application Setup

Application

This section contains application level configurations such as the application name, datasource, and jpa as shown below:

application.yaml
spring:
application:
name: sb-l2-redis
datasource:
driver-class-name: org.postgresql.Driver
url: <connection-string-from-rapidapp|or your own managed postgres url>
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
cache:
use_second_level_cache: true

Line 5: JDBC driver class for PostgreSQL.

Line 6: Connection URL for the PostgreSQL database. You can obtain this from Rapidapp or your own managed PostgreSQL service. It should have a format like jdbc:postgresql://<host>:<port>/<database>?user=<user>&password=<password>&sslmode=require.

warning

We use connection string here for demo purposes, but it is not secure to use connection string in application.yaml. You should use environment variables (SPRING_DATASOURCE_URL) or secrets management tools to store sensitive information.

Line 10: This will create tables in PostgreSQL database automatically by using entities.

Line 15: Enable second level cache in Hibernate. We will visit this configuration later.

Entities

Let's create a simple Product entity that will be stored in the PostgreSQL database.

Product.java
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Cache(region = "products", usage = CacheConcurrencyStrategy.READ_WRITE)
class Product {
@Id
@GeneratedValue
private Long id;
private String name;
private String description;
private BigDecimal price;
}
  1. @Entity annotation is used to mark the class as a JPA entity.
  2. @Cache annotation is used to enable caching for the entity.
  • region attribute is used to specify the cache name.
  • usage attribute is used to specify the cache concurrency strategy. The remaining attributes are coming from Lombok library to automatically generate getters, setters, and constructors.

Repository

Next, we will create a ProductRepository interface that extends JpaRepository to interact with the Product entity.

ProductRepository.java
interface ProductRepository extends JpaRepository<Product, Long> {}

Controller

Finally, let's set up a simple Spring Boot application endpoint. We will create a ProductController with endpoints to create, get, and list products.

ProductController.java
@RestController
@RequestMapping("/products")
class ProductController {

private final ProductRepository productRepository;

public ProductController(ProductRepository productRepository) {
this.productRepository = productRepository;
}

@GetMapping
public Iterable<Product> getProducts() {
return productRepository.findAll();
}

@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
return productRepository.findById(id).orElseThrow();
}

@PostMapping
public Product createProduct(@RequestBody CreateProductRequest request) {
Product product = Product.builder()
.name(request.getName())
.description(request.getDescription())
.price(request.getPrice())
.build();
return productRepository.save(product);
}
}

Generate Data

Right after running application, we can now generate some sample data with the following curl command.

curl \
--parallel \
-XPOST \
-H "Content-Type: application/json" \
http://localhost:8080/products\?\[1-200\] \
-d '{"name": "book", "description": "desc", "price": "100.3"}'

Line 2: --parallel flag is used to send multiple requests in parallel. Line 5: [1-200] is used to sent same payload 200 times.

Since we have spring.jpa.show_sql=true in our application.yaml, we can see 200 insert SQL statements in the console. In same way, we can see select SQL statements when we call GET /products/{id} endpoint. This is where second level cache comes into play.

Implementing Second Level Cache with Redis

Redis Client Configuration

First, we need to configure the Redis client in our Spring Boot application. We will use redisson as the Redis client library.

redisson.yaml
singleServerConfig:
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
password: <redis_password>
subscriptionsPerConnection: 5
clientName: sb-l2-redis
address: <redis_address>
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 10
connectionPoolSize: 64
database: 0
dnsMonitoringInterval: 5000

Line 7: Redis password. You can obtain this from Upstash or your own managed Redis service.

Line 10: Redis server address. You can obtain this from Upstash or your own managed Redis service.

You can see the other configuration internals here. Now we have redis client configuration, and with the help of spring.jpa.hibernate.properties.cache.use_second_level_cache: true in application.yaml file, we can enable second level cache in Hibernate. The cache provider class is automatically injected in to the application with following dependency.

pom.xml
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-hibernate-53</artifactId>
<version>3.30.0</version>
</dependency>

Cache Verification

After running the application, we can see that the select SQL statements are not executed when we call the GET /products/{id} endpoint second time. This is because the data is fetched from the Redis cache instead of the database.

Conclusion

Implementing a second level cache in your Spring Boot application using Redis can significantly improve performance by reducing database load. With the combination of Spring Data JPA, PostgreSQL, and Redis, you can achieve scalable and efficient caching solution. Using managed services like Rapidapp and Upstash for Redis further simplifies the setup and management of your infrastructure.

By following the steps outlined in this blog post, you can easily integrate a second level cache in your Spring Boot application and enjoy the benefits of faster data access and reduced database queries.

tip

You can find the complete source code for this project on GitHub.

Supercharge Spring Boot with RapidApp's PostgreSQL Service

· 5 min read
Huseyin BABAL
Software Developer

In the rapidly evolving landscape of software development, Spring Boot has emerged as a beacon for Java developers seeking to streamline their application development process. One of Spring Boot's most powerful features is its ability to utilize "starters" – pre-configured sets of code and dependencies that can be easily included in projects to provide specific functionality. These starters not only save time but also enforce best practices and reduce the likelihood of errors.

Understanding Spring Boot Starters

Spring Boot starters are essentially a set of convenient dependency descriptors that you can include in your application. Each starter provides a quick way to add and configure a specific technology or feature to your Spring Boot application, without the hassle of managing individual dependencies and their compatible versions. This approach significantly simplifies the build configuration, enabling developers to focus more on their application's unique functionality rather than boilerplate code and configuration.

The starters cover a wide range of needs, from web applications with spring-boot-starter-web to data access with spring-boot-starter-data-jpa, and much more. By abstracting complex configurations, starters offer a seamless, convention-over-configuration approach, adhering to the Spring Boot philosophy.

Introducing RapidApp Postgres Starter

tip

Create a free database in Rapidapp Starter in seconds here

Building on the concept of starters, we are excited to introduce the spring-boot-starter-rapidapp for users of RapidApp, a SaaS platform that includes PostgreSQL as a service. This starter is designed to make it incredibly easy for Spring Boot applications to integrate with a RapidApp PostgreSQL database, eliminating the need for developers to manage the database themselves.

Here's how it works:

  1. Easy Configuration: Developers need to add a few lines to their application.properties or application.yml file, specifying that they want to enable RapidApp Postgres, their API key, and the database ID. This is all it takes to configure the connection to the RapidApp PostgreSQL database:
<!-- pom.xml -->
<dependency>
<groupId>io.rapidapp</groupId>
<artifactId>spring-boot-starter-rapidapp</artifactId>
<version>0.0.2</version> <!-- Replace with the latest version https://mvnrepository.com/artifact/io.rapidapp/spring-boot-starter-rapidapp -->
</dependency>
# application.yaml
rapidapp:
postgres:
enabled: true
apiKey: <your_api_key> # Obtain from https://app.rapidapp.io/api_keys
  1. Automatic Resource Management: When a Spring Boot application using the spring-boot-starter-rapidapp is run, the starter automatically creates a PostgreSQL database, prepares a datasource and establishes a connection to the created PostgreSQL database. This is not the only use-case that you can achieve with Rapidapp starter project, let's take a look a couple of use cases.

Rapidapp starter use-cases

Temporary database

Assume you need a temporary database for a short-term task, where a PostgreSQL database is set up before your Spring Boot application starts and is destroyed before the application shuts down. While you could use an in-memory database like H2, imagine you have multiple replicas of your Spring Boot app that need to connect to a central database, such as PostgreSQL, in Rapidapp. You can easily accomplish this by configuring your Spring Boot project with the following steps:

# application.yaml
rapidapp:
postgres:
enabled: true
apiKey: <your_api_key> # Obtain from https://app.rapidapp.io/api_key
dropBeforeApplicationExit: true

Connecting to a pre-configured database

There are several ways to create a PostgreSQL database in Rapidapp. You can create it directly through the Rapidapp UI or automate the process using the Rapidapp Terraform Provider within your Infrastructure as Code (IaC) pipeline. After creating the PostgreSQL database, you can easily obtain the database ID by navigating to Details > Connection Properties and copying the database ID. Then, you can add it to your Spring application properties file as shown below.

# application.yaml
rapidapp:
postgres:
enabled: true
apiKey: <your_api_key> # Obtain from https://app.rapidapp.io/api_key
databaseId: <db_id>

You can optionally provide a database name for display in the Rapidapp UI (this name does not affect the actual database name). If you don't specify one, Rapidapp will automatically generate a name for your database.

Advantages of Using Spring Boot Starters like RapidApp Starter

The use of starters, including the RapidApp starter, brings several advantages to the table:

  • Simplicity: By abstracting the complexity of dependency management and configuration, starters make it much simpler to add and configure new features in a Spring Boot application.
  • Speed: Starters can significantly reduce the time required to bootstrap new applications or add new features, enabling faster development cycles.
  • Best Practices: Starters are designed with best practices in mind, ensuring that applications are configured optimally right from the start.
  • Focus on Business Logic: With starters handling much of the boilerplate code and configuration, developers can focus more on the unique business logic of their applications.

In conclusion, the spring-boot-starter-rapidapp exemplifies how Spring Boot starters can be leveraged to simplify and optimize application development. By providing an easy and efficient way to integrate Spring Boot applications with RapidApp's managed PostgreSQL service, it opens up new possibilities for developers to build scalable, serverless applications with minimal overhead. As the ecosystem of Spring Boot starters continues to grow, the opportunities for developers to innovate and streamline their development processes will only expand.

tip

You can see the demo project here