Spring JDBCTemplate 核心架構(gòu)與執(zhí)行流程詳解:從原理到實踐
圖表 系統(tǒng)解析了 Spring JDBCTemplate 的核心架構(gòu)與執(zhí)行流程,包含以下內(nèi)容:
- 架構(gòu)設(shè)計:展示
JdbcTemplate
、DataSource
、RowMapper
等核心組件的協(xié)作關(guān)系,對比傳統(tǒng) JDBC 的簡化設(shè)計。 - 執(zhí)行流程:以查詢和更新操作為例,詳細(xì)拆解 SQL 執(zhí)行、參數(shù)綁定、結(jié)果映射、資源釋放等關(guān)鍵步驟。
- 交互與狀態(tài):通過序列圖、流程圖和狀態(tài)圖,直觀呈現(xiàn)組件交互時序和操作狀態(tài)轉(zhuǎn)換。
- 擴展能力:涵蓋
NamedParameterJdbcTemplate
和自定義RowMapper
等高級用法。
適合開發(fā)者快速掌握 JDBCTemplate 的 自動化資源管理、異常封裝 和 高效數(shù)據(jù)訪問 原理,為 Spring 數(shù)據(jù)層開發(fā)提供清晰指導(dǎo)。
一、Spring JDBCTemplate 相關(guān)設(shè)計圖
1、核心類圖
圖片
2、JDBCTemplate 核心組件交互圖
圖片
3. 查詢操作狀態(tài)圖
圖片
4. JDBCTemplate 執(zhí)行流程圖(以 query 方法為例)
圖片
5. 更新操作流程圖
圖片
這些圖表展示了:
- JDBCTemplate 的核心組件及其關(guān)系
- 執(zhí)行 SQL 查詢時的完整流程
- 各組件之間的交互順序
- 操作的不同狀態(tài)轉(zhuǎn)換
關(guān)鍵點說明:
- JdbcTemplate 通過 DataSource 獲取數(shù)據(jù)庫連接
- 使用 PreparedStatement 防止 SQL 注入
- RowMapper 負(fù)責(zé)結(jié)果集到對象的映射
- 自動處理資源的打開和關(guān)閉
- 統(tǒng)一轉(zhuǎn)換 SQLException 為 DataAccessException
二、Spring JDBCTemplate 入門案例
JDBCTemplate 是 Spring 框架對 JDBC 的封裝,它簡化了傳統(tǒng) JDBC 的開發(fā)流程,減少了大量樣板代碼。下面是一個完整的 JDBCTemplate 入門案例。
1. 準(zhǔn)備工作
1.1 添加依賴
首先需要在項目中添加 Spring JDBC 和數(shù)據(jù)庫驅(qū)動的依賴(以 Maven 為例):
<!-- Spring JDBC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.18</version>
</dependency>
<!-- 數(shù)據(jù)庫驅(qū)動 (以MySQL為例) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- 數(shù)據(jù)源 (以HikariCP為例) -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
1.2 創(chuàng)建數(shù)據(jù)庫表
假設(shè)我們有一個簡單的用戶表:
CREATETABLE users (
id INTPRIMARYKEYAUTO_INCREMENT,
name VARCHAR(50)NOTNULL,
age INT,
email VARCHAR(100)
);
2. 配置數(shù)據(jù)源
2.1 Java 配置方式
importcom.zaxxer.hikari.HikariDataSource;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.jdbc.core.JdbcTemplate;
@Configuration
publicclassAppConfig{
@Bean
publicHikariDataSourcedataSource(){
HikariDataSource dataSource =newHikariDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/your_database");
dataSource.setUsername("your_username");
dataSource.setPassword("your_password");
dataSource.setMaximumPoolSize(10);
return dataSource;
}
@Bean
publicJdbcTemplatejdbcTemplate(HikariDataSource dataSource){
returnnewJdbcTemplate(dataSource);
}
}
2.2 XML 配置方式
<beansxmlns="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">
<beanid="dataSource"class="com.zaxxer.hikari.HikariDataSource">
<propertyname="driverClassName"value="com.mysql.cj.jdbc.Driver"/>
<propertyname="jdbcUrl"value="jdbc:mysql://localhost:3306/your_database"/>
<propertyname="username"value="your_username"/>
<propertyname="password"value="your_password"/>
<propertyname="maximumPoolSize"value="10"/>
</bean>
<beanid="jdbcTemplate"class="org.springframework.jdbc.core.JdbcTemplate">
<propertyname="dataSource"ref="dataSource"/>
</bean>
</beans>
3. 創(chuàng)建實體類
publicclassUser{
privateInteger id;
privateString name;
privateInteger age;
privateString email;
// 構(gòu)造方法、getter和setter、toString等
publicUser(){}
publicUser(String name,Integer age,String email){
this.name = name;
this.age = age;
this.email = email;
}
// 省略其他getter和setter
@Override
publicStringtoString(){
return"User{"+
"id="+ id +
", name='"+ name + ''' +
", age="+ age +
", email='"+ email + ''' +
'}';
}
}
4. 創(chuàng)建 DAO 類
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.jdbc.core.BeanPropertyRowMapper;
importorg.springframework.jdbc.core.JdbcTemplate;
importorg.springframework.jdbc.core.RowMapper;
importorg.springframework.stereotype.Repository;
importjava.util.List;
@Repository
publicclassUserDao{
@Autowired
privateJdbcTemplate jdbcTemplate;
// 添加用戶
publicintaddUser(User user){
String sql ="INSERT INTO users(name, age, email) VALUES(?, ?, ?)";
return jdbcTemplate.update(sql, user.getName(), user.getAge(), user.getEmail());
}
// 更新用戶
publicintupdateUser(User user){
String sql ="UPDATE users SET name=?, age=?, email=? WHERE id=?";
return jdbcTemplate.update(sql, user.getName(), user.getAge(), user.getEmail(), user.getId());
}
// 刪除用戶
publicintdeleteUser(Integer id){
String sql ="DELETE FROM users WHERE id=?";
return jdbcTemplate.update(sql, id);
}
// 根據(jù)ID查詢用戶
publicUsergetUserById(Integer id){
String sql ="SELECT * FROM users WHERE id=?";
RowMapper<User> rowMapper =newBeanPropertyRowMapper<>(User.class);
return jdbcTemplate.queryForObject(sql, rowMapper, id);
}
// 查詢所有用戶
publicList<User>getAllUsers(){
String sql ="SELECT * FROM users";
RowMapper<User> rowMapper =newBeanPropertyRowMapper<>(User.class);
return jdbcTemplate.query(sql, rowMapper);
}
// 查詢用戶數(shù)量
publicIntegergetCount(){
String sql ="SELECT COUNT(*) FROM users";
return jdbcTemplate.queryForObject(sql,Integer.class);
}
}
5. 測試類
importorg.springframework.context.ApplicationContext;
importorg.springframework.context.annotation.AnnotationConfigApplicationContext;
publicclassJdbcTemplateDemo{
publicstaticvoidmain(String[] args){
// 加載配置類
ApplicationContext context =newAnnotationConfigApplicationContext(AppConfig.class);
// 獲取UserDao實例
UserDao userDao = context.getBean(UserDao.class);
// 添加用戶
User user1 =newUser("張三",25,"zhangsan@example.com");
int result = userDao.addUser(user1);
System.out.println("添加用戶結(jié)果: "+ result);
// 查詢所有用戶
System.out.println("所有用戶:");
userDao.getAllUsers().forEach(System.out::println);
// 更新用戶
User userToUpdate = userDao.getUserById(1);
userToUpdate.setName("李四");
userToUpdate.setEmail("lisi@example.com");
result = userDao.updateUser(userToUpdate);
System.out.println("更新用戶結(jié)果: "+ result);
// 再次查詢所有用戶
System.out.println("更新后的所有用戶:");
userDao.getAllUsers().forEach(System.out::println);
// 查詢用戶數(shù)量
System.out.println("用戶總數(shù): "+ userDao.getCount());
// 刪除用戶
result = userDao.deleteUser(1);
System.out.println("刪除用戶結(jié)果: "+ result);
}
}
6. 核心方法說明
JDBCTemplate 提供了多種便捷方法:
- update() - 執(zhí)行 INSERT/UPDATE/DELETE 語句
jdbcTemplate.update(sql, params...);
- queryForObject() - 查詢單個對象
jdbcTemplate.queryForObject(sql, rowMapper, params...);
- query() - 查詢對象列表
jdbcTemplate.query(sql, rowMapper, params...);
- queryForList() - 查詢結(jié)果集到List[map]
jdbcTemplate.queryForList(sql, params...);
- execute() - 執(zhí)行任意SQL語句
jdbcTemplate.execute(sql);
三、Spring JDBCTemplate 入門案例
下面我將通過一個完整的 Spring Boot 整合 JDBCTemplate 的案例,詳細(xì)介紹如何使用 JDBCTemplate 進(jìn)行數(shù)據(jù)庫操作。
1. 創(chuàng)建 Spring Boot 項目
首先創(chuàng)建一個 Spring Boot 項目,可以使用 Spring Initializr 或 IDE 創(chuàng)建。
1.1 添加必要依賴
在 pom.xml
中添加以下依賴:
<dependencies>
<!-- Spring Boot Starter Web (可選,用于創(chuàng)建Controller測試) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 數(shù)據(jù)庫驅(qū)動 (以MySQL為例) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok (可選,簡化代碼) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 測試依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2. 配置數(shù)據(jù)源
2.1 配置文件
在 application.properties
或 application.yml
中配置數(shù)據(jù)源:
# application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_jdbc?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# HikariCP連接池配置 (Spring Boot默認(rèn)使用HikariCP)
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
或者使用 YAML 格式:
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/springboot_jdbc?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
username: root
password:123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size:10
minimum-idle:5
idle-timeout:600000
max-lifetime:1800000
connection-timeout:30000
3. 創(chuàng)建實體類
importlombok.Data;
@Data
publicclassUser{
privateInteger id;
privateString name;
privateInteger age;
privateString email;
// 無參構(gòu)造和有參構(gòu)造會被Lombok自動生成
}
4. 創(chuàng)建 Repository (DAO)
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.jdbc.core.BeanPropertyRowMapper;
importorg.springframework.jdbc.core.JdbcTemplate;
importorg.springframework.jdbc.core.RowMapper;
importorg.springframework.stereotype.Repository;
importjava.util.List;
@Repository
publicclassUserRepository{
privatefinalJdbcTemplate jdbcTemplate;
@Autowired
publicUserRepository(JdbcTemplate jdbcTemplate){
this.jdbcTemplate = jdbcTemplate;
}
// 創(chuàng)建表
publicvoidcreateTable(){
String sql ="CREATE TABLE IF NOT EXISTS users ("+
"id INT PRIMARY KEY AUTO_INCREMENT,"+
"name VARCHAR(50) NOT NULL,"+
"age INT,"+
"email VARCHAR(100)"+
")";
jdbcTemplate.execute(sql);
}
// 添加用戶
publicintsave(User user){
String sql ="INSERT INTO users(name, age, email) VALUES(?, ?, ?)";
return jdbcTemplate.update(sql, user.getName(), user.getAge(), user.getEmail());
}
// 批量添加用戶
publicint[]batchSave(List<User> users){
String sql ="INSERT INTO users(name, age, email) VALUES(?, ?, ?)";
return jdbcTemplate.batchUpdate(sql, users, users.size(),
(ps, user)->{
ps.setString(1, user.getName());
ps.setInt(2, user.getAge());
ps.setString(3, user.getEmail());
});
}
// 更新用戶
publicintupdate(User user){
String sql ="UPDATE users SET name=?, age=?, email=? WHERE id=?";
return jdbcTemplate.update(sql, user.getName(), user.getAge(), user.getEmail(), user.getId());
}
// 刪除用戶
publicintdeleteById(Integer id){
String sql ="DELETE FROM users WHERE id=?";
return jdbcTemplate.update(sql, id);
}
// 根據(jù)ID查詢用戶
publicUserfindById(Integer id){
String sql ="SELECT * FROM users WHERE id=?";
RowMapper<User> rowMapper =newBeanPropertyRowMapper<>(User.class);
return jdbcTemplate.queryForObject(sql, rowMapper, id);
}
// 查詢所有用戶
publicList<User>findAll(){
String sql ="SELECT * FROM users";
RowMapper<User> rowMapper =newBeanPropertyRowMapper<>(User.class);
return jdbcTemplate.query(sql, rowMapper);
}
// 根據(jù)名稱查詢用戶
publicList<User>findByName(String name){
String sql ="SELECT * FROM users WHERE name LIKE ?";
RowMapper<User> rowMapper =newBeanPropertyRowMapper<>(User.class);
return jdbcTemplate.query(sql, rowMapper,"%"+ name +"%");
}
// 查詢用戶數(shù)量
publicIntegercount(){
String sql ="SELECT COUNT(*) FROM users";
return jdbcTemplate.queryForObject(sql,Integer.class);
}
}
5. 創(chuàng)建 Service 層
importorg.springframework.stereotype.Service;
importorg.springframework.transaction.annotation.Transactional;
importjava.util.List;
@Service
publicclassUserService{
privatefinalUserRepository userRepository;
publicUserService(UserRepository userRepository){
this.userRepository = userRepository;
}
// 初始化表
publicvoidinitTable(){
userRepository.createTable();
}
// 保存用戶
publicintsave(User user){
return userRepository.save(user);
}
// 批量保存用戶
@Transactional
publicint[]batchSave(List<User> users){
return userRepository.batchSave(users);
}
// 更新用戶
publicintupdate(User user){
return userRepository.update(user);
}
// 刪除用戶
publicintdeleteById(Integer id){
return userRepository.deleteById(id);
}
// 根據(jù)ID查詢用戶
publicUserfindById(Integer id){
return userRepository.findById(id);
}
// 查詢所有用戶
publicList<User>findAll(){
return userRepository.findAll();
}
// 根據(jù)名稱查詢用戶
publicList<User>findByName(String name){
return userRepository.findByName(name);
}
// 查詢用戶數(shù)量
publicIntegercount(){
return userRepository.count();
}
}
6. 創(chuàng)建 Controller (可選)
importorg.springframework.web.bind.annotation.*;
importjava.util.List;
@RestController
@RequestMapping("/api/users")
publicclassUserController{
privatefinalUserService userService;
publicUserController(UserService userService){
this.userService = userService;
}
@PostMapping
publicintsave(@RequestBodyUser user){
return userService.save(user);
}
@PutMapping
publicintupdate(@RequestBodyUser user){
return userService.update(user);
}
@DeleteMapping("/{id}")
publicintdeleteById(@PathVariableInteger id){
return userService.deleteById(id);
}
@GetMapping("/{id}")
publicUserfindById(@PathVariableInteger id){
return userService.findById(id);
}
@GetMapping
publicList<User>findAll(){
return userService.findAll();
}
@GetMapping("/search")
publicList<User>findByName(@RequestParamString name){
return userService.findByName(name);
}
@GetMapping("/count")
publicIntegercount(){
return userService.count();
}
}
7. 測試代碼
7.1 單元測試
importorg.junit.jupiter.api.BeforeEach;
importorg.junit.jupiter.api.Test;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.boot.test.context.SpringBootTest;
importjava.util.Arrays;
importjava.util.List;
importstaticorg.junit.jupiter.api.Assertions.*;
@SpringBootTest
classUserServiceTest{
@Autowired
privateUserService userService;
@BeforeEach
voidsetUp(){
userService.initTable();
}
@Test
voidtestCRUD(){
// 測試保存
User user =newUser();
user.setName("張三");
user.setAge(25);
user.setEmail("zhangsan@example.com");
int result = userService.save(user);
assertEquals(1, result);
// 測試查詢
List<User> users = userService.findAll();
assertFalse(users.isEmpty());
assertEquals("張三", users.get(0).getName());
// 測試更新
User updatedUser = users.get(0);
updatedUser.setName("李四");
result = userService.update(updatedUser);
assertEquals(1, result);
// 驗證更新
User foundUser = userService.findById(updatedUser.getId());
assertEquals("李四", foundUser.getName());
// 測試刪除
result = userService.deleteById(foundUser.getId());
assertEquals(1, result);
// 驗證刪除
assertEquals(0, userService.count());
}
@Test
voidtestBatchSave(){
List<User> users =Arrays.asList(
newUser(null,"張三",25,"zhangsan@example.com"),
newUser(null,"李四",30,"lisi@example.com"),
newUser(null,"王五",28,"wangwu@example.com")
);
int[] results = userService.batchSave(users);
assertEquals(3, results.length);
assertEquals(1, results[0]);
assertEquals(3, userService.count());
}
}
7.2 主程序測試
importorg.springframework.boot.CommandLineRunner;
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
importorg.springframework.context.annotation.Bean;
importjava.util.Arrays;
@SpringBootApplication
publicclassJdbcTemplateDemoApplication{
publicstaticvoidmain(String[] args){
SpringApplication.run(JdbcTemplateDemoApplication.class, args);
}
@Bean
publicCommandLineRunnerdemo(UserService userService){
return args ->{
// 初始化表
userService.initTable();
// 添加單個用戶
User user1 =newUser();
user1.setName("張三");
user1.setAge(25);
user1.setEmail("zhangsan@example.com");
userService.save(user1);
// 批量添加用戶
List<User> users =Arrays.asList(
newUser(null,"李四",30,"lisi@example.com"),
newUser(null,"王五",28,"wangwu@example.com")
);
userService.batchSave(users);
// 查詢所有用戶
System.out.println("所有用戶:");
userService.findAll().forEach(System.out::println);
// 查詢用戶數(shù)量
System.out.println("用戶總數(shù): "+ userService.count());
};
}
}
8. 高級特性
8.1 使用 NamedParameterJdbcTemplate
對于命名參數(shù)的支持:
importorg.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
importorg.springframework.jdbc.core.namedparam.MapSqlParameterSource;
importorg.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
importorg.springframework.jdbc.core.namedparam.SqlParameterSource;
importorg.springframework.stereotype.Repository;
importjava.util.List;
@Repository
publicclassUserNamedParameterRepository{
privatefinalNamedParameterJdbcTemplate namedParameterJdbcTemplate;
publicUserNamedParameterRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate){
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
// 使用命名參數(shù)添加用戶
publicintsave(User user){
String sql ="INSERT INTO users(name, age, email) VALUES(:name, :age, :email)";
SqlParameterSource paramSource =newBeanPropertySqlParameterSource(user);
return namedParameterJdbcTemplate.update(sql, paramSource);
}
// 使用命名參數(shù)批量添加
publicint[]batchSave(List<User> users){
String sql ="INSERT INTO users(name, age, email) VALUES(:name, :age, :email)";
SqlParameterSource[] batchParams = users.stream()
.map(BeanPropertySqlParameterSource::new)
.toArray(SqlParameterSource[]::new);
return namedParameterJdbcTemplate.batchUpdate(sql, batchParams);
}
// 使用命名參數(shù)查詢
publicList<User>findByNameAndAge(String name,Integer age){
String sql ="SELECT * FROM users WHERE name LIKE :name AND age > :age";
SqlParameterSource paramSource =newMapSqlParameterSource()
.addValue("name","%"+ name +"%")
.addValue("age", age);
return namedParameterJdbcTemplate.query(sql, paramSource,
newBeanPropertyRowMapper<>(User.class));
}
}
8.2 自定義 RowMapper
importorg.springframework.jdbc.core.RowMapper;
importjava.sql.ResultSet;
importjava.sql.SQLException;
publicclassUserRowMapperimplementsRowMapper<User>{
@Override
publicUsermapRow(ResultSet rs,int rowNum)throwsSQLException{
User user =newUser();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
user.setEmail(rs.getString("email"));
// 可以在這里進(jìn)行額外的處理,如數(shù)據(jù)轉(zhuǎn)換等
return user;
}
}
// 使用自定義RowMapper
publicList<User>findAllWithCustomMapper(){
String sql ="SELECT * FROM users";
return jdbcTemplate.query(sql,newUserRowMapper());
}