HikariCP on Jakarta EE

by Sascha Böhme | 567 words | ~3 min read

hikari.png

Motivation

A JDBC connection pool is essential for application servers where several parallel requests need access to a database. Especially on high load, an efficient JDBC connection pool is important to avoid locked threads, delayed request processing or partial service interruptions.

Payara, closely related to the GlassFish reference implementation of Jakarta EE (JEE), comes with its own implementation of a JDBC connection pool. Under high load our application experienced locking deficiencies when it comes to high load. Fortunately, this implementation can be replaced with a custom connection pool using standard means of Jakarta EE. HikariCP offers a fast, reliable and small implementation of a connection pool without further dependencies.

Data source definition

Jakarta EE provides the annotation DataSourceDefinition to declare a data source. This annotation can be used to inject HikariCP into the application server. The following example class WrappedDataSource.java shows how to proceed:

 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
package full.pkg.name;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.postgresql.ds.PGSimpleDataSource;

import javax.annotation.sql.DataSourceDefinition;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
 
@DataSourceDefinition(
    name = "java:app/jdbc/DefaultDb",  // the JNDI name referenced in the persistence.xml
    className = "full.pkg.name.WrappedDataSource"
)
public class WrappedDataSource implements DataSource {
 
    private final HikariDataSource hikariDataSource;
 
    /**
     * Creates a new {@link WrappedDataSource}.
     */
    public WrappedDataSource() {
        // set-up the underlying JDBC datasource for accessing the database (PostgreSQL, Oracle)
        PGSimpleDataSource ds = ...;
 
        HikariConfig hc = new HikariConfig();
        hc.setDataSource(ds);
    
        // configure HikariCP as needed
        hc.setMinimumIdle(...);  
        hc.setMaximumPoolSize(...);
        hc.setConnectionTimeout(...);
        hc.setIdleTimeout(...);
        hc.setRegisterMbeans(...);
 
        hikariDataSource = new HikariDataSource(hc);
    }
 
    @Override
    public Connection getConnection() throws SQLException {
        return hikariDataSource.getConnection();
    }
 
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return hikariDataSource.getConnection(username, password);
    }
 
    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return hikariDataSource.getLogWriter();
    }
 
    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
        hikariDataSource.setLogWriter(out);
    }
 
    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        hikariDataSource.setLoginTimeout(seconds);
    }
 
    @Override
    public int getLoginTimeout() throws SQLException {
        return hikariDataSource.getLoginTimeout();
    }
 
    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return hikariDataSource.getParentLogger();
    }
 
    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return hikariDataSource.unwrap(iface);
    }
 
    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return hikariDataSource.isWrapperFor(iface);
    }
}

Thanks to the class annotation, the application server discovers this class on start-up and initializes HikariCP. The data source can thus be referenced in the JPA configuration file persistence.xml by the declared JNDI name:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">
    <persistence-unit name="Default" transaction-type="JTA">
        <jta-data-source>java:app/jdbc/DefaultDb</jta-data-source>
 
        <!-- further configuration -->
    </persistence-unit>
</persistence>

This results in HikariCP providing all JDBC connections required by @Transactional annotations for JTA.

Alternatively, direct access to the data source is possible via injection into CDI beans:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import javax.annotation.Resource;
import javax.enterprise.context.ApplicationScoped;
import javax.sql.DataSource;

@ApplicationScoped
public class SomeClass {

    @Resource(lookup = "java:app/jdbc/DefaultDb")
    private DataSource dataSource;

}

These examples have been tested on Payara. Since the DataSourceDefinition annotation is part of Jakarta EE, the shown example code should be applicable to any application server which implements Jakarta EE.

The Latest Posts