or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-template.mddatasource-management.mdexception-handling.mdfluent-client.mdindex.mdnamed-parameter.mdsimple-operations.mdtransaction-management.md

fluent-client.mddocs/

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

```