mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-21 05:21:01 +00:00
- ebean.version 17.2.0 -> 17.5.0 (minimum for TestEntityBuilder) - avaje-inject 11.5 -> 12.5 - Add io.avaje:junit:1.8 bundle to test dependencies - Remove .skipDataSourceCheck(true) from database config example - Add note about Configuration DI wiring in Step 3
296 lines
12 KiB
Markdown
296 lines
12 KiB
Markdown
# Guide: Add Ebean ORM (PostgreSQL) to an Existing Maven Project — Step 3: 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:
|
||
|
||
```java
|
||
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.
|
||
|
||
```java
|
||
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:
|
||
|
||
```java
|
||
@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)
|
||
.build();
|
||
}
|
||
```
|
||
|
||
If the project has a dedicated config-wrapper class (a `@Component` that reads config
|
||
keys), accept it as a parameter instead of `Configuration`.
|
||
|
||
> **Note:** Injecting `Configuration` requires that `avaje-config` is properly wired
|
||
> into the DI context. If you encounter "No dependency provided for
|
||
> io.avaje.config.Configuration" errors, use `Config.get(...)` static access instead
|
||
> (as shown in Step 2).
|
||
|
||
---
|
||
|
||
## 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`)
|
||
|
||
```java
|
||
@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`:
|
||
|
||
```java
|
||
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):
|
||
|
||
```java
|
||
.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](https://github.com/ebean-orm/ebean-datasource/tree/master/docs/guides/):
|
||
|
||
- **[Creating DataSource Pools](https://github.com/ebean-orm/ebean-datasource/blob/master/docs/guides/create-datasource-pool.md)** — Covers read-only pools (`readOnly(true)` + `autoCommit(true)`), Kubernetes deployment strategies using `initialConnections`, and AWS Lambda optimization
|
||
- **[AWS Aurora Read-Write Split](https://github.com/ebean-orm/ebean-datasource/blob/master/docs/guides/aws-aurora-read-write-split.md)** — Setting up dual DataSources with Aurora reader and writer endpoints, including Ebean secondary datasource routing
|
||
- **[Connection Validation Best Practices](https://github.com/ebean-orm/ebean-datasource/blob/master/docs/guides/connection-validation-best-practices.md)** — 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`.
|