1. 概述
Spring Data JDBC 是一个轻量级的持久化框架,相比 Spring Data JPA 更加简单直接。它没有缓存、懒加载、写后回刷(write-behind)等复杂机制,也没有 JPA 那么多“魔法”。但这不意味着它功能弱——它有自己的 ORM 模型,并支持我们熟悉的大部分 Spring Data 特性,比如:
✅ 映射实体(mapped entities)
✅ 仓库接口(repositories)
✅ 查询注解(query annotations)
✅ 原生集成 JdbcTemplate
⚠️ 但有个关键点必须注意:Spring Data JDBC 不支持数据库 schema 自动生成。这意味着你需要手动建表,不能靠 hibernate.hbm2ddl.auto
那一套。表结构得自己写 DDL 或通过脚本管理。
这也带来了好处:逻辑更透明,没有隐藏行为,适合追求可控性和性能的项目。
2. 添加 Spring Data JDBC 依赖
如果你用的是 Spring Boot,引入官方 starter 就行:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
📌 注意:这个 starter 不会自动包含数据库驱动!你需要自己指定,比如用 H2、MySQL 或 PostgreSQL。
我们这里以 H2 内存数据库为例:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
由于 Spring Data JDBC 不生成 schema,我们可以手动创建 schema.sql
文件,放在 src/main/resources
下:
DROP TABLE IF EXISTS person;
CREATE TABLE person (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(255),
last_name VARCHAR(255)
);
Spring Boot 启动时会自动执行这个文件,创建表结构。简单粗暴,但很可靠。
3. 定义实体类
和 Spring Data 家族其他成员一样,我们用注解把 POJO 映射到数据库表。
⚠️ 重要限制:实体类必须显式声明 @Id
字段,否则 Spring Data JDBC 无法识别主键。
默认命名策略采用驼峰转下划线:
- Java 类
Person
→ 表person
- 属性
firstName
→ 列first_name
当然你也可以用 @Table
和 @Column
显式指定:
public class Person {
@Id
private long id;
private String firstName;
private String lastName;
// 构造方法、getter、setter 省略
}
是不是很清爽?没有 @Entity
,没有 @Table(name = "...")
,除非必要,否则不用加。
4. 声明 JDBC 仓库接口
Spring Data JDBC 的仓库写法和 JPA 几乎一模一样。你可以继承以下任一接口:
Repository<T, ID>
—— 最基础的标记接口CrudRepository<T, ID>
—— 提供常用增删改查方法PagingAndSortingRepository<T, ID>
—— 支持分页和排序
我们来定义一个 PersonRepository
:
@Repository
public interface PersonRepository extends CrudRepository<Person, Long> {
}
✅ 继承后自动获得:
save(person)
findById(id)
deleteById(id)
findAll()
如果需要分页能力,直接换 PagingAndSortingRepository
就行,API 一致,无缝切换。
5. 自定义仓库方法
虽然内置方法够用,但实际开发中总要写自定义查询。Spring Data JDBC 支持两种方式:
5.1 方法名推导查询(Query Methods)
从 2.0 版本开始支持。只要方法名符合命名规范,框架会自动生成 SQL。
List<Person> findByFirstName(String firstName);
👉 框架会生成:
SELECT id, first_name, last_name FROM person WHERE first_name = ?
支持的操作符包括:And
, Or
, Between
, Like
, In
, IsNull
等,基本够用。
5.2 手写 SQL 查询
复杂逻辑还是得自己写 SQL。使用 @Query
注解,并配合 @Modifying
标记更新操作:
@Repository
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findByFirstName(String firstName);
@Modifying
@Query("UPDATE person SET first_name = :name WHERE id = :id")
boolean updateByFirstName(@Param("id") Long id, @Param("name") String name);
}
📌 关键细节:
- ❌ 不支持位置参数(如
?1
,?2
) - ✅ 只支持命名参数(
:paramName
),必须配合@Param
使用 - SQL 是原生 SQL,不是 JPQL 或 HQL
⚠️ 踩坑提醒:因为是原生 SQL,一旦换数据库(比如从 MySQL 切到 Oracle),可能因方言差异导致 SQL 报错。迁移成本比 JPA 高。
6. 初始化测试数据
为了验证功能,我们需要插入几条测试数据。可以用 JdbcTemplate
手动初始化:
@Component
public class DatabaseSeeder {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insertData() {
jdbcTemplate.execute("INSERT INTO person(first_name,last_name) VALUES('Victor', 'Hugo')");
jdbcTemplate.execute("INSERT INTO person(first_name,last_name) VALUES('Dante', 'Alighieri')");
jdbcTemplate.execute("INSERT INTO person(first_name,last_name) VALUES('Stefan', 'Zweig')");
jdbcTemplate.execute("INSERT INTO person(first_name,last_name) VALUES('Oscar', 'Wilde')");
}
}
💡 小技巧:可以在 CommandLineRunner
中调用 insertData()
,应用启动时自动执行:
@Bean
CommandLineRunner init(DatabaseSeeder seeder) {
return args -> seeder.insertData();
}
用 JdbcTemplate
直接操作,灵活又高效,完全掌控 SQL 执行过程。
7. 总结
Spring Data JDBC 的定位很清晰:在保留 Spring Data 编程模型的同时,去掉 JPA 的复杂性,贴近 JDBC 的性能和可控性。
✅ 优势
- 架构简单,无隐藏逻辑,学习成本低
- 性能优于 JPA,直接与数据库通信,无一级/二级缓存开销
- 支持熟悉的 Repository 模式,开发效率高
- 与
JdbcTemplate
天然集成,适合混合使用
❌ 劣势
- 不支持 schema 自动生成,需手动维护 DDL
- SQL 写死在代码里,数据库迁移时容易踩坑
- 缺少延迟加载、关联映射等高级特性(但这也是设计取舍)
📌 适用场景:
- 中小项目,追求简洁和性能
- 团队对 SQL 熟悉,愿意手动管理 schema
- 不想被 JPA 的“魔法”困扰,想要更透明的数据访问层
示例代码已上传至 GitHub:https://github.com/baeldung/spring-data-jdbc-example