Spring 数据访问
约 1079 字大约 4 分钟
布欧-Lewyon
2026-05-16
首页 › Java › Spring › Spring 数据访问
Spring 在原生 JDBC 之上提供了更简洁的数据访问方案:JdbcTemplate 消除样板代码,Spring Data JPA 通过接口声明自动生成 DAO 实现。本节聚焦这两种方式,并与之前学的 B+ JDBC 对照。
JdbcTemplate:JDBC 的简化封装
JdbcTemplate 是 Spring 对原生 JDBC 的轻量封装。它自动管理连接获取与释放、异常转换,让你只需关注 SQL 本身。
Maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>配置 DataSource
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:使用 JdbcTemplate
对比原生 JDBC 与 JdbcTemplate:
// 原生 JDBC(繁琐的资源管理)
public User findById(Long id) throws SQLException {
String sql = "SELECT * FROM users WHERE id = ?";
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, id);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return mapUser(rs);
}
return null;
}
}
}// JdbcTemplate(精简)
@Repository
public class UserDao {
private final JdbcTemplate jdbc;
public UserDao(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
public User findById(Long id) {
String sql = "SELECT * FROM users WHERE id = ?";
return jdbc.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), id);
}
public List<User> findAll() {
return jdbc.query("SELECT * FROM users", new BeanPropertyRowMapper<>(User.class));
}
public int save(User user) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
return jdbc.update(sql, user.getName(), user.getEmail());
}
}JdbcTemplate由 Spring Boot 自动配置(前提是 classpath 上有DataSource)。BeanPropertyRowMapper按列名与字段名自动映射(下划线转驼峰)。- 无需手写
try-with-resources,JdbcTemplate内部处理连接关闭。
Spring Data JPA
JPA(Java Persistence API)是 ORM(对象关系映射)规范,Spring Data JPA 在此基础上提供了仓库接口自动实现的能力。
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>实体类
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(unique = true)
private String email;
// 必须有无参构造器(JPA 要求)
public User() {}
public User(String name, String email) {
this.name = name;
this.email = email;
}
// getter / setter
}Repository 接口
public interface UserRepository extends JpaRepository<User, Long> {
// 方法命名查询——按规则命名,自动生成实现
Optional<User> findByEmail(String email);
List<User> findByNameContaining(String keyword);
// 自定义 JPQL 查询
@Query("SELECT u FROM User u WHERE u.email LIKE %:domain%")
List<User> findByEmailDomain(@Param("domain") String domain);
}JpaRepository<User, Long>已内置save、findById、findAll、deleteById等方法。- 方法命名规则:
findBy+ 字段名 +Containing/StartingWith/Between等关键词。 @Query注解写自定义 JPQL 或原生 SQL。
配合 Service
@Service
public class UserService {
private final UserRepository repo;
public UserService(UserRepository repo) {
this.repo = repo;
}
@Transactional
public User createUser(User user) {
return repo.save(user);
}
@Transactional(readOnly = true)
public User findById(Long id) {
return repo.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
}
}JPA vs JdbcTemplate 怎么选
| 维度 | JdbcTemplate | Spring Data JPA |
|---|---|---|
| 上手难度 | 低(熟悉 SQL 即可) | 中(需理解 ORM 概念) |
| SQL 控制 | 完全控制 | 自动生成,@Query 可覆盖 |
| 复杂查询 | 手写 SQL | 可能需 JPQL/Specification |
| 批量/大数据 | 更优 | 需额外配置 |
| 典型场景 | 报表、复杂联表 | CRUD 为主的业务系统 |
日常 CRUD 为主的业务系统优先选 JPA,复杂查询或有特定 SQL 优化需求时混用 JdbcTemplate(可同时存在)。
声明式事务
Spring 的 @Transactional 通过 AOP 实现声明式事务管理:
@Service
@Transactional // 类级别:该类所有 public 方法都开启事务
public class OrderService {
@Transactional(rollbackFor = Exception.class) // 方法级别覆盖类级别
public void createOrder(Order order) {
orderRepo.save(order);
inventoryRepo.deduct(order.getProductId(), order.getQuantity()); // 同一事务
}
}@Transactional默认只回滚RuntimeException和Error,不回滚受检异常。- 传播行为:
REQUIRED(默认,加入当前事务)、REQUIRES_NEW(挂起当前,另起新事务)。 - 只读事务
@Transactional(readOnly = true)可优化数据库查询性能。 @Transactional在private或protected方法上无效——AOP 代理只能拦截public方法。
小结
JdbcTemplate封装 JDBC,消除资源管理样板代码,适合需要精细控制 SQL 的场景。- Spring Data JPA 通过
JpaRepository接口声明自动生成 DAO,自动实现基础 CRUD。 - 方法命名查询(
findByEmail)和@Query注解是 JPA 的两种查询方式。 @Transactional声明事务,默认回滚RuntimeException,需显式rollbackFor处理受检异常。- 易错:
@Transactional在同一个类中 A 方法调用 B 方法时,B 上的事务注解不生效(AOP 代理绕过了自身调用)。解法:将 B 提取到另一个@Service中,或通过AopContext.currentProxy()自调用。 - 思考任务:创建一个 Spring Boot 项目,同时配置
JdbcTemplate和JpaRepository,比较两种方式完成同一张表的 CRUD 的代码量差异。
上一节:Spring MVC
下一节:Spring Boot 基础
