0
# DAO Testing
1
2
Database testing utilities with automated Hibernate SessionFactory management, transaction handling, and H2 in-memory database setup. Essential for testing data access layers with isolated database environments.
3
4
## Capabilities
5
6
### DAOTest
7
8
Abstract base class providing comprehensive database testing environment with Hibernate SessionFactory management, transaction handling, and configurable database setup.
9
10
```java { .api }
11
/**
12
* Abstract base class for DAO testing with Hibernate support
13
*/
14
public abstract class DAOTest {
15
16
/**
17
* Get the Hibernate SessionFactory for database operations
18
* @return SessionFactory instance for creating sessions
19
*/
20
public SessionFactory getSessionFactory();
21
22
/**
23
* Execute code within a database transaction with return value
24
* Automatically handles transaction commit/rollback
25
* @param call Callable to execute within transaction
26
* @return Result of the callable execution
27
* @throws Exception if the callable execution fails
28
*/
29
public <T> T inTransaction(Callable<T> call) throws Exception;
30
31
/**
32
* Execute code within a database transaction without return value
33
* Automatically handles transaction commit/rollback
34
* @param action Runnable to execute within transaction
35
* @throws Exception if the action execution fails
36
*/
37
public void inTransaction(Runnable action) throws Exception;
38
39
// Lifecycle methods
40
/**
41
* Initialize the database testing environment before tests
42
* Sets up SessionFactory, creates schema, etc.
43
* @throws Exception if initialization fails
44
*/
45
public void before() throws Exception;
46
47
/**
48
* Cleanup the database testing environment after tests
49
* Closes SessionFactory, drops schema, etc.
50
* @throws Exception if cleanup fails
51
*/
52
public void after() throws Exception;
53
}
54
```
55
56
### DAOTest.Builder
57
58
Fluent builder for configuring DAOTest instances with database connection settings, Hibernate properties, entity classes, and custom configuration.
59
60
```java { .api }
61
/**
62
* Abstract builder for DAOTest configuration
63
* @param <B> Builder type for method chaining
64
*/
65
public static abstract class Builder<B extends Builder<B>> {
66
67
// Database connection configuration
68
/**
69
* Set the database URL
70
* @param url JDBC URL (default: H2 in-memory database)
71
* @return Builder instance for chaining
72
*/
73
public B setUrl(String url);
74
75
/**
76
* Set the database username
77
* @param username Database username (default: "sa")
78
* @return Builder instance for chaining
79
*/
80
public B setUsername(String username);
81
82
/**
83
* Set the database password
84
* @param password Database password (default: empty)
85
* @return Builder instance for chaining
86
*/
87
public B setPassword(String password);
88
89
// JDBC driver configuration
90
/**
91
* Set the JDBC driver class
92
* @param driver JDBC driver class (default: H2 driver)
93
* @return Builder instance for chaining
94
*/
95
public B setDriver(Class<? extends Driver> driver);
96
97
/**
98
* Set the JDBC driver class by name
99
* @param driver JDBC driver class name
100
* @return Builder instance for chaining
101
*/
102
public B setDriver(String driver);
103
104
// Hibernate configuration
105
/**
106
* Set Hibernate DDL auto mode
107
* @param hbm2ddlAuto DDL mode (create-drop, create, update, validate, none)
108
* @return Builder instance for chaining
109
*/
110
public B setHbm2DdlAuto(String hbm2ddlAuto);
111
112
/**
113
* Enable/disable SQL statement logging
114
* @param showSql true to log SQL statements, false otherwise
115
* @return Builder instance for chaining
116
*/
117
public B setShowSql(boolean showSql);
118
119
/**
120
* Enable/disable SQL comments in logged statements
121
* @param useSqlComments true to include comments, false otherwise
122
* @return Builder instance for chaining
123
*/
124
public B useSqlComments(boolean useSqlComments);
125
126
/**
127
* Enable/disable Logback logging bootstrap
128
* @param value true to bootstrap logging, false otherwise
129
* @return Builder instance for chaining
130
*/
131
public B bootstrapLogging(boolean value);
132
133
// Entity configuration
134
/**
135
* Add an entity class to the Hibernate configuration
136
* @param entityClass JPA entity class to register
137
* @return Builder instance for chaining
138
*/
139
public B addEntityClass(Class<?> entityClass);
140
141
// Custom properties
142
/**
143
* Set a custom Hibernate property
144
* @param key Property name
145
* @param value Property value
146
* @return Builder instance for chaining
147
*/
148
public B setProperty(String key, String value);
149
150
/**
151
* Add custom configuration logic
152
* @param configurationCustomizer Consumer for custom Configuration setup
153
* @return Builder instance for chaining
154
*/
155
public B customizeConfiguration(Consumer<Configuration> configurationCustomizer);
156
157
/**
158
* Build the configured DAOTest instance
159
* @return Configured DAOTest instance ready for testing
160
*/
161
public abstract DAOTest build();
162
}
163
```
164
165
**Usage Examples:**
166
167
```java
168
// Basic DAO testing with H2 in-memory database
169
DAOTest daoTest = DAOTestExtension.newBuilder()
170
.addEntityClass(User.class)
171
.addEntityClass(Order.class)
172
.build();
173
174
@BeforeEach
175
public void setUp() throws Exception {
176
daoTest.before();
177
}
178
179
@AfterEach
180
public void tearDown() throws Exception {
181
daoTest.after();
182
}
183
184
@Test
185
public void testUserDAO() throws Exception {
186
UserDAO userDAO = new UserDAO(daoTest.getSessionFactory());
187
188
// Test data creation
189
User user = daoTest.inTransaction(() -> {
190
User newUser = new User("Alice", "alice@example.com");
191
return userDAO.save(newUser);
192
});
193
194
assertThat(user.getId()).isNotNull();
195
196
// Test data retrieval
197
Optional<User> foundUser = daoTest.inTransaction(() ->
198
userDAO.findById(user.getId())
199
);
200
201
assertThat(foundUser).isPresent();
202
assertThat(foundUser.get().getName()).isEqualTo("Alice");
203
}
204
205
// Custom database configuration
206
DAOTest daoTest = DAOTestExtension.newBuilder()
207
.setUrl("jdbc:postgresql://localhost:5432/test_db")
208
.setUsername("test_user")
209
.setPassword("test_password")
210
.setDriver("org.postgresql.Driver")
211
.addEntityClass(User.class)
212
.addEntityClass(Order.class)
213
.setHbm2DdlAuto("create-drop")
214
.setShowSql(true)
215
.build();
216
217
// Multiple entity classes and custom properties
218
DAOTest daoTest = DAOTestExtension.newBuilder()
219
.addEntityClass(User.class)
220
.addEntityClass(Order.class)
221
.addEntityClass(Product.class)
222
.addEntityClass(Category.class)
223
.setProperty("hibernate.cache.use_second_level_cache", "false")
224
.setProperty("hibernate.cache.use_query_cache", "false")
225
.setProperty("hibernate.jdbc.batch_size", "25")
226
.useSqlComments(true)
227
.build();
228
229
// Custom configuration with advanced Hibernate settings
230
DAOTest daoTest = DAOTestExtension.newBuilder()
231
.addEntityClass(User.class)
232
.customizeConfiguration(config -> {
233
config.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
234
config.setProperty("hibernate.connection.isolation", "2");
235
config.setProperty("hibernate.order_inserts", "true");
236
config.setProperty("hibernate.order_updates", "true");
237
})
238
.build();
239
240
// Testing with transaction rollback for data isolation
241
@Test
242
public void testTransactionRollback() throws Exception {
243
UserDAO userDAO = new UserDAO(daoTest.getSessionFactory());
244
245
try {
246
daoTest.inTransaction(() -> {
247
User user = new User("Bob", "bob@example.com");
248
userDAO.save(user);
249
250
// Simulate error that causes rollback
251
throw new RuntimeException("Test rollback");
252
});
253
} catch (RuntimeException e) {
254
// Expected exception
255
}
256
257
// Verify rollback occurred
258
List<User> users = daoTest.inTransaction(() -> userDAO.findAll());
259
assertThat(users).isEmpty();
260
}
261
262
// Complex DAO testing with relationships
263
@Test
264
public void testUserOrderRelationship() throws Exception {
265
UserDAO userDAO = new UserDAO(daoTest.getSessionFactory());
266
OrderDAO orderDAO = new OrderDAO(daoTest.getSessionFactory());
267
268
User user = daoTest.inTransaction(() -> {
269
User newUser = new User("Charlie", "charlie@example.com");
270
return userDAO.save(newUser);
271
});
272
273
Order order = daoTest.inTransaction(() -> {
274
Order newOrder = new Order(user, "Test Product", new BigDecimal("99.99"));
275
return orderDAO.save(newOrder);
276
});
277
278
// Test relationship loading
279
User userWithOrders = daoTest.inTransaction(() ->
280
userDAO.findByIdWithOrders(user.getId())
281
);
282
283
assertThat(userWithOrders.getOrders()).hasSize(1);
284
assertThat(userWithOrders.getOrders().get(0).getId()).isEqualTo(order.getId());
285
}
286
287
// Testing DAO queries and pagination
288
@Test
289
public void testDAOQueries() throws Exception {
290
UserDAO userDAO = new UserDAO(daoTest.getSessionFactory());
291
292
// Create test data
293
daoTest.inTransaction(() -> {
294
for (int i = 1; i <= 10; i++) {
295
User user = new User("User" + i, "user" + i + "@example.com");
296
userDAO.save(user);
297
}
298
});
299
300
// Test pagination
301
List<User> firstPage = daoTest.inTransaction(() ->
302
userDAO.findAll(0, 5)
303
);
304
assertThat(firstPage).hasSize(5);
305
306
List<User> secondPage = daoTest.inTransaction(() ->
307
userDAO.findAll(5, 5)
308
);
309
assertThat(secondPage).hasSize(5);
310
311
// Test search queries
312
List<User> searchResults = daoTest.inTransaction(() ->
313
userDAO.findByEmailContaining("user1")
314
);
315
assertThat(searchResults).hasSize(2); // user1 and user10
316
}
317
```
318
319
## Common DAO Testing Patterns
320
321
### Entity Lifecycle Testing
322
323
```java
324
@Test
325
public void testEntityLifecycle() throws Exception {
326
UserDAO userDAO = new UserDAO(daoTest.getSessionFactory());
327
328
// Create
329
User user = daoTest.inTransaction(() -> {
330
User newUser = new User("Dave", "dave@example.com");
331
return userDAO.save(newUser);
332
});
333
assertThat(user.getId()).isNotNull();
334
335
// Read
336
Optional<User> foundUser = daoTest.inTransaction(() ->
337
userDAO.findById(user.getId())
338
);
339
assertThat(foundUser).isPresent();
340
341
// Update
342
daoTest.inTransaction(() -> {
343
User toUpdate = userDAO.findById(user.getId()).orElseThrow();
344
toUpdate.setEmail("dave.updated@example.com");
345
userDAO.save(toUpdate);
346
});
347
348
User updatedUser = daoTest.inTransaction(() ->
349
userDAO.findById(user.getId()).orElseThrow()
350
);
351
assertThat(updatedUser.getEmail()).isEqualTo("dave.updated@example.com");
352
353
// Delete
354
daoTest.inTransaction(() -> {
355
userDAO.delete(user.getId());
356
});
357
358
Optional<User> deletedUser = daoTest.inTransaction(() ->
359
userDAO.findById(user.getId())
360
);
361
assertThat(deletedUser).isEmpty();
362
}
363
```
364
365
### Query Performance Testing
366
367
```java
368
@Test
369
public void testQueryPerformance() throws Exception {
370
UserDAO userDAO = new UserDAO(daoTest.getSessionFactory());
371
372
// Create large dataset
373
daoTest.inTransaction(() -> {
374
for (int i = 0; i < 1000; i++) {
375
User user = new User("User" + i, "user" + i + "@example.com");
376
userDAO.save(user);
377
}
378
});
379
380
// Test query performance
381
long startTime = System.currentTimeMillis();
382
383
List<User> results = daoTest.inTransaction(() ->
384
userDAO.findActiveUsersByDomain("example.com")
385
);
386
387
long endTime = System.currentTimeMillis();
388
long duration = endTime - startTime;
389
390
assertThat(results).isNotEmpty();
391
assertThat(duration).isLessThan(1000); // Should complete within 1 second
392
}
393
```
394
395
### Constraint Violation Testing
396
397
```java
398
@Test
399
public void testUniqueConstraintViolation() {
400
UserDAO userDAO = new UserDAO(daoTest.getSessionFactory());
401
402
assertThatThrownBy(() -> {
403
daoTest.inTransaction(() -> {
404
// Create first user
405
User user1 = new User("Alice", "alice@example.com");
406
userDAO.save(user1);
407
408
// Try to create second user with same email (should violate unique constraint)
409
User user2 = new User("Alice2", "alice@example.com");
410
userDAO.save(user2);
411
});
412
}).hasCauseInstanceOf(ConstraintViolationException.class);
413
}
414
```
415
416
### Transaction Isolation Testing
417
418
```java
419
@Test
420
public void testTransactionIsolation() throws Exception {
421
UserDAO userDAO = new UserDAO(daoTest.getSessionFactory());
422
423
// Create initial data
424
User user = daoTest.inTransaction(() -> {
425
User newUser = new User("Eve", "eve@example.com");
426
return userDAO.save(newUser);
427
});
428
429
// Simulate concurrent modification
430
CountDownLatch latch = new CountDownLatch(2);
431
AtomicReference<Exception> exception = new AtomicReference<>();
432
433
// Transaction 1: Update user
434
CompletableFuture.runAsync(() -> {
435
try {
436
daoTest.inTransaction(() -> {
437
User toUpdate = userDAO.findById(user.getId()).orElseThrow();
438
toUpdate.setName("Eve Updated");
439
Thread.sleep(100); // Simulate processing time
440
userDAO.save(toUpdate);
441
});
442
} catch (Exception e) {
443
exception.set(e);
444
} finally {
445
latch.countDown();
446
}
447
});
448
449
// Transaction 2: Concurrent read
450
CompletableFuture.runAsync(() -> {
451
try {
452
User readUser = daoTest.inTransaction(() ->
453
userDAO.findById(user.getId()).orElseThrow()
454
);
455
// Should read original value due to isolation
456
assertThat(readUser.getName()).isEqualTo("Eve");
457
} catch (Exception e) {
458
exception.set(e);
459
} finally {
460
latch.countDown();
461
}
462
});
463
464
latch.await(5, TimeUnit.SECONDS);
465
466
if (exception.get() != null) {
467
throw exception.get();
468
}
469
}
470
```