Skip to content

Latest commit

 

History

History
291 lines (226 loc) · 11.5 KB

File metadata and controls

291 lines (226 loc) · 11.5 KB

Guide: Add Ebean ORM (PostgreSQL) to an Existing Maven Project — Step 2: Database Configuration

Purpose

This guide provides step-by-step instructions for configuring an Ebean Database bean using Avaje Inject (@Factory / @Bean), backed by a PostgreSQL datasource built with Ebean's DataSourceBuilder. Follow every step in order. This is Step 3 of 3.


Prerequisites

  • Step 1 complete: pom.xml already includes ebean-postgres, ebean-maven-plugin, and querybean-generator (see add-ebean-postgres-maven-pom.md)
  • Step 2 complete: Test container setup is working and mvn verify passes (see add-ebean-postgres-test-container.md)
  • Avaje Inject is on the classpath (e.g. io.avaje:avaje-inject)
  • A configuration source is available at runtime (e.g. avaje-config reading application.yml or environment variables)
  • The following configuration keys are resolvable at runtime (adapt names to your project):
    Key Description
    db_url JDBC URL for the master/write connection
    db_user Database username
    db_pass Database password
    db_master_min_connections Minimum pool size (default: 1)
    db_master_initial_connections Initial pool size at startup — set high to pre-warm on pod start (see K8s note below)
    db_master_max_connections Maximum pool size (default: 200)

Step 1 — Locate or create the @Factory class

Look for an existing Avaje Inject @Factory-annotated class in the project (often named AppConfig, DatabaseConfig, or similar). If one exists, add the new @Bean method to it. If none exists, create one:

package com.example.configuration;

import io.avaje.inject.Bean;
import io.avaje.inject.Factory;

@Factory
class DatabaseConfig {
    // beans will be added in the steps below
}

Step 2 — Add the Database bean method (minimal — master datasource only)

Add the following @Bean method to the @Factory class. This creates an Ebean Database backed by a single master (read-write) PostgreSQL datasource.

import io.ebean.Database;
import io.ebean.datasource.DataSourceBuilder;

@Bean
Database database() {
    var dataSource = DataSourceBuilder.create()
        .url(/* resolve from config, e.g.: */ Config.get("db_url"))
        .username(Config.get("db_user"))
        .password(Config.get("db_pass"))
        .driver("org.postgresql.Driver")
        .schema("myschema")                    // set to your target schema
        .applicationName("my-app")             // visible in pg_stat_activity
        .minConnections(Config.getInt("db_master_min_connections", 1))
        .initialConnections(Config.getInt("db_master_initial_connections", 10))
        .maxConnections(Config.getInt("db_master_max_connections", 200));

    return Database.builder()
        .name("db")                            // logical name for this Database instance
        .dataSourceBuilder(dataSource)
        .build();
}

Field guidance

Field Notes
url Full JDBC URL, e.g. jdbc:postgresql://host:5432/dbname
schema The Postgres schema Ebean should use (omit if using public)
applicationName Shown in pg_stat_activity.application_name; helps with DB-side diagnostics
name("db") Logical Ebean database name; relevant if multiple Database instances exist
minConnections Connections kept open at all times; pool will not shrink below this
initialConnections Connections opened at startup; see K8s warm-up note below
maxConnections Hard upper limit on concurrent connections

Connection pool sizing for Kubernetes (and similar orchestrated environments)

When a pod starts in Kubernetes it will receive live traffic as soon as it passes readiness checks — often before the connection pool has had a chance to grow to handle the load. This can cause latency spikes on the first wave of requests while the pool expands one connection at a time.

Use initialConnections to pre-warm the pool at startup so it is already sized for peak load when the pod goes live:

minConnections:     2    ← floor; pool will shrink back here when idle
initialConnections: 20   ← opened at pod start, before first request arrives
maxConnections:     50   ← hard ceiling

The lifecycle is:

  1. Pod starts — pool opens initialConnections connections immediately.
  2. Pod receives traffic — pool is already at capacity; no growth latency.
  3. Traffic drops — idle connections are closed; pool trims back toward minConnections.
  4. Next traffic spike — pool grows again up to maxConnections on demand.

Set initialConnections to a value high enough that the pool does not need to grow during the first minute of live traffic. A common starting point is 50–75% of maxConnections.


Step 3 — Inject configuration via a constructor or config helper (recommended)

Rather than calling Config.get(...) inline, inject a typed config helper or the Avaje Configuration bean if one is available. This makes the factory testable and keeps the wiring explicit. For example:

@Bean
Database database(Configuration config) {
    String url  = config.get("db_url");
    String user = config.get("db_user");
    String pass = config.get("db_pass");
    int    min  = config.getInt("db_master_min_connections", 1);
    int    init = config.getInt("db_master_initial_connections", 10);
    int    max  = config.getInt("db_master_max_connections", 200);

    var dataSource = DataSourceBuilder.create()
        .url(url)
        .username(user)
        .password(pass)
        .driver("org.postgresql.Driver")
        .schema("myschema")
        .applicationName("my-app")
        .minConnections(min)
        .initialConnections(init)
        .maxConnections(max);

    return Database.builder()
        .name("db")
        .dataSourceBuilder(dataSource)
        .skipDataSourceCheck(true)
        .build();
}

If the project has a dedicated config-wrapper class (a @Component that reads config keys), accept it as a parameter instead of Configuration.


Step 4 (Optional) — Add a read-only datasource

For production services that have a separate read-replica, add a second DataSourceBuilder for read-only queries and wire it via readOnlyDataSourceBuilder(...). The read-only datasource:

  • Uses readOnly(true) and autoCommit(true) (Ebean routes read queries there automatically)
  • Typically has a higher max connection count than the master
  • Benefits from a prepared-statement cache (pstmtCacheSize)
@Bean
Database database(Configuration config) {
    String masterUrl   = config.get("db_url");
    String readOnlyUrl = config.get("db_url_readonly");
    String user        = config.get("db_user");
    String pass        = config.get("db_pass");

    var masterDataSource = buildDataSource(user, pass)
        .url(masterUrl)
        .minConnections(config.getInt("db_master_min_connections", 1))
        .initialConnections(config.getInt("db_master_initial_connections", 10))
        .maxConnections(config.getInt("db_master_max_connections", 50));

    var readOnlyDataSource = buildDataSource(user, pass)
        .url(readOnlyUrl)
        .readOnly(true)
        .autoCommit(true)
        .pstmtCacheSize(250)          // cache up to 250 prepared statements per connection
        .maxInactiveTimeSecs(600)     // close idle connections after 10 minutes
        .minConnections(config.getInt("db_readonly_min_connections", 2))
        .initialConnections(config.getInt("db_readonly_initial_connections", 10))
        .maxConnections(config.getInt("db_readonly_max_connections", 200));

    return Database.builder()
        .name("db")
        .dataSourceBuilder(masterDataSource)
        .readOnlyDataSourceBuilder(readOnlyDataSource)
        .build();
}

private static DataSourceBuilder buildDataSource(String user, String pass) {
    return DataSourceBuilder.create()
        .username(user)
        .password(pass)
        .driver("org.postgresql.Driver")
        .schema("myschema")
        .applicationName("my-app")
        .addProperty("prepareThreshold", "2");   // PostgreSQL: server-side prepared statements
}

Additional configuration keys for the read-only datasource

Key Description Default
db_url_readonly JDBC URL for the read replica
db_master_initial_connections Initial master pool size at startup 10
db_readonly_min_connections Minimum pool size 2
db_readonly_initial_connections Initial pool size at startup same as min
db_readonly_max_connections Maximum pool size 20

Step 5 (Optional) — Enable the migration runner

If the project uses Ebean's built-in DB migration runner to apply SQL migrations on startup, enable it on the DatabaseBuilder:

return Database.builder()
    .name("db")
    .dataSourceBuilder(dataSource)
    .runMigration(true)          // run pending migrations on startup
    .build();

This is equivalent to setting ebean.migration.run=true in application.properties but is preferred because it keeps all database configuration in one place. To make it conditional (e.g. only in non-production environments):

.runMigration(config.getBoolean("db.runMigrations", false))

See the DB migration generation guide (add-ebean-db-migration-generation.md) for full details on generating and managing migration files.


See Also

For advanced connection pool configuration, production deployment patterns, and connection validation best practices, see the ebean-datasource guides:

  • Creating DataSource Pools — Covers read-only pools (readOnly(true) + autoCommit(true)), Kubernetes deployment strategies using initialConnections, and AWS Lambda optimization
  • AWS Aurora Read-Write Split — Setting up dual DataSources with Aurora reader and writer endpoints, including Ebean secondary datasource routing
  • Connection Validation Best Practices — Why Connection.isValid() is the recommended default and when (rarely) explicit heartbeatSql is needed

Verification

  1. Start the application (or run mvn test -pl <your-module>).

  2. Look for log output similar to:

    INFO  o.a.datasource.pool.ConnectionPool - DataSourcePool [db] autoCommit[false] min[1] max[5]
    INFO  io.ebean.internal.DefaultContainer - DatabasePlatform name:db platform:postgres
    
  3. If you see DataSourcePool and DatabasePlatform log lines, Ebean is connected and the database bean is wired correctly.


Troubleshooting

Symptom Likely cause Fix
ClassNotFoundException: org.postgresql.Driver PostgreSQL JDBC driver missing Add org.postgresql:postgresql dependency (see Step 1 guide)
Cannot connect to database at startup DB unreachable but skipDataSourceCheck is false Set .skipDataSourceCheck(true)
Ebean enhancement warnings in logs ebean-maven-plugin not configured Complete Step 1 guide
NullPointerException reading config key Config key not defined Add the key to application.yml or environment

Related

The test container setup (Step 2) should already be complete and passing before this step. See add-ebean-postgres-test-container.md.