全部笔记All notes

Web程序设计笔记08——第四章:Spring的数据库开发

阅读 10m 49s10m 49s read

Spring 数据库开发完整指南

概述

Spring 提供了强大的数据库访问支持,主要通过 Spring JDBC 模块实现。JdbcTemplate 是 Spring JDBC 的核心类,它简化了 JDBC 操作,提供了更加便捷和安全的数据库访问方式。

💡 核心优势:

  • 简化 JDBC 操作:自动处理资源的创建和释放
  • 异常转换:将 SQLException 转换为 Spring 的 DataAccessException
  • 参数化查询:支持命名参数和位置参数
  • 结果集映射:提供多种结果集映射方式

环境准备

1. 数据库准备

创建测试数据库
-- 创建数据库
CREATE DATABASE spring_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 使用数据库
USE spring_demo;

-- 创建用户表
CREATE TABLE account (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    balance DECIMAL(10,2) DEFAULT 0.00,
    created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 插入测试数据
INSERT INTO account (username, balance) VALUES 
('zhangsan', 1000.00),
('lisi', 2000.00),
('wangwu', 3000.00);

2. Maven 依赖配置

<dependencies>
    <!-- Spring 核心依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.21</version>
    </dependency>
    
    <!-- Spring JDBC 依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.21</version>
    </dependency>
    
    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.32</version>
    </dependency>
    
    <!-- 数据源连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.16</version>
    </dependency>
    
    <!-- 单元测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

3. 数据源配置

方式一:XML 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring_demo?useSSL=false&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="password"/>
        <!-- 连接池配置 -->
        <property name="initialSize" value="5"/>
        <property name="maxActive" value="20"/>
        <property name="minIdle" value="5"/>
    </bean>

    <!-- 配置 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>
方式二:Java 配置类
@Configuration
public class DataSourceConfig {
    
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring_demo?useSSL=false&serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        dataSource.setInitialSize(5);
        dataSource.setMaxActive(20);
        dataSource.setMinIdle(5);
        return dataSource;
    }
    
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

JdbcTemplate 核心 API

1. execute() 方法 - DDL 操作

execute() 方法主要用于执行 DDL 语句(创建、删除、修改表结构)。

public class JdbcTemplateExecuteTest {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Test
    public void testCreateTable() {
        String sql = "CREATE TABLE user (" +
                     "id INT PRIMARY KEY AUTO_INCREMENT, " +
                     "name VARCHAR(50), " +
                     "email VARCHAR(100)" +
                     ")";
        
        jdbcTemplate.execute(sql);
        System.out.println("表创建成功!");
    }
    
    @Test
    public void testDropTable() {
        String sql = "DROP TABLE IF EXISTS user";
        jdbcTemplate.execute(sql);
        System.out.println("表删除成功!");
    }
}

2. update() 方法 - DML 操作

update() 方法用于执行 INSERT、UPDATE、DELETE 操作。

插入操作
@Repository
public class AccountDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    // 插入单条数据
    public int addAccount(Account account) {
        String sql = "INSERT INTO account (username, balance) VALUES (?, ?)";
        return jdbcTemplate.update(sql, account.getUsername(), account.getBalance());
    }
    
    // 批量插入
    public int[] batchAddAccount(List<Account> accounts) {
        String sql = "INSERT INTO account (username, balance) VALUES (?, ?)";
        
        List<Object[]> batchArgs = new ArrayList<>();
        for (Account account : accounts) {
            batchArgs.add(new Object[]{account.getUsername(), account.getBalance()});
        }
        
        return jdbcTemplate.batchUpdate(sql, batchArgs);
    }
}
更新操作
// 更新账户余额
public int updateBalance(int id, BigDecimal balance) {
    String sql = "UPDATE account SET balance = ? WHERE id = ?";
    return jdbcTemplate.update(sql, balance, id);
}

// 批量更新
public int[] batchUpdateBalance(Map<Integer, BigDecimal> updates) {
    String sql = "UPDATE account SET balance = ? WHERE id = ?";
    
    List<Object[]> batchArgs = new ArrayList<>();
    for (Map.Entry<Integer, BigDecimal> entry : updates.entrySet()) {
        batchArgs.add(new Object[]{entry.getValue(), entry.getKey()});
    }
    
    return jdbcTemplate.batchUpdate(sql, batchArgs);
}
删除操作
// 删除单个账户
public int deleteAccount(int id) {
    String sql = "DELETE FROM account WHERE id = ?";
    return jdbcTemplate.update(sql, id);
}

// 清空表数据
public int deleteAllAccounts() {
    String sql = "TRUNCATE TABLE account";
    return jdbcTemplate.update(sql);
}

3. query() 方法 - 查询操作

查询单个对象
// 实体类
@Data
public class Account {
    private Integer id;
    private String username;
    private BigDecimal balance;
    private Date createdTime;
}

// RowMapper 实现
public class AccountRowMapper implements RowMapper<Account> {
    @Override
    public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
        Account account = new Account();
        account.setId(rs.getInt("id"));
        account.setUsername(rs.getString("username"));
        account.setBalance(rs.getBigDecimal("balance"));
        account.setCreatedTime(rs.getTimestamp("created_time"));
        return account;
    }
}

// DAO 方法
public Account getAccountById(int id) {
    String sql = "SELECT * FROM account WHERE id = ?";
    return jdbcTemplate.queryForObject(sql, new AccountRowMapper(), id);
}
查询多个对象
// 查询所有账户
public List<Account> getAllAccounts() {
    String sql = "SELECT * FROM account";
    return jdbcTemplate.query(sql, new AccountRowMapper());
}

// 使用 Lambda 表达式简化
public List<Account> getAccountsByBalanceRange(BigDecimal min, BigDecimal max) {
    String sql = "SELECT * FROM account WHERE balance BETWEEN ? AND ?";
    
    return jdbcTemplate.query(sql, (rs, rowNum) -> {
        Account account = new Account();
        account.setId(rs.getInt("id"));
        account.setUsername(rs.getString("username"));
        account.setBalance(rs.getBigDecimal("balance"));
        account.setCreatedTime(rs.getTimestamp("created_time"));
        return account;
    }, min, max);
}
查询单个值
// 统计账户数量
public Integer getAccountCount() {
    String sql = "SELECT COUNT(*) FROM account";
    return jdbcTemplate.queryForObject(sql, Integer.class);
}

// 查询总余额
public BigDecimal getTotalBalance() {
    String sql = "SELECT SUM(balance) FROM account";
    return jdbcTemplate.queryForObject(sql, BigDecimal.class);
}

// 查询用户名
public String getUsername(int id) {
    String sql = "SELECT username FROM account WHERE id = ?";
    return jdbcTemplate.queryForObject(sql, String.class, id);
}

4. 命名参数 JdbcTemplate

@Configuration
public class NamedJdbcConfig {
    
    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate(DataSource dataSource) {
        return new NamedParameterJdbcTemplate(dataSource);
    }
}

@Repository
public class AccountNamedDao {
    
    @Autowired
    private NamedParameterJdbcTemplate namedJdbcTemplate;
    
    public int addAccount(Account account) {
        String sql = "INSERT INTO account (username, balance) " +
                     "VALUES (:username, :balance)";
        
        Map<String, Object> params = new HashMap<>();
        params.put("username", account.getUsername());
        params.put("balance", account.getBalance());
        
        return namedJdbcTemplate.update(sql, params);
    }
    
    // 使用 BeanPropertySqlParameterSource
    public int addAccountWithBean(Account account) {
        String sql = "INSERT INTO account (username, balance) " +
                     "VALUES (:username, :balance)";
        
        SqlParameterSource paramSource = new BeanPropertySqlParameterSource(account);
        return namedJdbcTemplate.update(sql, paramSource);
    }
}
(3)创建配置文件 a.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--1.配置数据源:    创建DriverManagerDataSource对象,连接数据库-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="8520"/>
    </bean>

<!--    2.创建JdbcTemplate类对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    创建实现类对象-->
    <bean id="accountDao" class="com.owlbay.dao.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
</beans>
(4)测试execute()方法
package com.owlbay;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

public class JdbcTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext =new ClassPathXmlApplicationContext("a.xml");
        JdbcTemplate jdbcTemplate = (JdbcTemplate) applicationContext.getBean("jdbcTemplate");
        String sql="create table account(id int primary key auto_increment,username varchar (50),balance double)";
        jdbcTemplate.execute(sql);
    }
}

运行结果如下:数据库中已经成功创建表account

image-20220329195454770

二、update():增删改

实体类对象 转换 表中记录

实体类:

​ (1)和表对应,一张表对应一个实体类

​ (2)实体类中的成员变量 表中字段一一对应(数据类型 名称)

​ (3)实体类中的作用就是和表进行数据传输

SSM项目分三层:dao层 服务层 控制层,此代码为了简便,只创建dao层

​ dao层: ​ 接口:接口中所有方法就是系统的功能。 面向接口编程 ​ 实现类:声明了一个JdbcTemplate类对象

步骤:

(1)创建实体类
package com.owlbay.domain;

public class Account {
    private int id;
    private String username;
    private double balance;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", balance=" + balance +
                '}';
    }
}
(2)创建接口
package com.owlbay.dao;

import com.owlbay.domain.Account;

public interface AccountDao {
    //增加记录
    int addAccount(Account account);
    
    //删除根据用户ID删除账户
    int deleteById(int id);

    //更新记录
    int updateAccount(Account account);
}
(3)实现接口
package com.owlbay.dao;

import com.owlbay.domain.Account;
import org.springframework.jdbc.core.JdbcTemplate;

public class AccountDaoImpl implements AccountDao {
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    //增加记录
    @Override
    public int addAccount(Account account) {
        String sql = "insert into account values(?,?,?)";
        int update = jdbcTemplate.update(sql, account.getId(), account.getUsername(), account.getBalance());
        return update;
    }

    //删除根据用户ID删除账户
    @Override
    public int deleteById(int id) {
        String sql = "delete from account where id=?";
        int update = jdbcTemplate.update(sql, id);
        return update;
    }

    //更新记录
    @Override
    public int updateAccount(Account account) {
        String sql = "update account set username=?,balance=? where id=?";
        int update = jdbcTemplate.update(sql, account.getUsername(), account.getBalance(), account.getId());
        return update;
    }
}
(4)写配置文件
<!--	1.配置数据源:    创建DriverManagerDataSource对象,连接数据库-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="8520"/>
    </bean>

<!--    2.创建JdbcTemplate类对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
        
<!--    3.创建实现类对象-->
    <bean id="accountDao" class="com.owlbay.dao.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
(5)测试Test方法
package com.owlbay;

import com.owlbay.dao.AccountDao;
import com.owlbay.domain.Account;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class JdbcTest {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("a.xml");
    AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");

    @Test
    //增加记录
    public void addAccountTets() {
        Account account = new Account();
        account.setId(1);
        account.setUsername("zhangsan");
        account.setBalance(1000);
        accountDao.addAccount(account);
    }

    @Test
    //删除根据用户ID删除账户
    public void deleteByIdTest() {
        accountDao.deleteById(1);
    }

    @Test
    //更新记录
    public void updateAccountTest() {
        Account account = new Account();
        account.setUsername("zhangsan");
        account.setBalance(3000);
        account.setId(1);
        accountDao.updateAccount(account);
    }
}
运行截图:

1.创建两个数据

image-20220331221520933

2.测试删除数据

image-20220331221604934

3.更新数据

image-20220331221704292

补充:

1.Object

当方法形参个数不确定的时候,可以使用Object…,代表的是多个多种类型的参数

2.基本数据类型 包装类

​ int Integer ​ double Double

int a = 5;
Integer b = a;  //装箱
int c = b;  //拆箱
3.单元测试

可以直接运行一个非main方法的方法 @Test

public class AccountDao{
	@Test
    public void cat(){
        System.out.println("a");
    }
    
    @Test
    public void cat(){
        System.out.println("b");
    }
}

三、query():查

表中的记录 转换 实体类对象

步骤:

(1)创建实体类

同上

(2)创建接口
//查询  根据id进行查询  单条记录
Account findAccountById(int id);

//查所有记录
List<Account> findAllAccounts();
(3)实现接口
//查询  根据id进行查询  单条记录
@Override
public Account findAccountById(int id) {
    String sql = "select * from account where id =?";
    RowMapper<Account> rowMapper = new BeanPropertyRowMapper<>(Account.class);
    Account account = jdbcTemplate.queryForObject(sql, rowMapper, id);
    return account;
}

//查所有记录
@Override
public List<Account> findAllAccounts() {
    String sql = "select * from account";
    RowMapper<Account> rowMapper = new BeanPropertyRowMapper<Account>(Account.class);
    List<Account> account = jdbcTemplate.query(sql, rowMapper);
    return account;
}
(4)写配置文件

同上

(5)测试Test方法
@Test
//查询  根据id进行查询  单条记录
public void findAccountByIdTest() {
    Account accountById = accountDao.findAccountById(1);
    System.out.println(accountById);
}

@Test
//查所有记录
public void findAllAccoundsTest() {
    List<Account> accounts = accountDao.findAllAccounts();
    for (Account i : accounts) {
        System.out.println(i);
    }
}
运行截图:

1.单个查询

image-20220331223326855

2.多项查询

image-20220331223422159

数据库操作实战

完整示例:账户管理系统

1. 实体类设计
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    private Integer id;
    private String username;
    private BigDecimal balance;
    private Date createdTime;
}
2. DAO 层实现
@Repository
public class AccountDaoImpl implements AccountDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Override
    public int save(Account account) {
        String sql = "INSERT INTO account (username, balance) VALUES (?, ?)";
        return jdbcTemplate.update(sql, account.getUsername(), account.getBalance());
    }
    
    @Override
    public int update(Account account) {
        String sql = "UPDATE account SET username = ?, balance = ? WHERE id = ?";
        return jdbcTemplate.update(sql, account.getUsername(), 
                                   account.getBalance(), account.getId());
    }
    
    @Override
    public int delete(Integer id) {
        String sql = "DELETE FROM account WHERE id = ?";
        return jdbcTemplate.update(sql, id);
    }
    
    @Override
    public Account findById(Integer id) {
        String sql = "SELECT * FROM account WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, 
            new BeanPropertyRowMapper<>(Account.class), id);
    }
    
    @Override
    public List<Account> findAll() {
        String sql = "SELECT * FROM account";
        return jdbcTemplate.query(sql, 
            new BeanPropertyRowMapper<>(Account.class));
    }
    
    @Override
    public Long count() {
        String sql = "SELECT COUNT(*) FROM account";
        return jdbcTemplate.queryForObject(sql, Long.class);
    }
}
3. Service 层实现
@Service
@Transactional
public class AccountServiceImpl implements AccountService {
    
    @Autowired
    private AccountDao accountDao;
    
    @Override
    public void transfer(Integer fromId, Integer toId, BigDecimal amount) {
        // 查询转出账户
        Account from = accountDao.findById(fromId);
        if (from.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("余额不足!");
        }
        
        // 查询转入账户
        Account to = accountDao.findById(toId);
        
        // 转账
        from.setBalance(from.getBalance().subtract(amount));
        to.setBalance(to.getBalance().add(amount));
        
        // 更新数据库
        accountDao.update(from);
        accountDao.update(to);
    }
}

事务管理

1. 编程式事务

@Repository
public class TransactionalAccountDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void transfer(final Integer fromId, final Integer toId, 
                        final BigDecimal amount) {
        
        transactionTemplate.execute(new TransactionCallback<Void>() {
            @Override
            public Void doInTransaction(TransactionStatus status) {
                try {
                    // 扣款
                    String sql1 = "UPDATE account SET balance = balance - ? WHERE id = ?";
                    jdbcTemplate.update(sql1, amount, fromId);
                    
                    // 模拟异常
                    // int i = 1 / 0;
                    
                    // 加款
                    String sql2 = "UPDATE account SET balance = balance + ? WHERE id = ?";
                    jdbcTemplate.update(sql2, amount, toId);
                    
                } catch (Exception e) {
                    status.setRollbackOnly();
                    throw e;
                }
                return null;
            }
        });
    }
}

2. 声明式事务

XML 配置方式
<!-- 配置事务管理器 -->
<bean id="transactionManager" 
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 开启注解驱动的事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
注解方式
@Service
public class AccountService {
    
    @Autowired
    private AccountDao accountDao;
    
    @Transactional(propagation = Propagation.REQUIRED, 
                   isolation = Isolation.DEFAULT,
                   timeout = 30,
                   readOnly = false,
                   rollbackFor = Exception.class)
    public void transfer(Integer fromId, Integer toId, BigDecimal amount) {
        // 事务内的所有操作要么全部成功,要么全部回滚
        accountDao.decreaseBalance(fromId, amount);
        accountDao.increaseBalance(toId, amount);
    }
}

数据源配置

1. 多种数据源对比

数据源特点适用场景
DriverManagerDataSourceSpring 内置,无连接池开发测试
HikariCP高性能,轻量级生产环境推荐
Druid阿里开源,监控强大需要监控的场景
C3P0老牌连接池传统项目

2. HikariCP 配置

@Bean
public DataSource dataSource() {
    HikariConfig config = new HikariConfig();
    config.setJdbcUrl("jdbc:mysql://localhost:3306/spring_demo");
    config.setUsername("root");
    config.setPassword("password");
    config.setDriverClassName("com.mysql.cj.jdbc.Driver");
    
    // 连接池配置
    config.setMaximumPoolSize(20);
    config.setMinimumIdle(5);
    config.setConnectionTimeout(30000);
    config.setIdleTimeout(600000);
    config.setMaxLifetime(1800000);
    
    return new HikariDataSource(config);
}

3. 配置文件外置

application.properties
# 数据库配置
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_demo?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=password

# 连接池配置
jdbc.initialSize=5
jdbc.maxActive=20
jdbc.minIdle=5
jdbc.maxWait=60000
配置类
@Configuration
@PropertySource("classpath:application.properties")
public class DataSourceConfig {
    
    @Value("${jdbc.driver}")
    private String driver;
    
    @Value("${jdbc.url}")
    private String url;
    
    @Value("${jdbc.username}")
    private String username;
    
    @Value("${jdbc.password}")
    private String password;
    
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

高级特性

1. 存储过程调用

public class StoredProcedureDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void callProcedure(final Integer userId) {
        jdbcTemplate.execute(
            new CallableStatementCreator() {
                public CallableStatement createCallableStatement(Connection con) 
                        throws SQLException {
                    String proc = "{call update_user_status(?, ?)}";
                    CallableStatement cs = con.prepareCall(proc);
                    cs.setInt(1, userId);
                    cs.registerOutParameter(2, Types.VARCHAR);
                    return cs;
                }
            },
            new CallableStatementCallback() {
                public Object doInCallableStatement(CallableStatement cs) 
                        throws SQLException {
                    cs.execute();
                    return cs.getString(2);
                }
            }
        );
    }
}

2. LOB 处理

public class LobDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Autowired
    private LobHandler lobHandler;
    
    // 保存大文本
    public void saveClob(final Integer id, final String content) {
        String sql = "UPDATE article SET content = ? WHERE id = ?";
        
        jdbcTemplate.execute(sql, new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
            protected void setValues(PreparedStatement ps, LobCreator lobCreator) 
                    throws SQLException {
                lobCreator.setClobAsString(ps, 1, content);
                ps.setInt(2, id);
            }
        });
    }
    
    // 读取大文本
    public String getClob(Integer id) {
        String sql = "SELECT content FROM article WHERE id = ?";
        
        return jdbcTemplate.queryForObject(sql, new RowMapper<String>() {
            public String mapRow(ResultSet rs, int rowNum) throws SQLException {
                return lobHandler.getClobAsString(rs, "content");
            }
        }, id);
    }
}

3. 批处理优化

public class BatchDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void batchInsertWithPreparedStatement(final List<Account> accounts) {
        String sql = "INSERT INTO account (username, balance) VALUES (?, ?)";
        
        jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                Account account = accounts.get(i);
                ps.setString(1, account.getUsername());
                ps.setBigDecimal(2, account.getBalance());
            }
            
            @Override
            public int getBatchSize() {
                return accounts.size();
            }
        });
    }
}

最佳实践

1. 异常处理

@Repository
public class SafeAccountDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public Account findById(Integer id) {
        try {
            String sql = "SELECT * FROM account WHERE id = ?";
            return jdbcTemplate.queryForObject(sql, 
                new BeanPropertyRowMapper<>(Account.class), id);
        } catch (EmptyResultDataAccessException e) {
            // 没有找到数据
            return null;
        } catch (DataAccessException e) {
            // 数据访问异常
            throw new RuntimeException("数据库访问异常", e);
        }
    }
}

2. SQL 注入防护

// 错误示例 - 存在 SQL 注入风险
public List<Account> searchAccountsUnsafe(String username) {
    String sql = "SELECT * FROM account WHERE username = '" + username + "'";
    return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Account.class));
}

// 正确示例 - 使用参数化查询
public List<Account> searchAccountsSafe(String username) {
    String sql = "SELECT * FROM account WHERE username = ?";
    return jdbcTemplate.query(sql, 
        new BeanPropertyRowMapper<>(Account.class), username);
}

3. 资源管理

@Component
public class ResourceManagement {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void processLargeResult() {
        String sql = "SELECT * FROM large_table";
        
        jdbcTemplate.query(sql, new RowCallbackHandler() {
            @Override
            public void processRow(ResultSet rs) throws SQLException {
                // 逐行处理,避免内存溢出
                processRecord(rs);
            }
        });
    }
    
    private void processRecord(ResultSet rs) throws SQLException {
        // 处理每一行数据
    }
}

常见问题

问题 1:连接池耗尽

原因:事务没有正确提交或回滚,连接没有释放

解决方案:

// 配置连接池超时
dataSource.setRemoveAbandoned(true);
dataSource.setRemoveAbandonedTimeout(180);
dataSource.setLogAbandoned(true);

问题 2:MySQL 8.0 连接问题

解决方案:

# 正确的连接字符串
jdbc.url=jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true

# 驱动类名
jdbc.driver=com.mysql.cj.jdbc.Driver

问题 3:中文乱码

解决方案:

# 在 URL 中指定编码
jdbc.url=jdbc:mysql://localhost:3306/db?characterEncoding=utf8&useUnicode=true

问题 4:事务不生效

原因:

  1. 没有开启事务管理
  2. 方法不是 public
  3. 同一个类内部调用
  4. 异常被 catch 没有抛出

解决方案:

// 确保事务注解在 public 方法上
@Transactional(rollbackFor = Exception.class)
public void transfer() {
    // 事务操作
}

总结

Spring JDBC 通过 JdbcTemplate 提供了简单而强大的数据库访问能力:

🎯 核心优势

  1. 简化开发:减少样板代码,专注业务逻辑
  2. 资源管理:自动处理连接的创建和释放
  3. 异常翻译:将 JDBC 异常转换为更有意义的 Spring 异常
  4. 事务支持:与 Spring 事务管理无缝集成

📊 适用场景

  • 简单查询:需要直接执行 SQL 的场景
  • 存储过程:调用数据库存储过程
  • 批处理:大量数据的批量操作
  • 特殊需求:需要精确控制 SQL 执行

🚀 最佳实践

  1. 使用参数化查询,避免 SQL 注入
  2. 合理配置连接池,提高性能
  3. 正确处理异常,保证程序稳定性
  4. 利用事务管理,保证数据一致性

相关文章

前后章节导航

相关主题