Skip to content

JDBC In SpringBoot#

JDBC In SpringBoot Projects#

  • In SpringBoot projects, we usually use dependency libraries spring-boot-starter-jdbc for JDBC. We should note that these SpringBoot dependencies will wrap many others libraries and JDBC core are just a part of them.
  • Let's see the dependency tree of spring-boot-starter-jdbc as below.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
+- org.springframework.boot:spring-boot-starter-jdbc:jar:2.4.4:compile
|  +- org.springframework.boot:spring-boot-starter:jar:2.4.4:compile
|  |  +- org.springframework.boot:spring-boot:jar:2.4.4:compile
|  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:2.4.4:compile
|  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.4.4:compile
|  |  |  +- ch.qos.logback:logback-classic:jar:1.2.3:compile
|  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.3:compile
|  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.13.3:compile
|  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.13.3:compile
|  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.30:compile
|  |  +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
|  |  +- org.springframework:spring-core:jar:5.3.5:compile
|  |  |  \- org.springframework:spring-jcl:jar:5.3.5:compile
|  |  \- org.yaml:snakeyaml:jar:1.27:compile
|  +- com.zaxxer:HikariCP:jar:3.4.5:compile
|  |  \- org.slf4j:slf4j-api:jar:1.7.25:compile
|  \- org.springframework:spring-jdbc:jar:5.3.5:compile
|     +- org.springframework:spring-beans:jar:5.3.5:compile
|     \- org.springframework:spring-tx:jar:5.3.5:compile
  • As you can see we will have the spring-jdbc and HikariCP also.

What Is The HikariCP?#

  • HikariCP is solid high-performance JDBC connection pool. A connection pool is a cache of database connections maintained so that the connections can be reused when future requests to the database are required. Connection pools may significantly reduce the overall resource usage. You can find more here.
    • HikariCP is a very lightweight and high performance compared with other connection pooling frameworks. So It is the reason why HikariCP become the default pool implementation in Spring Boot 2.0.
    • One more thing, HikariCP has some default configurations and configure HikariCP is also simple, so It will not take you many times to work with. You can find more here.

Spring Boot JDBC Example#

  • We will make an example with Spring Boot JDBC starter in SpringBoot projects.

Dependencies#

  • So in this example we need to imports dependencies as spring-jdbc and commons-dbcp as below. In which commons-dbcp (dbcp - Database Connection Pool) from Apache will help us to manage database connections pool.
pom.xml
1
2
3
4
5
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <version>2.4.4</version>
</dependency>

DAO#

  • In Database we will have some tables with One to Many relationships as the image below

 #zoom

  • Note: you need to go to Database and create these tables manually before running this Spring Boot application. Please use the scripts below for Postgres database.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
CREATE TABLE "customers" (
  "id" uuid PRIMARY KEY NOT NULL,
  "address" varchar(255) DEFAULT NULL,
  "dob" date DEFAULT NULL,
  "email" varchar(255) DEFAULT NULL,
  "gender" varchar(255) DEFAULT NULL,
  "full_name" varchar(255) DEFAULT NULL,
  "phone" varchar(255) DEFAULT NULL
);

CREATE TABLE "orders" (
    "id" uuid PRIMARY KEY NOT NULL,
    "created_date"  timestamp,
    "last_updated_date" timestamp,
    "order_name" varchar(255),
    "order_status" varchar(255),
    "customer_id"  uuid,
    FOREIGN KEY ("customer_id") REFERENCES customers("id")
);

CREATE TABLE "items" (
    "id" uuid PRIMARY KEY NOT NULL,
    "item_name" varchar(225),
    "price"  numeric,
    "quantity" BIGINT,
    "order_id" uuid,
    FOREIGN KEY ("order_id") REFERENCES orders("id")

);
  • Then we will create a DAO (Data Access Object) java class which will contain queries for Create a customer with orders and items in database. The code in CustomerDao will look like as below.
CustomerDao.java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package com.springboot.data.jdbc.app.dao;

import com.springboot.data.jdbc.app.model.OrderStatus;
import com.springboot.data.jdbc.app.model.request.CustomerRequest;
import com.springboot.data.jdbc.app.model.request.ItemRequest;
import com.springboot.data.jdbc.app.model.request.OrderRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@Repository
public class CustomerDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private NamedParameterJdbcOperations namedParameterJdbcOperations;


    public UUID createCustomer(CustomerRequest customerRequest) {
        String sqlCustomerQuery =
                           "INSERT INTO customers(                          "
                +          "id,                                             "   /*1*/
                +          "address,                                        "   /*2*/
                +          "dob,                                            "   /*3*/
                +          "email,                                          "   /*4*/
                +          "full_name,                                      "   /*5*/
                +          "gender,                                         "   /*6*/
                +          "phone)                                          "   /*7*/
                +          "VALUES (?, ?, ?, ?, ?, ?, ?)                    "
                ;
        UUID uuid = this.getRandomUUID();
        this.jdbcTemplate.update(sqlCustomerQuery,
                uuid.toString(),
                customerRequest.getAddress(),
                customerRequest.getDob(),
                customerRequest.getEmail(),
                customerRequest.getFullName(),
                customerRequest.getGender().toString(),
                customerRequest.getPhone());
        return uuid;
    }

    public Map<UUID, OrderRequest> createOrders(UUID customerId, List<OrderRequest> orderRequestList) {
        Map<UUID, OrderRequest> map = new HashMap<>();
        final int size = orderRequestList.size();
        String sqlOrderQuery =
                         "INSERT INTO orders(                                "
                 +       "id,                                                " /*1*/
                 +       "created_date,                                      " /*2*/
                 +       "last_updated_date,                                 " /*3*/
                 +       "order_name,                                        " /*4*/
                 +       "order_status,                                      " /*5*/
                 +       "customer_id)                                       " /*6*/
                 +       "VALUES(                                            "
                 +       ":id,                                               " /*1*/
                 +       ":created_date,                                     " /*2*/
                 +       ":last_updated_date,                                " /*3*/
                 +       ":order_name,                                       " /*4*/
                 +       ":order_status,                                     " /*5*/
                 +       ":customer_id)                                      " /*6*/
                ;
        List<SqlParameterSource> sqlParameterSources = new ArrayList<>(size);
        for (OrderRequest orderRequest: orderRequestList) {
            UUID uuid = this.getRandomUUID();
            MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource();
            LocalDateTime now = LocalDateTime.now();
            mapSqlParameterSource.addValue("id", uuid.toString());
            mapSqlParameterSource.addValue("created_date",now);
            mapSqlParameterSource.addValue("last_updated_date", now);
            mapSqlParameterSource.addValue("order_name", orderRequest.getOrderName());
            mapSqlParameterSource.addValue("order_status", OrderStatus.CREATED.toString());
            mapSqlParameterSource.addValue("customer_id", customerId.toString());
            sqlParameterSources.add(mapSqlParameterSource);
            map.put(uuid, orderRequest);
        }
        this.namedParameterJdbcOperations.batchUpdate(sqlOrderQuery, sqlParameterSources.toArray(new SqlParameterSource[size]));
        return map;
    }

    public void createItems(UUID orderId, List<ItemRequest> itemRequests) {
        final int size = itemRequests.size();
        String itemSqlQuery =
                                "INSERT INTO items(                                 "
                        +       "id,                                                " /*1*/
                        +       "item_name,                                         " /*2*/
                        +       "price,                                             " /*3*/
                        +       "quantity,                                          " /*4*/
                        +       "order_id)                                          " /*5*/
                        +       "VALUES(                                            "
                        +       ":id,                                               " /*1*/
                        +       ":item_name,                                        " /*2*/
                        +       ":price,                                            " /*3*/
                        +       ":quantity,                                         " /*4*/
                        +       ":order_id)                                         " /*5*/
                        ;
        UUID uuid = this.getRandomUUID();
        List<SqlParameterSource> sqlParameterSources = new ArrayList<>(size);
        for (ItemRequest itemRequest: itemRequests) {
            MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource();
            mapSqlParameterSource.addValue("id", uuid.toString());
            mapSqlParameterSource.addValue("item_name", itemRequest.getItemName());
            mapSqlParameterSource.addValue("price", itemRequest.getPrice());
            mapSqlParameterSource.addValue("quantity", itemRequest.getQuantity());
            mapSqlParameterSource.addValue("order_id", orderId.toString());
            sqlParameterSources.add(mapSqlParameterSource);
        }
        this.namedParameterJdbcOperations.batchUpdate(itemSqlQuery, sqlParameterSources.toArray(new SqlParameterSource[size]));
    }

    private UUID getRandomUUID() {
        return UUID.randomUUID();
    }
}

Service#

  • Next we need to create a service named CustomerJdbcService to handle logics for create a Customer with many orders and items. The Customer will be created first, then if the Customer contains orders so orders will be created and if in every order which contains items then items will be also created. See example code below:
CustomerJdbcService.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.springboot.data.jdbc.app.service;

import com.springboot.data.jdbc.app.dao.CustomerDao;
import com.springboot.data.jdbc.app.model.request.CustomerRequest;
import com.springboot.data.jdbc.app.model.request.OrderRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.UUID;

@Service
public class CustomerJdbcService {


    @Autowired
    private CustomerDao customerDao;

    public UUID createCustomer(CustomerRequest customerRequest) {
        UUID customerId = this.customerDao.createCustomer(customerRequest);
        List<OrderRequest> orders = customerRequest.getOrders();
        if (orders != null && !orders.isEmpty()) {
            Map<UUID, OrderRequest> orderRequestMap = this.customerDao.createOrders(customerId, customerRequest.getOrders());
            for (Map.Entry<UUID, OrderRequest> map : orderRequestMap.entrySet()) {
                if (map.getValue().getItems() != null && !map.getValue().getItems().isEmpty()) {
                    this.customerDao.createItems(map.getKey(), map.getValue().getItems());
                }
            }
        }
        return customerId;
    }

}

Configuration#

  • Now we will go to database connection configuration with hikariCP. To do this, firstly you need to add these configurations below into application.yml.
application.yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# config datasource for Jpa and Jdbc
spring:
  datasource:
    driverClassName: "org.postgresql.Driver"
    url: "jdbc:postgresql://localhost:5432/sample?useUnicode=true&characterEncoding=UTF-8"
    username: "root"
    password: "password"

    #config hikari pool
    hikari:
      connection-timeout: "30000"
      maximum-pool-size: "20"
      minimumIdle: "15"
      idleTimeout: "30000"
      maxLifetime: "180000"

logging:
  level:
    org:
      hibernate:
        stat: "DEBUG"
      springframework:
        jdbc:
          core: "DEBUG"
  • To configure HikariCP we will focus on some parameters below:
Configuration Description Value Explanation
connection-timeout Maximum time to wait for a connection from the pool before throwing an exception 30000 Waits up to 30 seconds (30,000 ms) before timing out if a connection isn't available in the pool.
maximum-pool-size Maximum number of connections in the pool 20 Limits the total number of active database connections to 20.
minimumIdle Minimum number of idle connections maintained in the pool 15 Ensures at least 15 idle connections are available for use, even during low traffic periods.
idleTimeout Maximum time that a connection can remain idle in the pool before it is eligible for eviction 30000 After 30 seconds (30,000 ms) of inactivity, idle connections are eligible for removal from the pool.
maxLifetime Maximum lifetime of a connection in the pool before it is closed and replaced 180000 Connections are closed and replaced after 3 minutes (180,000 ms) to prevent stale connections from lingering too long.
### Testing
- So now, let's start your Spring Boot service then use the postman and call the test the Api /v1/jdbc/customers then we can see the successful result as below.

 #zoom

  • By default, HikariCP only creates the connection pool when the DataSource is accessed (e.g., when a database query is executed). If no code accesses the database on startup, the connection pool might not be initialized immediately. So to make sure the HikariCP connections are created we used postman to call the Api create customer first.

  • Then login into postgres database and use command below to view the HikariCP connections

1
2
3
SELECT application_name, count(*)
FROM pg_stat_activity
GROUP BY application_name;
  • Then you will be result with row PostgreSQL JDBC Driver with 15 connections count that is the same with the number we configured in the application.yaml
application_name count
DBeaver 22.2.4 - Metadata 1
DBeaver 22.2.4 - SQLEditor 1
DBeaver 22.2.4 - Metadata 1
DBeaver 22.2.4 - Main 1
PostgreSQL JDBC Driver 15
5
DBeaver 22.2.4 - Main 1

See Also#

References#