Skip to content

Spring JDBC

一、概述

Spring JDBC vs JDBC

┌─────────────────────────────────────────────────────────────────┐
│                    JDBC vs Spring JDBC                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  传统 JDBC:                                                    │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ Connection conn = null;                                  │  │
│  │ PreparedStatement pstmt = null;                         │  │
│  │ ResultSet rs = null;                                    │  │
│  │ try {                                                   │  │
│  │     conn = ds.getConnection();                         │  │
│  │     pstmt = conn.prepareStatement(SQL);                 │  │
│  │     pstmt.setString(1, name);                         │  │
│  │     rs = pstmt.executeQuery();                         │  │
│  │     while (rs.next()) { ... }                          │  │
│  │ } finally {                                             │  │
│  │     close(rs, pstmt, conn);  // 容易遗漏/出错          │  │
│  │ }                                                       │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                                  │
│  Spring JDBC:                                                 │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │ jdbcTemplate.queryForObject(SQL, new Object[]{name},   │  │
│  │     (rs, rowNum) -> { ... });                        │  │
│  │ // 自动管理连接、异常转换                                │  │
│  └─────────────────────────────────────────────────────────┘  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

二、快速开始

Maven 依赖

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

配置

yaml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

三、JdbcTemplate

查询

java
@Service
public class UserService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    // 查询单个对象
    public User findById(Long id) {
        String sql = "SELECT * FROM users WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
            User user = new User();
            user.setId(rs.getLong("id"));
            user.setName(rs.getString("name"));
            user.setEmail(rs.getString("email"));
            return user;
        }, id);
    }
    
    // 查询列表
    public List<User> findAll() {
        String sql = "SELECT * FROM users";
        return jdbcTemplate.query(sql, (rs, rowNum) -> {
            User user = new User();
            user.setId(rs.getLong("id"));
            user.setName(rs.getString("name"));
            return user;
        });
    }
    
    // 查询基本类型
    public int count() {
        String sql = "SELECT COUNT(*) FROM users";
        return jdbcTemplate.queryForObject(sql, Integer.class);
    }
    
    // 查询 Map
    public Map<String, Object> findAsMap(Long id) {
        String sql = "SELECT * FROM users WHERE id = ?";
        return jdbcTemplate.queryForMap(sql, id);
    }
    
    // 查询 List<Map>
    public List<Map<String, Object>> findAllAsMap() {
        String sql = "SELECT * FROM users";
        return jdbcTemplate.queryForList(sql);
    }
}

更新

java
@Service
public class UserService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    // 插入
    public int insert(User user) {
        String sql = "INSERT INTO users(name, email) VALUES(?, ?)";
        return jdbcTemplate.update(sql, user.getName(), user.getEmail());
    }
    
    // 更新
    public int update(User user) {
        String sql = "UPDATE users SET name = ?, email = ? WHERE id = ?";
        return jdbcTemplate.update(sql, user.getName(), user.getEmail(), user.getId());
    }
    
    // 删除
    public int delete(Long id) {
        String sql = "DELETE FROM users WHERE id = ?";
        return jdbcTemplate.update(sql, id);
    }
    
    // 批量插入
    public int batchInsert(List<User> users) {
        String sql = "INSERT INTO users(name, email) VALUES(?, ?)";
        
        List<Object[]> batch = users.stream()
            .map(u -> new Object[]{u.getName(), u.getEmail()})
            .collect(Collectors.toList());
        
        int[] results = jdbcTemplate.batchUpdate(sql, batch);
        return Arrays.stream(results).sum();
    }
}

命名参数

java
@Service
public class UserService {
    
    @Autowired
    private NamedParameterJdbcTemplate namedJdbcTemplate;
    
    public User findByName(String name) {
        String sql = "SELECT * FROM users WHERE name = :name";
        Map<String, Object> params = Map.of("name", name);
        return namedJdbcTemplate.queryForObject(sql, params, (rs, rowNum) -> {
            User user = new User();
            user.setId(rs.getLong("id"));
            user.setName(rs.getString("name"));
            return user;
        });
    }
    
    // 使用 SqlParameterSource
    public int insert(User user) {
        String sql = "INSERT INTO users(name, email) VALUES(:name, :email)";
        SqlParameterSource params = new BeanPropertySqlParameterSource(user);
        return namedJdbcTemplate.update(sql, params);
    }
}

四、RowMapper

自定义 RowMapper

java
public class UserRowMapper implements RowMapper<User> {
    
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        User user = new User();
        user.setId(rs.getLong("id"));
        user.setName(rs.getString("name"));
        user.setEmail(rs.getString("email"));
        user.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
        return user;
    }
}

// 使用
public User findById(Long id) {
    String sql = "SELECT * FROM users WHERE id = ?";
    return jdbcTemplate.queryForObject(sql, new UserRowMapper(), id);
}

BeanPropertyRowMapper

java
public User findById(Long id) {
    String sql = "SELECT * FROM users WHERE id = ?";
    return jdbcTemplate.queryForObject(sql, 
        new BeanPropertyRowMapper<>(User.class), id);
}

五、事务管理

@Transactional

java
@Service
public class TransferService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        // 扣款
        String sql1 = "UPDATE accounts SET balance = balance - ? WHERE id = ?";
        jdbcTemplate.update(sql1, amount, fromId);
        
        // 存款
        String sql2 = "UPDATE accounts SET balance = balance + ? WHERE id = ?";
        jdbcTemplate.update(sql2, amount, toId);
        
        // 如果异常,事务回滚
    }
}

六、SQL 结果映射

java
// 查询单个值
String name = jdbcTemplate.queryForObject("SELECT name FROM users WHERE id = ?", String.class, id);

// 查询计数
int count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Integer.class);

// 查询是否存在
boolean exists = jdbcTemplate.queryForObject(
    "SELECT COUNT(*) > 0 FROM users WHERE id = ?", Boolean.class, id);

// IN 查询
List<Object> ids = Arrays.asList(1, 2, 3);
String sql = "SELECT * FROM users WHERE id IN (" + 
    ids.stream().map(id -> "?").collect(Collectors.joining(",")) + ")";
jdbcTemplate.query(sql, new UserRowMapper(), ids.toArray());

七、面试高频问题

Q1: JdbcTemplate 如何管理连接?

通过 DataSourceUtils 从连接池获取连接,使用后自动归还

Q2: 命名参数有什么用?

使用 :name 代替 ?,避免参数顺序错误

Q3: RowMapper 和 ResultSetExtractor 区别?

  • RowMapper:每行映射一个对象
  • ResultSetExtractor:整个结果集映射一个对象

八、下一章预告

下一章我们将学习 JWT 实现登录

  • JWT 结构与原理
  • Spring Security + JWT
  • Token 刷新与黑名单

基于 MIT 许可发布