1. 简介
在之前的 JDBI 教程 中,我们介绍了 JDBI 的基本用法。JDBI 是一个开源的数据库访问库,能显著减少直接使用 JDBC 时的模板代码。
本文将重点讲解 如何在 Spring Boot 项目中集成 JDBI。同时也会分析 JDBI 在某些场景下为何是 Spring Data JPA 的一个优秀替代方案——尤其当你需要更细粒度的 SQL 控制时。
2. 项目配置
首先,我们需要引入 JDBI 的相关依赖。这次我们使用 JDBI 官方提供的 Spring 集成插件,它会自动带入核心依赖。此外,还会引入 SqlObject 插件,它提供了更强大的 DAO 接口能力:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-spring5</artifactId>
<version>3.38.0</version>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-sqlobject</artifactId>
<version>3.38.0</version>
</dependency>
最新版本可在 Maven Central 查看:
此外,还需要一个数据库驱动。本文使用 H2 内存数据库,因此添加如下依赖:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.214</version>
<scope>runtime</scope>
</dependency>
⚠️ 注意:虽然这里引入了 spring-boot-starter-data-jpa
,但我们并不会使用 JPA,仅是为了方便启动数据源配置。你也可以替换成 spring-boot-starter-jdbc
更轻量。
3. JDBI 实例化与配置
在 Spring 环境中,我们需要将 Jdbi
实例注册为 Spring Bean,以便全局使用。
借助 Spring Boot 的自动配置机制,我们可以轻松获取 DataSource
,并通过 @Bean
方法创建并配置 Jdbi
实例。同时注册所有插件和 RowMapper
。
@Configuration
public class JdbiConfiguration {
@Bean
public Jdbi jdbi(DataSource ds, List<JdbiPlugin> jdbiPlugins, List<RowMapper<?>> rowMappers) {
TransactionAwareDataSourceProxy proxy = new TransactionAwareDataSourceProxy(ds);
Jdbi jdbi = Jdbi.create(proxy);
// 注册所有插件
log.info("[I27] Installing plugins... ({} found)", jdbiPlugins.size());
jdbiPlugins.forEach(jdbi::installPlugin);
// 注册所有 RowMapper
log.info("[I31] Installing rowMappers... ({} found)", rowMappers.size());
rowMappers.forEach(jdbi::registerRowMapper);
return jdbi;
}
}
✅ 关键点:
- 使用
TransactionAwareDataSourceProxy
包装DataSource
,这是实现 JDBI 与 Spring 事务集成 的核心。 - 自动发现并注册所有
JdbiPlugin
和RowMapper
,避免手动配置。 - 最终返回一个“开箱即用”的
Jdbi
Bean,后续可直接注入使用。
4. 示例领域模型
我们采用一个极简的领域模型:CarMaker
(汽车厂商)和 CarModel
(车型)。JDBI 不要求实体类加注解,直接使用标准 POJO 即可:
public class CarMaker {
private Long id;
private String name;
private List<CarModel> models;
// getters and setters ...
}
public class CarModel {
private Long id;
private String name;
private Integer yearDate;
private String sku;
private Long makerId;
// getters and setters ...
}
⚠️ 提示:虽然这里没有注解,但 JDBI 默认通过字段名或 getter/setter 映射数据库列,命名需遵循 JavaBean 规范。
5. 创建 DAO 接口
JDBI 的 SqlObject 插件允许我们通过接口定义 DAO,风格类似 Spring Data,但更贴近 SQL。
只需定义接口并添加注解,JDBI 会自动处理连接、Statement、ResultSet 等底层细节:
@UseClasspathSqlLocator
public interface CarMakerDao {
@SqlUpdate
@GetGeneratedKeys
Long insert(@BindBean CarMaker carMaker);
@SqlBatch("insert")
@GetGeneratedKeys
List<Long> bulkInsert(@BindBean List<CarMaker> carMakers);
@SqlQuery
CarMaker findById(Long id);
}
@UseClasspathSqlLocator
public interface CarModelDao {
@SqlUpdate
@GetGeneratedKeys
Long insert(@BindBean CarModel carModel);
@SqlBatch("insert")
@GetGeneratedKeys
List<Long> bulkInsert(@BindBean List<CarModel> models);
@SqlQuery
CarModel findByMakerIdAndSku(@Bind("makerId") Long makerId, @Bind("sku") String sku );
}
5.1 @UseClasspathSqlLocator
- ✅ 作用:告诉 JDBI SQL 语句位于 classpath 下的外部
.sql
文件中。 - 默认路径规则:
接口全限定名/方法名.sql
,例如com.example.dao.CarMakerDao/insert.sql
。 - 可通过
@SqlQuery("custom.sql")
覆盖默认路径。
5.2 @SqlUpdate / @SqlBatch / @SqlQuery
注解 | 用途 | 示例 |
---|---|---|
@SqlUpdate |
执行 INSERT/UPDATE/DELETE | insert , updateById |
@SqlBatch |
批量执行同一条 SQL | bulkInsert 处理 List |
@SqlQuery |
执行 SELECT 并返回结果 | findById , findAll |
✅ @SqlBatch
在处理大批量数据时性能优势明显,避免 N+1 问题。
5.3 @GetGeneratedKeys
- ✅ 用于获取插入后数据库生成的主键(如自增 ID)。
- 常见于
INSERT
操作后需要返回新 ID 的场景。 - 支持单条和批量插入(返回
List<Long>
)。
5.4 @BindBean / @Bind
注解 | 绑定方式 | 示例 |
---|---|---|
@BindBean |
将 POJO 属性映射到 SQL 命名参数 | INSERT INTO car (name) VALUES (:name) |
@Bind |
将方法参数绑定到命名参数 | :makerId , :sku |
⚠️ @BindBean
支持嵌套属性(如 :owner.name
),非常方便。
6. 使用 DAO
在 Spring 中,最简单的使用方式是通过 Jdbi.onDemand()
创建代理 Bean:
@Bean
public CarMakerDao carMakerDao(Jdbi jdbi) {
return jdbi.onDemand(CarMakerDao.class);
}
@Bean
public CarModelDao carModelDao(Jdbi jdbi) {
return jdbi.onDemand(CarModelDao.class);
}
✅ 优势:
- 返回的 DAO 实例是线程安全的。
- 每次方法调用才获取连接,调用结束自动释放。
- 与
TransactionAwareDataSourceProxy
配合,完美支持 Spring 事务。
⚠️ 踩坑提醒:如果项目表很多,手动写这些 @Bean
方法会很繁琐。建议封装一个通用的 JdbiDaoRegistrar
或使用 AOP 自动生成。
7. 事务性服务
下面是一个典型业务场景:保存一个 CarMaker
及其关联的多个 CarModel
,要求 原子性 —— 任一失败则整体回滚。
@Service
public class CarMakerService {
private final CarMakerDao carMakerDao;
private final CarModelDao carModelDao;
public CarMakerService(CarMakerDao carMakerDao, CarModelDao carModelDao) {
this.carMakerDao = carMakerDao;
this.carModelDao = carModelDao;
}
@Transactional
public int bulkInsert(CarMaker carMaker) {
Long carMakerId;
if (carMaker.getId() == null) {
carMakerId = carMakerDao.insert(carMaker);
carMaker.setId(carMakerId);
}
carMaker.getModels().forEach(m -> {
m.setMakerId(carMaker.getId());
carModelDao.insert(m);
});
return carMaker.getModels().size();
}
}
✅ 关键机制:
- 使用 Spring 的
@Transactional
而非 JDBI 的@Transaction
,确保跨资源事务一致性。 - 所有 DAO 操作共享同一个数据库连接,由
TransactionAwareDataSourceProxy
保证。 - 若插入某个
CarModel
失败(如唯一键冲突),整个事务回滚,数据不会残留。
💡 简单粗暴地说:只要配置正确,JDBI 就能“无缝融入”Spring 事务体系,像原生 JDBC 一样可控,又像 ORM 一样省心。
8. 总结
本文演示了如何在 Spring Boot 中高效集成 JDBI:
- ✅ 利用
jdbi3-spring5
实现与 Spring 的深度整合 - ✅ 通过
onDemand
创建轻量级 DAO 代理 - ✅ 借助
TransactionAwareDataSourceProxy
实现事务透明管理 - ✅ SQL 与代码分离,提升可维护性
当你需要比 JPA 更灵活的 SQL 控制,又不想手写 JDBC 模板代码时,JDBI 是一个非常值得考虑的选择。
所有示例代码已上传至 GitHub:https://github.com/yourname/spring-boot-jdbi-demo