JDBC Basics and Connection Pooling Explained
References
- What Is JDBC?
- (14) Dive into JDBC, Connection Pool, and Introduce H2 DB
- Design Pattern - Object Pool
- Understanding the Implementation and Principles of Database Connection Pool
- Why Is Hikari So Fast
- Difference Between spring-boot-starter-jdbc and spring-boot-starter-data-jdbc
- Spring Boot - Data Properties
Introduction
JDBC, short for Java Database Connectivity, is primarily an API standard used for connecting Java programming language to databases. You can also think of it as a library. But what exactly does this API standard encompass?
- Connecting to a database.
- Creating and executing SQL statements.
JDBC Architecture
The JDBC API provides the following interfaces and classes:
Driver Manager
: Manages a list of database drivers. It matches connection requests from Java applications with the appropriate database driver to establish a database connection.Driver
: Handles communication with the database. It is rarely communicated with directly, and instead, interactions are managed through the Driver Manager.Connection
: Provides methods for connecting to the database.Statement
: Used for executing SQL statements and returning results.ResultSet
: Used for holding the results obtained from a database query.SQLException
: This class handles any errors that occur in the database application.
You can understand the purposes of JDBC API interfaces through the following example:
1 | // Reference: https://medium.com/learning-from-jhipster/14-%E6%B7%B1%E5%85%A5-jdbc-connection-pool-%E4%B8%A6%E5%B0%8E%E5%85%A5-h2-db-939adee9c50 |
JDBC Connection
Usually, to establish a database connection, the following steps are required:
- Set up the Driver: Load the JDBC Driver for SQL Server.
- Create a Connection: (Using
DriverManager.getConnection()
) to establish a TCP socket connection. - Submit SQL: Create a
Statement
and execute SQL commands. - Close the Connection: Close the TCP socket.
However, every time we need to communicate with the database, performing these steps repeatedly can create unnecessary overhead for the system.
If we can extract those repetitive actions every time we connect, we can reduce the system’s burden and improve program execution performance. This is what we are going to discuss next - Connection Pooling.
Connection Pool
Connection Pooling is implemented using the “Object Pool Pattern.” Let’s start with a brief explanation from Wikipedia:
The object pool pattern is a creational design pattern. An object pool contains a set of initialized and ready-to-use objects that can be created and destroyed as needed. Clients can request objects from the pool, use them for processing, and return them to the pool instead of creating and destroying them directly. It’s a special kind of factory object.
In simple terms, it means pre-creating and storing some commonly used objects in a pool. When needed, you can directly take objects from the pool, avoiding the need to recreate them every time. Using an object pool can significantly improve performance. The time to get an object from the pool is predictable, whereas creating a new instance takes an uncertain amount of time.
The following diagram illustrates how clients use connections from the pool to connect to a data source. The main tasks are:
- Managing available connections.
- Allocating connections.
- Releasing connections.
Now, let’s take a look at the UML diagram:
Object Pool Class
: It is a Singleton, meaning it has a private constructor and a staticgetInstance()
method to obtain an instance of the Object Pool.acquireReusable()
: Used to get objects from the Object Pool. If there are no available objects, it will create a new one.releaseReusable()
: Used to release an object from the client’s hand and return it to the available objects.setMaxPoolSize()
: Used to set the maximum capacity of the Object Pool.
classDiagram class client class ResuablePool{ - reusable +BigDecimal balance +static getInstance() ReusablePool +acquireReusable() Reusable +releaseReusable(Reusable reusable) +setMaxPoolSize(int maxPoolSize) } client --> ResuablePool : ResusablePool.getInstance().acquareReusable()
JDBC Connection Pool Example
You can refer to Azure’s approach for a simple JDBC Connection Pool implementation. Inside, you can see how a Connection Pool is implemented.
1 | import java.sql.Connection; |
About Third-Party Connection Pool Libraries
Now that we understand how Connection Pools work, it’s important to note that there are many mature third-party libraries available for us to use. These third-party Connection Pool libraries have been optimized for performance and reducing redundant work.
The development of different third-party Connection Pools is primarily driven by performance considerations. Since JDBC has defined interfaces for JDBC Drivers, these third-party Connection Pool libraries adhere to the JDBC Driver Interface API. They all have the same API, but the difference lies in the internal implementation, which affects performance.
Currently, the most powerful and high-performance third-party Connection Pool library is HikariCP, with performance that surpasses other Connection Pool libraries by more than two times.
In the graph below:
- Connection Cycle Comparison: It shows the connection speed from
DataSource.getConnection()
toConnection.close()
. HikariCP can complete around 40,000 to 50,000 connections in less than 1 millisecond. - Statement Cycle Comparison: It shows the SQL execution speed from
Connection.prepareStatement()
toStatement.execute()
toStatement.close()
. In terms of SQL execution speed, it can complete approximately 15,000 cycles within 1 millisecond. The only other library that can keep up is Tomcat’s JDBC Connection Pool; other libraries have much lower performance.
To understand why it is so fast, you can read this article: Reasons for HikariCP’s Speed
About Third-Party Libraries Used by Spring Boot
Previously, we explained how to connect to databases using JDBC and the importance of Connection Pools. In Spring Boot, a series of commonly used tools are integrated, simplifying configuration tasks related to JDBC, Connection Pools, JPA, and more.
Now, you only need to import dependencies and configure properties to quickly and conveniently establish a connection with the database. If you want to communicate with a database using a Java application, you typically import the spring-boot-starter-jdbc
library:
1 | <dependency> |
But now it mainly uses spring-boot-starter-data-jdbc
, with an extra word for data!
1 | <dependency> |
Spring Boot Starters for Data and JDBC
When using spring-boot-starter-jdbc
or spring-boot-starter-data-jpa
starters, we automatically get a dependency on HikariCP. (See the snapshot below)
But what’s the difference between these data-related starters? If you are developing with JPA, the most commonly used one is spring-boot-starter-data-jdbc
.
spring-boot-starter-jdbc
: This starter is the most basic package. If you only need JDBC, you can import this starter.spring-boot-starter-data-jdbc
: This starter builds uponspring-boot-starter-jdbc
and provides a quick development interface forcurdRepository
. If you need to use JPA, then you should import this starter.
spring-jdbc (from: https://blog.csdn.net/assember/article/details/121976290)
1 | import javax.sql.DataSource; |
spring-data-jdbc Except for the basic database connection, it also provides CrudReporitoy
1 | import org.springframework.data.repository.CrudRepository; |
Properties Configuration
If you take a closer look at this starter’s content, you will find that it mainly includes two dependencies:
- spring-JDBC: This is Spring’s library that wraps JDBC. If you use this starter, you must configure the database connection information in the properties (application.yml).
- HikariCP: Spring Boot uses HikariCP as the default Connection Pool and includes it in
spring-boot-starter-jdbc
. (It’s highly efficient!)
1 | <dependency> |
Can refer to Spring Boot - Data Properties for configuration methods:
1 | spring: |
spring.datasource.type
Since we’ve imported spring-boot-starter-data-jdbc
, we can directly use spring.datasource.type
to configure the Connection Pool we want to use. By default, Spring Boot will automatically detect it from the classpath, and if it’s not found, it will default to HikariCP.
If you’re curious about how it auto-detects or which class names it can auto-detect, you can refer to the DataSourceBuilder.java — Source Code, or check out the code snippet below:
1 | private static <T extends DataSource> MappedDataSourceProperties<T> lookupPooled(ClassLoader classLoader, |
In this code snippet, we can see that Spring Boot automatically detects several Connection Pool implementations, with HikariCP being the default choice.
org.springframework.jdbc.datasource.SimpleDriverDataSource
com.zaxxer.hikari.HikariDataSource
org.apache.tomcat.jdbc.pool.DataSource
oracle.ucp.jdbc.PoolDataSourceImpl
oracle.jdbc.OracleConnection
- and more.
If you use the spring-boot-starter-jdbc or spring-boot-starter-data-jpa “starters”, you automatically get a dependency to HikariCP.
Regarding org.springframework.jdbc.datasource.SimpleDriverDataSource
, check its API documentation, and you’ll find the note This class is not an actual connection pool; it does not actually pool Connections. This means that this class is not a genuine Connection Pool, and it doesn’t perform Connection Pooling operations.