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&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

二、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.创建两个数据

2.测试删除数据

3.更新数据

补充:
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.单个查询

2.多项查询

数据库操作实战
完整示例:账户管理系统
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. 多种数据源对比
| 数据源 | 特点 | 适用场景 |
|---|---|---|
| DriverManagerDataSource | Spring 内置,无连接池 | 开发测试 |
| 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:事务不生效
原因:
- 没有开启事务管理
- 方法不是 public
- 同一个类内部调用
- 异常被 catch 没有抛出
解决方案:
// 确保事务注解在 public 方法上
@Transactional(rollbackFor = Exception.class)
public void transfer() {
// 事务操作
}
总结
Spring JDBC 通过 JdbcTemplate 提供了简单而强大的数据库访问能力:
🎯 核心优势
- 简化开发:减少样板代码,专注业务逻辑
- 资源管理:自动处理连接的创建和释放
- 异常翻译:将 JDBC 异常转换为更有意义的 Spring 异常
- 事务支持:与 Spring 事务管理无缝集成
📊 适用场景
- 简单查询:需要直接执行 SQL 的场景
- 存储过程:调用数据库存储过程
- 批处理:大量数据的批量操作
- 特殊需求:需要精确控制 SQL 执行
🚀 最佳实践
- 使用参数化查询,避免 SQL 注入
- 合理配置连接池,提高性能
- 正确处理异常,保证程序稳定性
- 利用事务管理,保证数据一致性
相关文章
前后章节导航
相关主题
- Spring 事务管理完整指南 - 声明式事务管理和数据库事务控制