# Simple JDBC
`Simple JDBC` 提供了一套轻量级的 JDBC 封装工具类,是作者在对传统遗留项目进行改造时设计。该项目未引入 ORM 框架,原本的数据库交互高度依赖原生 JDBC API,导致存在大量冗余的样板代码(Boilerplate Code)。本项目通过抽象底层数据库操作,简化了连接管理、SQL 执行与结果集处理流程,提升数据访问层的开发效率与代码可维护性。
> 注:本项目基于 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 开源协议发布。
---
## 1. 核心特性
- **轻量无依赖**:基于原生 JDBC 封装,无第三方重量级依赖。
- **API 简洁**:提供丰富的快捷方法,大幅减少样板代码。
- **灵活的映射**:支持自定义 `ResultHandler` 与 `RowMapper`,内置默认 Bean 映射策略。
- **事务与批处理**:提供声明式的事务模板与完善的批量更新错误处理机制。
- **线程安全**:核心模板类无状态设计,天然支持多线程环境。
---
## 2. 设计考量与边界
`Simple JDBC` 不追求大而全,在功能设计上保持克制。以下是本项目的一些设计考量:
- 明确**不支持存储过程**:专注于基础 CRUD。
- **不提供分页 API**:不同数据库的分页方言差异巨大,且实际业务中的分页查询往往不是简单的 `LIMIT OFFSET`(存在如游标分页、延迟关联等深度优化空间)。为了保持轻量与灵活,分页 SQL 交由开发者根据具体数据库与业务场景自行编写。
- **不提供缓存支持**:数据缓存应当被视为一个独立的关注点,通常交由更高层的抽象模块来处理。
---
## 3. 快速开始
### 3.1 环境要求
- **JDK 8** 或更高版本
### 3.2 添加 Maven 依赖
将以下配置添加到您的 `pom.xml` 中:
```xml
xyz.zhouxy.jdbc
simple-jdbc
${simple-jdbc.version}
```
### 3.3 初始化
```java
SimpleJdbcTemplate jdbcTemplate = new SimpleJdbcTemplate(dataSource);
```
> 💡 关于与数据库连接池(如 HikariCP、Druid、DBCP 2 等)的集成方式,请参见「[连接池集成](#8-连接池集成)」章节。
### 3.4 查询操作
```java
// 基础查询(使用 ResultHandler 处理全部结果)
List accounts = jdbcTemplate.query(
"SELECT * FROM account WHERE deleted = 0 AND username LIKE ? AND org_no = ?",
buildParams("admin%", "0000"),
rs -> {
List result = new ArrayList<>();
while (rs.next()) {
result.add(new Account(
rs.getLong("id"),
rs.getString("username"),
rs.getString("password"),
rs.getString("org_no"),
rs.getTimestamp("create_time"),
rs.getTimestamp("update_time")
));
}
return result;
}
);
// 查询列表(单列)
List usernames = jdbcTemplate.queryList(
"SELECT username FROM account WHERE deleted = 0 AND username LIKE ? AND org_no = ?",
buildParams("admin%", "0000"),
String.class
);
// 查询列表(使用内置 Bean 映射)
List mappedAccounts = jdbcTemplate.queryList(
"SELECT * FROM account WHERE deleted = 0 AND username LIKE ? AND org_no = ?",
buildParams("admin%", "0000"),
RowMapper.beanRowMapper(Account.class)
);
// 查询列表(使用自定义 RowMapper 映射)
List customMappedAccounts = jdbcTemplate.queryList(
"SELECT * FROM account WHERE deleted = 0 AND username LIKE ? AND org_no = ?",
buildParams("admin%", "0000"),
(rs, rowNum) -> new Account(
rs.getLong("id"),
rs.getString("username"),
rs.getString("password"),
rs.getString("org_no"),
rs.getTimestamp("create_time"),
rs.getTimestamp("update_time")
)
);
// 查询单行数据
Optional account = jdbcTemplate.queryFirst(
"SELECT * FROM account WHERE deleted = 0 AND id = ?",
buildParams(10000L),
(rs, rowNum) -> new Account(
rs.getLong("id"),
rs.getString("username")
// ... 省略其他字段
)
);
// 查询单个值(所有 ResultSet.getObject 支持的类型)
Long count = jdbcTemplate.queryFirst(
"SELECT COUNT(*) FROM account WHERE deleted = 0 AND username LIKE ? AND org_no = ?",
buildParams("admin%", "0000"),
Long.class
).orElse(0L);
// 查询 Boolean 值
boolean exists = jdbcTemplate.queryBoolean(
"SELECT EXISTS(SELECT 1 FROM account WHERE deleted = 0 AND id = ?)",
buildParams(10000L)
);
// 无参数 SQL 可直接省略 params 参数
List allAccounts = jdbcTemplate.queryList(
"SELECT * FROM account WHERE deleted = 0",
RowMapper.beanRowMapper(Account.class)
);
```
> 📖 完整的方法列表与映射策略说明请参见「[4. 数据查询](#4-数据查询-query)」章节。
### 3.5 更新操作
```java
// 执行常规 DML
int affectedRows = jdbcTemplate.update(
"UPDATE account SET deleted = 1 WHERE id = ?",
buildParams(10000L)
);
// 执行 DML 并获取生成的主键
// 注:按 JDBC 规范可获取自增 ID,能否获取其他值取决于具体数据库及其 JDBC Driver 实现。
List> keys = jdbcTemplate.updateAndReturnKeys(
"INSERT INTO account (username, password, org_no) VALUES (?, ?, ?)",
buildParams("admin", "123456", "0000"),
(rs, rowNum) -> Pair.of(
rs.getLong("id"),
rs.getObject("create_time", LocalDateTime.class)
)
);
```
> 📖 完整的方法列表与批量更新结果说明请参见「[5. 数据更新](#5-数据更新-update)」章节。
### 3.6 批量更新操作
```java
// 默认模式:遇错即中断
BatchUpdateResult result = jdbcTemplate.batchUpdate(
"INSERT INTO account (username, password, org_no) VALUES (?, ?, ?)",
buildBatchParams(accountList, account -> buildParams(
account.getUsername(),
account.getPassword(),
account.getOrgNo()
)),
100 // 每 100 条数据为一个批次
);
// 静默模式:遇错不中断,全部执行完毕后统一检查
BatchUpdateResult quietResult = jdbcTemplate.batchUpdate(
"INSERT INTO account (username, password, org_no) VALUES (?, ?, ?)",
buildBatchParams(accountList, account -> buildParams(
account.getUsername(),
account.getPassword(),
account.getOrgNo()
)),
100,
true // quietly = true,遇错不中断
);
// 检查批量更新结果
if (quietResult.getStatus() == BatchUpdateStatus.COMPLETED_WITH_ERRORS) {
for (int idx : quietResult.getErrorBatchIndexes()) {
BatchUpdateErrorInfo err = quietResult.getBatchUpdateErrorInfo(idx);
System.err.println("批次 " + idx + " 失败: " + err.getCause().getMessage());
}
}
```
> 📖 批量更新的详细结果说明请参见「[5.2 批量更新结果](#52-批量更新结果-batchupdateresult)」章节。
### 3.7 事务管理
```java
// 自动提交/回滚事务
jdbcTemplate.transaction().execute(jdbc -> {
...
jdbc.update(...);
...
jdbc.update(...);
...
// 内部无异常抛出则自动提交,抛出异常则自动回滚
});
// 根据返回值控制事务
jdbcTemplate.transaction().commitIfTrue(jdbc -> {
...
jdbc.update(...);
...
if (...) {
// 中断操作并回滚
return false;
}
...
if (...) {
// 某些条件下提前结束并提交事务
return true;
}
...
jdbc.update(...);
...
// 提交事务
return true;
});
```
> 📖 事务方法的详细说明请参见「[6. 事务管理](#6-事务管理-transaction)」章节。
---
## 4. 数据查询 (Query)
### 4.1 查询方法列表
| 方法签名 | 说明 |
| :--- | :--- |
| `query(sql, params, resultHandler)` | 最基础的查询,通过 `ResultHandler` 自定义完整的映射逻辑。 |
| `queryList(sql, params, rowMapper)` | 查询列表,通过 `RowMapper` 逐行映射。 |
| `queryList(sql, params, Class)` | 单列查询列表,每行提取第一列并转换为指定类型。 |
| `queryList(sql, params)` | 查询列表,每行自动转换为 `Map`。 |
| `queryFirst(sql, params, rowMapper)` | 查询第一行,通过 `RowMapper` 映射,返回 `Optional`。 |
| `queryFirst(sql, params, Class)` | 查询第一行第一列,返回 `Optional`。 |
| `queryFirst(sql, params)` | 查询第一行,返回 `Optional