0
# Modern Fluent Client
1
2
The `JdbcClient` interface, introduced in Spring Framework 6.1, provides a modern, fluent API that unifies JDBC operations under a single, intuitive interface. It offers method chaining, type-safe parameter binding, and streamlined query and update operations while maintaining all the benefits of Spring's JDBC abstraction.
3
4
## Core Imports
5
6
```java
7
import org.springframework.jdbc.core.simple.JdbcClient;
8
import org.springframework.jdbc.core.RowMapper;
9
import org.springframework.jdbc.core.ResultSetExtractor;
10
import org.springframework.jdbc.core.RowCallbackHandler;
11
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
12
import org.springframework.jdbc.support.KeyHolder;
13
import org.springframework.jdbc.support.GeneratedKeyHolder;
14
import org.springframework.jdbc.support.rowset.SqlRowSet;
15
import javax.sql.DataSource;
16
```
17
18
## Capabilities
19
20
### JdbcClient Creation
21
22
Factory methods for creating JdbcClient instances from DataSource or existing JdbcOperations.
23
24
```java { .api }
25
/**
26
* Modern fluent JDBC client providing unified database access API
27
* Introduced in Spring 6.1 as the recommended approach for new applications
28
*/
29
public interface JdbcClient {
30
/** Create JdbcClient from DataSource */
31
static JdbcClient create(DataSource dataSource);
32
33
/** Create JdbcClient from existing JdbcOperations */
34
static JdbcClient create(JdbcOperations jdbcOperations);
35
36
/** Start building SQL statement with fluent API */
37
StatementSpec sql(String sql);
38
}
39
```
40
41
**Usage Examples:**
42
43
```java
44
// Create from DataSource
45
DataSource dataSource = // ... obtain DataSource
46
JdbcClient jdbcClient = JdbcClient.create(dataSource);
47
48
// Create from existing JdbcTemplate
49
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
50
JdbcClient jdbcClient = JdbcClient.create(jdbcTemplate);
51
52
// Basic usage pattern
53
List<User> users = jdbcClient
54
.sql("SELECT id, name, email FROM users WHERE active = ?")
55
.param(true)
56
.query(User.class)
57
.list();
58
```
59
60
### Statement Specification
61
62
Fluent interface for building SQL statements with parameter binding and execution options.
63
64
```java { .api }
65
/**
66
* Fluent interface for building SQL statements
67
*/
68
public interface StatementSpec {
69
// Parameter binding - positional
70
StatementSpec param(Object value);
71
StatementSpec param(int jdbcIndex, Object value);
72
StatementSpec param(int jdbcIndex, Object value, int sqlType);
73
StatementSpec params(Object... values);
74
StatementSpec params(List<?> values);
75
76
// Parameter binding - named
77
StatementSpec param(String name, Object value);
78
StatementSpec param(String name, Object value, int sqlType);
79
StatementSpec params(Map<String, ?> paramMap);
80
StatementSpec paramSource(Object namedParamObject);
81
StatementSpec paramSource(SqlParameterSource paramSource);
82
83
// Query operations
84
ResultQuerySpec query();
85
<T> MappedQuerySpec<T> query(Class<T> mappedClass);
86
<T> MappedQuerySpec<T> query(RowMapper<T> rowMapper);
87
void query(RowCallbackHandler rch);
88
<T> T query(ResultSetExtractor<T> rse);
89
90
// Update operations
91
int update();
92
int update(KeyHolder generatedKeyHolder);
93
int update(KeyHolder generatedKeyHolder, String... keyColumnNames);
94
}
95
```
96
97
**Usage Examples:**
98
99
```java
100
// Positional parameters
101
User user = jdbcClient
102
.sql("SELECT * FROM users WHERE id = ? AND active = ?")
103
.param(123)
104
.param(true)
105
.query(User.class)
106
.single();
107
108
// Multiple parameters at once
109
List<User> users = jdbcClient
110
.sql("SELECT * FROM users WHERE department = ? AND salary > ? AND active = ?")
111
.params("Engineering", 50000, true)
112
.query(User.class)
113
.list();
114
115
// Named parameters
116
List<Order> orders = jdbcClient
117
.sql("SELECT * FROM orders WHERE user_id = :userId AND status = :status")
118
.param("userId", 123)
119
.param("status", "COMPLETED")
120
.query(Order.class)
121
.list();
122
123
// Map-based parameters
124
Map<String, Object> params = Map.of(
125
"minDate", LocalDate.of(2024, 1, 1),
126
"maxDate", LocalDate.of(2024, 12, 31),
127
"status", "ACTIVE"
128
);
129
130
List<User> activeUsers = jdbcClient
131
.sql("SELECT * FROM users WHERE created_date BETWEEN :minDate AND :maxDate AND status = :status")
132
.params(params)
133
.query(User.class)
134
.list();
135
```
136
137
### Query Operations
138
139
Fluent query interface supporting various result types and mapping strategies.
140
141
```java { .api }
142
/**
143
* Interface for simple result queries returning raw database data
144
*/
145
public interface ResultQuerySpec {
146
SqlRowSet rowSet();
147
List<Map<String, Object>> listOfRows();
148
Map<String, Object> singleRow();
149
List<Object> singleColumn();
150
Object singleValue();
151
Optional<Object> optionalValue();
152
}
153
154
/**
155
* Fluent interface for query operations with type-safe result mapping
156
*/
157
public interface MappedQuerySpec<T> {
158
// Single result
159
T single();
160
Optional<T> optional();
161
162
// Multiple results
163
List<T> list();
164
Set<T> set();
165
Stream<T> stream();
166
167
// Custom result processing
168
<R> R extract(ResultSetExtractor<R> extractor);
169
void forEach(RowCallbackHandler rch);
170
}
171
```
172
173
**Usage Examples:**
174
175
```java
176
// Single result - throws exception if not exactly one row
177
User user = jdbcClient
178
.sql("SELECT * FROM users WHERE email = ?")
179
.param("john@example.com")
180
.query(User.class)
181
.single();
182
183
// Optional result - returns Optional.empty() if no rows
184
Optional<User> userOpt = jdbcClient
185
.sql("SELECT * FROM users WHERE email = ?")
186
.param("unknown@example.com")
187
.query(User.class)
188
.optional();
189
190
// List results
191
List<User> activeUsers = jdbcClient
192
.sql("SELECT * FROM users WHERE active = ?")
193
.param(true)
194
.query(User.class)
195
.list();
196
197
// Stream for large result sets
198
jdbcClient
199
.sql("SELECT * FROM users WHERE department = ?")
200
.param("Engineering")
201
.query(User.class)
202
.stream()
203
.filter(user -> user.getSalary() > 75000)
204
.forEach(this::processHighEarner);
205
206
// Custom row mapper
207
List<UserSummary> summaries = jdbcClient
208
.sql("SELECT id, name, email FROM users WHERE active = ?")
209
.param(true)
210
.query((rs, rowNum) -> new UserSummary(
211
rs.getLong("id"),
212
rs.getString("name"),
213
rs.getString("email")
214
))
215
.list();
216
217
// Map-based results for dynamic queries
218
List<Map<String, Object>> results = jdbcClient
219
.sql("SELECT * FROM " + tableName + " WHERE created_date > ?")
220
.param(cutoffDate)
221
.query()
222
.list();
223
224
// Custom ResultSetExtractor
225
Map<String, Integer> departmentCounts = jdbcClient
226
.sql("SELECT department, COUNT(*) as count FROM users GROUP BY department")
227
.query()
228
.extract(rs -> {
229
Map<String, Integer> counts = new HashMap<>();
230
while (rs.next()) {
231
counts.put(rs.getString("department"), rs.getInt("count"));
232
}
233
return counts;
234
});
235
```
236
237
### Update Operations
238
239
JdbcClient provides direct update operations through the StatementSpec interface.
240
241
**Usage Examples:**
242
243
```java
244
// Simple update
245
int rowsUpdated = jdbcClient
246
.sql("UPDATE users SET last_login = ? WHERE id = ?")
247
.param(Timestamp.from(Instant.now()))
248
.param(123)
249
.update();
250
251
// Insert with key generation
252
KeyHolder keyHolder = new GeneratedKeyHolder();
253
int rowsInserted = jdbcClient
254
.sql("INSERT INTO users (name, email, department) VALUES (?, ?, ?)")
255
.param("Jane Doe")
256
.param("jane@example.com")
257
.param("Marketing")
258
.update(keyHolder);
259
Long newUserId = keyHolder.getKey().longValue();
260
261
// Named parameter insert with specific key columns
262
KeyHolder keyHolder = new GeneratedKeyHolder();
263
int rowsInserted = jdbcClient
264
.sql("INSERT INTO orders (user_id, product_id, quantity) VALUES (:userId, :productId, :quantity)")
265
.param("userId", 123)
266
.param("productId", 456)
267
.param("quantity", 2)
268
.update(keyHolder, "order_id", "created_date");
269
```
270
271
272
### Integration Patterns
273
274
Common patterns for integrating JdbcClient with existing Spring JDBC components.
275
276
**Usage Examples:**
277
278
```java
279
// Repository pattern with JdbcClient
280
@Repository
281
public class UserRepository {
282
private final JdbcClient jdbcClient;
283
284
public UserRepository(DataSource dataSource) {
285
this.jdbcClient = JdbcClient.create(dataSource);
286
}
287
288
public Optional<User> findById(Long id) {
289
return jdbcClient
290
.sql("SELECT * FROM users WHERE id = ?")
291
.param(id)
292
.query(User.class)
293
.optional();
294
}
295
296
public List<User> findByDepartment(String department) {
297
return jdbcClient
298
.sql("SELECT * FROM users WHERE department = ?")
299
.param(department)
300
.query(User.class)
301
.list();
302
}
303
304
public User save(User user) {
305
if (user.getId() == null) {
306
KeyHolder keyHolder = jdbcClient
307
.sql("INSERT INTO users (name, email, department) VALUES (?, ?, ?)")
308
.params(user.getName(), user.getEmail(), user.getDepartment())
309
.update()
310
.keys();
311
user.setId(keyHolder.getKey().longValue());
312
} else {
313
jdbcClient
314
.sql("UPDATE users SET name = ?, email = ?, department = ? WHERE id = ?")
315
.params(user.getName(), user.getEmail(), user.getDepartment(), user.getId())
316
.update()
317
.rows();
318
}
319
return user;
320
}
321
322
public void deleteById(Long id) {
323
jdbcClient
324
.sql("DELETE FROM users WHERE id = ?")
325
.param(id)
326
.update()
327
.rows();
328
}
329
}
330
331
// Service layer usage
332
@Service
333
@Transactional
334
public class UserService {
335
private final JdbcClient jdbcClient;
336
337
public UserService(DataSource dataSource) {
338
this.jdbcClient = JdbcClient.create(dataSource);
339
}
340
341
public UserStatistics getDepartmentStatistics(String department) {
342
return jdbcClient
343
.sql("""
344
SELECT
345
department,
346
COUNT(*) as total_users,
347
AVG(salary) as avg_salary,
348
MAX(salary) as max_salary,
349
MIN(salary) as min_salary
350
FROM users
351
WHERE department = ?
352
GROUP BY department
353
""")
354
.param(department)
355
.query((rs, rowNum) -> new UserStatistics(
356
rs.getString("department"),
357
rs.getInt("total_users"),
358
rs.getBigDecimal("avg_salary"),
359
rs.getBigDecimal("max_salary"),
360
rs.getBigDecimal("min_salary")
361
))
362
.single();
363
}
364
}
365
```
366
367
## Core Interfaces
368
369
```java { .api }
370
/**
371
* Modern fluent JDBC client interface
372
*/
373
public interface JdbcClient {
374
static JdbcClient create(DataSource dataSource);
375
static JdbcClient create(JdbcOperations jdbcOperations);
376
377
StatementSpec sql(String sql);
378
}
379
380
/**
381
* Fluent statement building interface
382
*/
383
public interface StatementSpec {
384
StatementSpec param(Object value);
385
StatementSpec params(Object... values);
386
StatementSpec param(String name, Object value);
387
StatementSpec params(Map<String, ?> paramMap);
388
389
<T> MappedQuerySpec<T> query(Class<T> mappedClass);
390
<T> MappedQuerySpec<T> query(RowMapper<T> rowMapper);
391
MappedQuerySpec<Map<String, Object>> query();
392
393
UpdateSpec update();
394
}
395
396
/**
397
* Fluent query result interface
398
*/
399
public interface MappedQuerySpec<T> {
400
T single();
401
Optional<T> optional();
402
List<T> list();
403
Set<T> set();
404
Stream<T> stream();
405
}
406
407
/**
408
* Fluent update operations interface
409
*/
410
public interface UpdateSpec {
411
int rows();
412
KeyHolder keys();
413
KeyHolder keys(String... keyColumnNames);
414
int[] batch(Object[]... batchArgs);
415
}
416
```