0
# Dependency Injection
1
2
Runtime and compile-time dependency injection modules with support for named databases, lifecycle management, and both Guice and manual DI approaches.
3
4
## Capabilities
5
6
### Runtime Dependency Injection
7
8
Guice-based dependency injection modules for runtime binding.
9
10
```scala { .api }
11
/** DB runtime inject module */
12
final class DBModule extends SimpleModule((environment, configuration) => {
13
// Creates bindings for DBApi and named databases
14
})
15
16
/** HikariCP runtime inject module */
17
class HikariCPModule extends SimpleModule(bind[ConnectionPool].to[HikariCPConnectionPool])
18
```
19
20
**Usage Examples:**
21
22
```scala
23
import play.api.db._
24
import play.api.inject.guice.GuiceApplicationBuilder
25
26
// Enable DB module in Play application
27
val app = new GuiceApplicationBuilder()
28
.configure(
29
"db.default.driver" -> "org.h2.Driver",
30
"db.default.url" -> "jdbc:h2:mem:test"
31
)
32
.build()
33
34
// Modules are automatically loaded via play.modules.enabled
35
```
36
37
**Application Configuration:**
38
39
```hocon
40
# conf/application.conf
41
play.modules.enabled += "play.api.db.DBModule"
42
play.modules.enabled += "play.api.db.HikariCPModule"
43
44
# Or disable if not using databases
45
play.modules.disabled += "play.api.db.DBModule"
46
```
47
48
### Compile-Time Dependency Injection
49
50
Traits for compile-time dependency injection without Guice.
51
52
```scala { .api }
53
/** DB components (for compile-time injection) */
54
trait DBComponents {
55
def environment: Environment
56
def configuration: Configuration
57
def connectionPool: ConnectionPool
58
def applicationLifecycle: ApplicationLifecycle
59
60
lazy val dbApi: DBApi
61
}
62
63
/** HikariCP components (for compile-time injection) */
64
trait HikariCPComponents {
65
def environment: Environment
66
67
lazy val connectionPool: ConnectionPool
68
}
69
```
70
71
**Usage Examples:**
72
73
```scala
74
import play.api.db._
75
import play.api._
76
import play.api.inject.DefaultApplicationLifecycle
77
78
// Implement compile-time DI components
79
class MyApplicationComponents extends HikariCPComponents with DBComponents {
80
val environment = Environment.simple()
81
val configuration = Configuration.load(environment)
82
val applicationLifecycle = new DefaultApplicationLifecycle()
83
84
// HikariCPComponents provides connectionPool
85
// DBComponents provides dbApi
86
87
// Use dbApi in your application
88
lazy val userRepository = new UserRepository(dbApi.database("default"))
89
}
90
91
// Use in application loader
92
class MyApplicationLoader extends ApplicationLoader {
93
def load(context: ApplicationLoader.Context): Application = {
94
val components = new MyApplicationComponents()
95
// Build application with components
96
}
97
}
98
```
99
100
### Database Injection (Scala)
101
102
Direct injection of Database instances.
103
104
```scala { .api }
105
// Inject default database
106
class UserController @Inject()(db: Database) {
107
// Use db instance
108
}
109
110
// Inject DBApi for multiple databases
111
class MultiDbController @Inject()(dbApi: DBApi) {
112
val userDb = dbApi.database("users")
113
val logDb = dbApi.database("logs")
114
}
115
```
116
117
**Usage Examples:**
118
119
```scala
120
import play.api.db._
121
import play.api.mvc._
122
import javax.inject.Inject
123
124
// Single database injection
125
class UserController @Inject()(
126
cc: ControllerComponents,
127
db: Database
128
) extends AbstractController(cc) {
129
130
def getUser(id: Long) = Action {
131
val name = db.withConnection { implicit conn =>
132
val stmt = conn.prepareStatement("SELECT name FROM users WHERE id = ?")
133
stmt.setLong(1, id)
134
val rs = stmt.executeQuery()
135
if (rs.next()) rs.getString("name") else "Unknown"
136
}
137
Ok(name)
138
}
139
}
140
141
// Multiple database injection via DBApi
142
class AnalyticsController @Inject()(
143
cc: ControllerComponents,
144
dbApi: DBApi
145
) extends AbstractController(cc) {
146
147
def generateReport() = Action {
148
val userDb = dbApi.database("users")
149
val analyticsDb = dbApi.database("analytics")
150
151
val userCount = userDb.withConnection { implicit conn =>
152
val stmt = conn.prepareStatement("SELECT COUNT(*) FROM users")
153
val rs = stmt.executeQuery()
154
rs.next()
155
rs.getInt(1)
156
}
157
158
analyticsDb.withConnection { implicit conn =>
159
val stmt = conn.prepareStatement("INSERT INTO reports (user_count, generated_at) VALUES (?, ?)")
160
stmt.setInt(1, userCount)
161
stmt.setTimestamp(2, new java.sql.Timestamp(System.currentTimeMillis()))
162
stmt.executeUpdate()
163
}
164
165
Ok(s"Report generated: $userCount users")
166
}
167
}
168
```
169
170
### Named Database Injection (Java)
171
172
Inject specific databases by name using the @NamedDatabase annotation.
173
174
```java { .api }
175
@Qualifier
176
@Retention(RetentionPolicy.RUNTIME)
177
public @interface NamedDatabase {
178
String value();
179
}
180
```
181
182
**Usage Examples:**
183
184
```java
185
import play.db.*;
186
import javax.inject.Inject;
187
188
public class UserService {
189
private final Database userDb;
190
private final Database sessionDb;
191
private final DBApi dbApi;
192
193
@Inject
194
public UserService(
195
@NamedDatabase("users") Database userDb,
196
@NamedDatabase("sessions") Database sessionDb,
197
DBApi dbApi
198
) {
199
this.userDb = userDb;
200
this.sessionDb = sessionDb;
201
this.dbApi = dbApi;
202
}
203
204
public void createUser(String username, String email) {
205
userDb.withTransaction(connection -> {
206
PreparedStatement stmt = connection.prepareStatement(
207
"INSERT INTO users (username, email, created_at) VALUES (?, ?, ?)"
208
);
209
stmt.setString(1, username);
210
stmt.setString(2, email);
211
stmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
212
stmt.executeUpdate();
213
});
214
}
215
216
public void createUserSession(long userId, String sessionToken) {
217
sessionDb.withConnection(connection -> {
218
PreparedStatement stmt = connection.prepareStatement(
219
"INSERT INTO user_sessions (user_id, session_token, created_at) VALUES (?, ?, ?)"
220
);
221
stmt.setLong(1, userId);
222
stmt.setString(2, sessionToken);
223
stmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
224
stmt.executeUpdate();
225
});
226
}
227
228
// Access all databases when needed
229
public void performMaintenance() {
230
dbApi.getDatabases().forEach(db -> {
231
db.withConnection(connection -> {
232
PreparedStatement stmt = connection.prepareStatement("ANALYZE");
233
stmt.execute();
234
});
235
});
236
}
237
}
238
```
239
240
### Default Database Injection (Java)
241
242
Inject the default database without annotation.
243
244
```java { .api }
245
public class SimpleUserService {
246
private final Database db;
247
248
@Inject
249
public SimpleUserService(Database db) {
250
this.db = db; // Injects the default database
251
}
252
253
public Optional<User> findUser(long id) {
254
return db.withConnection(connection -> {
255
PreparedStatement stmt = connection.prepareStatement("SELECT * FROM users WHERE id = ?");
256
stmt.setLong(1, id);
257
ResultSet rs = stmt.executeQuery();
258
return rs.next() ? Optional.of(mapUser(rs)) : Optional.empty();
259
});
260
}
261
}
262
```
263
264
### Provider Classes
265
266
Provider implementations for dependency injection framework integration.
267
268
```scala { .api }
269
/** Inject provider for DB implementation of DB API */
270
@Singleton
271
class DBApiProvider(
272
environment: Environment,
273
configuration: Configuration,
274
defaultConnectionPool: ConnectionPool,
275
lifecycle: ApplicationLifecycle,
276
maybeInjector: Option[Injector]
277
) extends Provider[DBApi] {
278
lazy val get: DBApi
279
}
280
281
/** Inject provider for named databases */
282
class NamedDatabaseProvider(name: String) extends Provider[Database] {
283
@Inject private var dbApi: DBApi = _
284
lazy val get: Database = dbApi.database(name)
285
}
286
```
287
288
**Usage Examples:**
289
290
```scala
291
import play.api.db._
292
import play.api.inject._
293
294
// Manual provider usage (typically not needed)
295
val provider = new NamedDatabaseProvider("users")
296
// Inject dbApi into provider
297
val database = provider.get
298
```
299
300
### Module Configuration
301
302
Configure database modules and bindings in application configuration.
303
304
```hocon
305
# Enable/disable database modules
306
play.modules.enabled += "play.api.db.DBModule"
307
play.modules.enabled += "play.api.db.HikariCPModule"
308
309
# Disable modules if not using databases
310
play.modules.disabled += "play.api.db.DBModule"
311
312
# Database binding configuration
313
play.db.config = "db" # Configuration key for databases
314
play.db.default = "default" # Name of default database
315
play.db.pool = "hikaricp" # Default connection pool
316
317
# Multiple database configurations create named bindings
318
db {
319
default {
320
driver = "org.h2.Driver"
321
url = "jdbc:h2:mem:default"
322
}
323
324
users {
325
driver = "org.postgresql.Driver"
326
url = "jdbc:postgresql://localhost/users"
327
}
328
329
sessions {
330
driver = "org.postgresql.Driver"
331
url = "jdbc:postgresql://localhost/sessions"
332
}
333
}
334
```
335
336
## Lifecycle Management
337
338
### Automatic Lifecycle
339
340
When using dependency injection, database lifecycle is managed automatically.
341
342
```scala { .api }
343
// DBApiProvider automatically handles lifecycle
344
class DBApiProvider(..., lifecycle: ApplicationLifecycle, ...) extends Provider[DBApi] {
345
lazy val get: DBApi = {
346
val db = new DefaultDBApi(...)
347
348
// Register shutdown hook
349
lifecycle.addStopHook { () =>
350
Future.fromTry(Try(db.shutdown()))
351
}
352
353
// Initialize databases
354
db.initialize(logInitialization = environment.mode != Mode.Test)
355
db
356
}
357
}
358
```
359
360
### Manual Lifecycle
361
362
For compile-time DI or manual management:
363
364
```scala
365
import play.api.db._
366
367
// Create and initialize
368
val dbApi = new DefaultDBApi(configurations, connectionPool, environment)
369
dbApi.initialize(logInitialization = true)
370
371
try {
372
// Use databases
373
val db = dbApi.database("default")
374
// ... application logic
375
} finally {
376
// Shutdown when done
377
dbApi.shutdown()
378
}
379
```
380
381
## Testing with Dependency Injection
382
383
### Test Database Injection
384
385
```scala
386
import play.api.db._
387
import play.api.test._
388
import org.scalatest.BeforeAndAfterAll
389
390
class DatabaseSpec extends PlaySpec with BeforeAndAfterAll {
391
392
"Database injection" should {
393
"inject test database" in new WithApplication(
394
_.configure(
395
"db.default.driver" -> "org.h2.Driver",
396
"db.default.url" -> "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"
397
)
398
) {
399
val db = app.injector.instanceOf[Database]
400
401
db.withConnection { implicit conn =>
402
conn.prepareStatement("CREATE TABLE test (id INT)").execute()
403
conn.prepareStatement("INSERT INTO test VALUES (1)").execute()
404
405
val rs = conn.prepareStatement("SELECT COUNT(*) FROM test").executeQuery()
406
rs.next()
407
rs.getInt(1) must equal(1)
408
}
409
}
410
}
411
}
412
```
413
414
### Java Test Example
415
416
```java
417
import play.db.*;
418
import play.test.*;
419
import org.junit.Test;
420
import javax.inject.Inject;
421
422
public class DatabaseTest extends WithApplication {
423
424
@Inject Database db;
425
426
@Test
427
public void testDatabaseInjection() {
428
Integer count = db.withConnection(connection -> {
429
PreparedStatement stmt = connection.prepareStatement("SELECT 1");
430
ResultSet rs = stmt.executeQuery();
431
return rs.next() ? 1 : 0;
432
});
433
434
assertEquals(Integer.valueOf(1), count);
435
}
436
}
437
```
438
439
## Error Handling
440
441
### Injection Errors
442
443
- **Missing configuration**: Thrown when database configuration is missing for named databases
444
- **Circular dependencies**: Prevented by lazy initialization in providers
445
- **Module conflicts**: Thrown when conflicting modules are enabled
446
447
### Provider Errors
448
449
- **Initialization failures**: Thrown during application startup if database cannot be initialized
450
- **Connection failures**: Logged but don't prevent application startup (use `initialize()` instead of deprecated `connect()`)
451
452
## Best Practices
453
454
### Module Selection
455
456
```hocon
457
# Use specific modules based on needs
458
play.modules.enabled += "play.api.db.DBModule" # Always needed for databases
459
play.modules.enabled += "play.api.db.HikariCPModule" # Use for HikariCP (recommended)
460
461
# Alternative connection pools
462
play.modules.enabled += "play.api.db.CustomPoolModule" # Custom implementation
463
```
464
465
### Named Database Organization
466
467
```scala
468
// Organize by domain/purpose
469
@NamedDatabase("users") Database userDb
470
@NamedDatabase("analytics") Database analyticsDb
471
@NamedDatabase("cache") Database cacheDb
472
473
// Rather than generic names
474
@NamedDatabase("db1") Database db1 // Avoid
475
@NamedDatabase("db2") Database db2 // Avoid
476
```
477
478
### Compile-Time vs Runtime DI
479
480
```scala
481
// Use compile-time DI for:
482
// - Better performance (no reflection)
483
// - Compile-time error checking
484
// - Smaller application size
485
486
// Use runtime DI for:
487
// - Development convenience
488
// - Plugin/module systems
489
// - Dynamic configuration
490
```