Get connectedΒ FASTΒ withΒ Postrise, a thread-safe Java library enabling developers to acquire pooled JDBC connections from PostgreSQL. Postrise provides a simple, object-oriented solution for configuring data sources while encouraging safe database access. The event-based architecture enables subscriptions to the data source lifecycle. Connection pooling is provided by the exceptional HikariCP implementation.
π‘ Find the latest Postrise version and extra installation snippets in the Maven Central Repository.
Add the following to your pom.xml:
<properties>
<version.postrise>1.0.9</version.postrise>
</properties>
<dependency>
<groupId>org.adonix</groupId>
<artifactId>postrise</artifactId>
<version>${version.postrise}</version>
<scope>compile</scope>
</dependency>Create and configure a PostgreSQL server and data sources after Install.
SUPERUSER. Refer to the Security section for instructions on creating a NOSUPERUSER role or bypassing this behavior if necessary.
Create a Java class extending PostgresServer:
import org.adonix.postrise.DataSourceSettings;
import org.adonix.postrise.PostgresServer;
public class MyPostgresServer extends PostgresServer {
}@Override superclass methods as needed to connect to your PostgreSQL server.
/**
* Default: "localhost"
*/
@Override
public String getHostName() {
return "db.mydomain.com";
}/**
* Default: 5432
*/
@Override
public Integer getPort() {
return 5433;
}@Override
public void beforeCreate(final DataSourceSettings settings) {
// Default username is the current OS user.
settings.setUsername("my_login_user");
// Set the password or configure secure access using pg_hba.conf
// for your user.
settings.setPassword("In1g0M@nt0Ya");
// For all other settings, it is recommended to begin with
// the default values.
}π See also pg_hba.conf and HikariCP
After your server is configured, it can be instantiated. Each data source and connection pool are created on demand when a connection is requested by your application. Your new server implements the AutoCloseable interface, and all contained data sources will be closed when the server is closed. The instantiation and closure details of Postrise servers will depend on your application, but here is a simple example:
import java.sql.Connection;
import org.adonix.postrise.Server;
public class MyApp {
// Your PostgreSQL server. Also could be declared in the
// try-with-resources scope of the application.
private static final Server server = new MyPostgresServer();
public static void main(String[] args) throws Exception {
// A new data source is created on the first connection
// request to the database.
try (final Connection connection = server.getConnection("my_database")) {
// Do something with this connection.
} finally {
// Closes the server and all data sources.
server.close();
}
}
}Or if delegating to a NOLOGIN role:
try (final Connection connection = server.getConnection("my_database", "my_application_role")) {
// The current_user for this connection is now "my_application_role".
}The Postrise architecture supports subscriptions to the data source and server lifecycle.
Two interfaces are provided for data source events:
-
DataSourceListener- implementations will receive the events for all data sources. -
DatabaseListener- implementations will only receive data source events for the specified database.
Postrise servers implement the DataSourceListener interface and are automatically subscribed to their own data source events.
π‘ Additional subscribers can be added to your Server with the addListener(DataSourceListener) and addListener(DatabaseListener) methods.
Events are dispatched to each DataSourceListener in the order they were registered. Your server is always the first listener to receive notifications, followed by any additional DataSourceListener instances. If a DatabaseListener is present, it will be notified last.
Implement these events as needed (the default implementation is no-op):
| Event | Parameter | Description |
|---|---|---|
| beforeCreate | DataSourceSettings | Subscribe to this event to configure the data source. If an exception occurs during data source creation, the data source will be closed, and that exception will be thrown. π‘ This is the most common event to be implemented as it configures each data source. |
| afterCreate | DataSourceContext | The data source has been created successfully. |
| beforeClose | DataSourceContext | The data source will be closed. |
| afterClose | DataSourceContext | The data source is now closed. |
MyDatabaseListener.java
import org.adonix.postrise.DataSourceSettings;
import org.adonix.postrise.DatabaseListener;
/**
* MyDatabaseListener will only receive events for the data source with the
* specified database name.
*/
public class MyDatabaseListener implements DatabaseListener {
@Override
public String getDatabaseName() {
return "my_database";
}
@Override
public void beforeCreate(final DataSourceSettings settings) {
settings.setUsername("my_login_user");
}
}MyPostgresServer.java
import org.adonix.postrise.DataSourceSettings;
import org.adonix.postrise.PostgresServer;
public class MyPostgresServer extends PostgresServer {
public MyPostgresServer() {
/**
* Register MyDatabaseListener with MyPostgresServer.
*/
addListener(new MyDatabaseListener());
}
/**
* This event will be dispatched for all data sources. Then if the database name
* matches MyDatabaseListener, beforeCreate will be called on that listener.
*/
@Override
public void beforeCreate(final DataSourceSettings settings) {
settings.setMaxPoolSize(15);
}
}Lastly, there are server-level events that may be useful. Override these methods in your server as needed (the default implementation is no-op):
| Event | Parameter | Description |
|---|---|---|
| onInit | Called during server construction. | |
| beforeClose | Your server is closing. Dispatched before any data sources are closed. | |
| afterClose | Your server is now closed. Dispatched after all data sources are closed. | |
| onException | Exception | An unexpected Exception has occurred that should not normally be thrown β for example, while your server or data sources are in the process of closing. π‘ The exception will be logged as an error. |
If a non-privileged ROLE does not exist, create a secure PostgreSQL LOGIN role without SUPERUSER privileges:
-- Recreate if exists
DROP ROLE IF EXISTS my_login_user;
-- The LOGIN role is NOSUPERUSER.
CREATE ROLE my_login_user
LOGIN
NOSUPERUSER
NOCREATEDB
NOCREATEROLE
NOINHERIT
NOBYPASSRLS;π‘ Grant the minimally required permissions to this ROLE, or delegate those permissions to a NOLOGIN role that the LOGIN role can switch to as follows:
-- Recreate if exists
DROP ROLE IF EXISTS my_application_role;
-- The application role cannot LOGIN.
CREATE ROLE my_application_role
NOLOGIN
NOSUPERUSER
NOCREATEDB
NOCREATEROLE
NOINHERIT
NOBYPASSRLS;
-- Allow the LOGIN user to switch to this ROLE.
GRANT my_application_role TO my_login_user;
-- The application role can only SELECT from my_table.
GRANT SELECT ON my_table TO my_application_role;Use the following SQL to query the session and current users on any connection:
SELECT session_user, current_user;Example result set:
| session_user | current_user |
|---|---|
| my_login_user | my_application_role |
π See also Database Roles, Grant
If SUPERUSER connections are absolutely required, disable Postrise ROLE security as follows:
import static org.adonix.postrise.security.RoleSecurityProvider.DISABLE_ROLE_SECURITY;
import org.adonix.postrise.DataSourceSettings;
import org.adonix.postrise.PostgresServer;
public class MyPostgresServer extends PostgresServer {
@Override
public void beforeCreate(final DataSourceSettings settings) {
settings.setRoleSecurity(DISABLE_ROLE_SECURITY);
}
}Built-in ROLE security settings are provided by RoleSecurityProvider:
| Name | Description |
|---|---|
| POSTGRES_DEFAULT_ROLE_SECURITY | An exception will be thrown if logging in as a SUPERUSER. No check is performed when getting a connection with a different ROLE. |
| POSTGRES_STRICT_ROLE_SECURITY | An exception will be thrown if logging in as a SUPERUSER or when switching from the LOGIN user to a different ROLE with SUPERUSER. |
| DISABLE_ROLE_SECURITY | No security checks are performed on any ROLE. Use this setting only if SUPERUSER is required. |
π‘ Custom security can be created by implementing the RoleSecurityListener interface.
If your application does not use PostgreSQL roles, performance will be improved by disabling Postrise ROLE management on your server as follows:
import org.adonix.postrise.PostgresDataSourceNoRoles;
import org.adonix.postrise.PostgresServer;
public class MyPostgresServer extends PostgresServer {
@Override
protected PostgresDataSource createDataSource(final String databaseName) {
return new PostgresDataSourceNoRoles(this, databaseName);
}
}UnsupportedOperationException will be thrown when attempting to acquire a connection from Postrise with a given ROLE.
Postrise is a pure Java library that can easily be cloned and built locally.
The following prerequisites must be installed before building:
- JDK 11+ - the latest Long-Term Support (LTS) version is JDK 21.
- Maven - may already be installed with your IDE.
- Docker - installed and running to perform JUnit 5 tests.
π‘ Before continuing, use this command to verify the expected Maven and Java versions are on your PATH:
mvn -vThe output should look similar to this with variances for OS and versions:
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: /Users/Inigo/Programs/apache-maven-3.9.9
Java version: 23, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk-23.jdk/Contents/Home
Default locale: en_US, platform encoding: UTF-8
OS name: "mac os x", version: "15.3.2", arch: "aarch64", family: "mac"Create a working directory for the Postrise project and use the command line to clone the repository into it:
git clone https://github.com/adonix-org/Postrise.gitNext use that same command-line to switch to the Postrise folder:
cd PostriseFinally, run this Maven command to build and test Postrise:
mvn clean verifyOr use this Maven command to build, test, and install Postrise:
mvn clean installAll notable changes to Postrise will be documented here.
