Play Framework JDBC support library providing database access, connection pooling, and database configuration management for Play applications.
—
Runtime and compile-time dependency injection modules with support for named databases, lifecycle management, and both Guice and manual DI approaches.
Guice-based dependency injection modules for runtime binding.
/** DB runtime inject module */
final class DBModule extends SimpleModule((environment, configuration) => {
// Creates bindings for DBApi and named databases
})
/** HikariCP runtime inject module */
class HikariCPModule extends SimpleModule(bind[ConnectionPool].to[HikariCPConnectionPool])Usage Examples:
import play.api.db._
import play.api.inject.guice.GuiceApplicationBuilder
// Enable DB module in Play application
val app = new GuiceApplicationBuilder()
.configure(
"db.default.driver" -> "org.h2.Driver",
"db.default.url" -> "jdbc:h2:mem:test"
)
.build()
// Modules are automatically loaded via play.modules.enabledApplication Configuration:
# conf/application.conf
play.modules.enabled += "play.api.db.DBModule"
play.modules.enabled += "play.api.db.HikariCPModule"
# Or disable if not using databases
play.modules.disabled += "play.api.db.DBModule"Traits for compile-time dependency injection without Guice.
/** DB components (for compile-time injection) */
trait DBComponents {
def environment: Environment
def configuration: Configuration
def connectionPool: ConnectionPool
def applicationLifecycle: ApplicationLifecycle
lazy val dbApi: DBApi
}
/** HikariCP components (for compile-time injection) */
trait HikariCPComponents {
def environment: Environment
lazy val connectionPool: ConnectionPool
}Usage Examples:
import play.api.db._
import play.api._
import play.api.inject.DefaultApplicationLifecycle
// Implement compile-time DI components
class MyApplicationComponents extends HikariCPComponents with DBComponents {
val environment = Environment.simple()
val configuration = Configuration.load(environment)
val applicationLifecycle = new DefaultApplicationLifecycle()
// HikariCPComponents provides connectionPool
// DBComponents provides dbApi
// Use dbApi in your application
lazy val userRepository = new UserRepository(dbApi.database("default"))
}
// Use in application loader
class MyApplicationLoader extends ApplicationLoader {
def load(context: ApplicationLoader.Context): Application = {
val components = new MyApplicationComponents()
// Build application with components
}
}Direct injection of Database instances.
// Inject default database
class UserController @Inject()(db: Database) {
// Use db instance
}
// Inject DBApi for multiple databases
class MultiDbController @Inject()(dbApi: DBApi) {
val userDb = dbApi.database("users")
val logDb = dbApi.database("logs")
}Usage Examples:
import play.api.db._
import play.api.mvc._
import javax.inject.Inject
// Single database injection
class UserController @Inject()(
cc: ControllerComponents,
db: Database
) extends AbstractController(cc) {
def getUser(id: Long) = Action {
val name = db.withConnection { implicit conn =>
val stmt = conn.prepareStatement("SELECT name FROM users WHERE id = ?")
stmt.setLong(1, id)
val rs = stmt.executeQuery()
if (rs.next()) rs.getString("name") else "Unknown"
}
Ok(name)
}
}
// Multiple database injection via DBApi
class AnalyticsController @Inject()(
cc: ControllerComponents,
dbApi: DBApi
) extends AbstractController(cc) {
def generateReport() = Action {
val userDb = dbApi.database("users")
val analyticsDb = dbApi.database("analytics")
val userCount = userDb.withConnection { implicit conn =>
val stmt = conn.prepareStatement("SELECT COUNT(*) FROM users")
val rs = stmt.executeQuery()
rs.next()
rs.getInt(1)
}
analyticsDb.withConnection { implicit conn =>
val stmt = conn.prepareStatement("INSERT INTO reports (user_count, generated_at) VALUES (?, ?)")
stmt.setInt(1, userCount)
stmt.setTimestamp(2, new java.sql.Timestamp(System.currentTimeMillis()))
stmt.executeUpdate()
}
Ok(s"Report generated: $userCount users")
}
}Inject specific databases by name using the @NamedDatabase annotation.
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface NamedDatabase {
String value();
}Usage Examples:
import play.db.*;
import javax.inject.Inject;
public class UserService {
private final Database userDb;
private final Database sessionDb;
private final DBApi dbApi;
@Inject
public UserService(
@NamedDatabase("users") Database userDb,
@NamedDatabase("sessions") Database sessionDb,
DBApi dbApi
) {
this.userDb = userDb;
this.sessionDb = sessionDb;
this.dbApi = dbApi;
}
public void createUser(String username, String email) {
userDb.withTransaction(connection -> {
PreparedStatement stmt = connection.prepareStatement(
"INSERT INTO users (username, email, created_at) VALUES (?, ?, ?)"
);
stmt.setString(1, username);
stmt.setString(2, email);
stmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
stmt.executeUpdate();
});
}
public void createUserSession(long userId, String sessionToken) {
sessionDb.withConnection(connection -> {
PreparedStatement stmt = connection.prepareStatement(
"INSERT INTO user_sessions (user_id, session_token, created_at) VALUES (?, ?, ?)"
);
stmt.setLong(1, userId);
stmt.setString(2, sessionToken);
stmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
stmt.executeUpdate();
});
}
// Access all databases when needed
public void performMaintenance() {
dbApi.getDatabases().forEach(db -> {
db.withConnection(connection -> {
PreparedStatement stmt = connection.prepareStatement("ANALYZE");
stmt.execute();
});
});
}
}Inject the default database without annotation.
public class SimpleUserService {
private final Database db;
@Inject
public SimpleUserService(Database db) {
this.db = db; // Injects the default database
}
public Optional<User> findUser(long id) {
return db.withConnection(connection -> {
PreparedStatement stmt = connection.prepareStatement("SELECT * FROM users WHERE id = ?");
stmt.setLong(1, id);
ResultSet rs = stmt.executeQuery();
return rs.next() ? Optional.of(mapUser(rs)) : Optional.empty();
});
}
}Provider implementations for dependency injection framework integration.
/** Inject provider for DB implementation of DB API */
@Singleton
class DBApiProvider(
environment: Environment,
configuration: Configuration,
defaultConnectionPool: ConnectionPool,
lifecycle: ApplicationLifecycle,
maybeInjector: Option[Injector]
) extends Provider[DBApi] {
lazy val get: DBApi
}
/** Inject provider for named databases */
class NamedDatabaseProvider(name: String) extends Provider[Database] {
@Inject private var dbApi: DBApi = _
lazy val get: Database = dbApi.database(name)
}Usage Examples:
import play.api.db._
import play.api.inject._
// Manual provider usage (typically not needed)
val provider = new NamedDatabaseProvider("users")
// Inject dbApi into provider
val database = provider.getConfigure database modules and bindings in application configuration.
# Enable/disable database modules
play.modules.enabled += "play.api.db.DBModule"
play.modules.enabled += "play.api.db.HikariCPModule"
# Disable modules if not using databases
play.modules.disabled += "play.api.db.DBModule"
# Database binding configuration
play.db.config = "db" # Configuration key for databases
play.db.default = "default" # Name of default database
play.db.pool = "hikaricp" # Default connection pool
# Multiple database configurations create named bindings
db {
default {
driver = "org.h2.Driver"
url = "jdbc:h2:mem:default"
}
users {
driver = "org.postgresql.Driver"
url = "jdbc:postgresql://localhost/users"
}
sessions {
driver = "org.postgresql.Driver"
url = "jdbc:postgresql://localhost/sessions"
}
}When using dependency injection, database lifecycle is managed automatically.
// DBApiProvider automatically handles lifecycle
class DBApiProvider(..., lifecycle: ApplicationLifecycle, ...) extends Provider[DBApi] {
lazy val get: DBApi = {
val db = new DefaultDBApi(...)
// Register shutdown hook
lifecycle.addStopHook { () =>
Future.fromTry(Try(db.shutdown()))
}
// Initialize databases
db.initialize(logInitialization = environment.mode != Mode.Test)
db
}
}For compile-time DI or manual management:
import play.api.db._
// Create and initialize
val dbApi = new DefaultDBApi(configurations, connectionPool, environment)
dbApi.initialize(logInitialization = true)
try {
// Use databases
val db = dbApi.database("default")
// ... application logic
} finally {
// Shutdown when done
dbApi.shutdown()
}import play.api.db._
import play.api.test._
import org.scalatest.BeforeAndAfterAll
class DatabaseSpec extends PlaySpec with BeforeAndAfterAll {
"Database injection" should {
"inject test database" in new WithApplication(
_.configure(
"db.default.driver" -> "org.h2.Driver",
"db.default.url" -> "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"
)
) {
val db = app.injector.instanceOf[Database]
db.withConnection { implicit conn =>
conn.prepareStatement("CREATE TABLE test (id INT)").execute()
conn.prepareStatement("INSERT INTO test VALUES (1)").execute()
val rs = conn.prepareStatement("SELECT COUNT(*) FROM test").executeQuery()
rs.next()
rs.getInt(1) must equal(1)
}
}
}
}import play.db.*;
import play.test.*;
import org.junit.Test;
import javax.inject.Inject;
public class DatabaseTest extends WithApplication {
@Inject Database db;
@Test
public void testDatabaseInjection() {
Integer count = db.withConnection(connection -> {
PreparedStatement stmt = connection.prepareStatement("SELECT 1");
ResultSet rs = stmt.executeQuery();
return rs.next() ? 1 : 0;
});
assertEquals(Integer.valueOf(1), count);
}
}initialize() instead of deprecated connect())# Use specific modules based on needs
play.modules.enabled += "play.api.db.DBModule" # Always needed for databases
play.modules.enabled += "play.api.db.HikariCPModule" # Use for HikariCP (recommended)
# Alternative connection pools
play.modules.enabled += "play.api.db.CustomPoolModule" # Custom implementation// Organize by domain/purpose
@NamedDatabase("users") Database userDb
@NamedDatabase("analytics") Database analyticsDb
@NamedDatabase("cache") Database cacheDb
// Rather than generic names
@NamedDatabase("db1") Database db1 // Avoid
@NamedDatabase("db2") Database db2 // Avoid// Use compile-time DI for:
// - Better performance (no reflection)
// - Compile-time error checking
// - Smaller application size
// Use runtime DI for:
// - Development convenience
// - Plugin/module systems
// - Dynamic configurationInstall with Tessl CLI
npx tessl i tessl/maven-com-typesafe-play--play-jdbc-2-11