以下是一个完整的 Spring JDBC 编程式事务示例,包含批量插入、事务管理、XML 配置和单元测试:
1. 项目依赖(pom.xml)
xml
<dependencies>
<!-- Spring JDBC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.20</version>
</dependency>
<!-- Spring 事务管理 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.20</version>
</dependency>
<!-- 数据库驱动(MySQL) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
2. applicationContext.xml 配置
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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 数据源配置 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test_db?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
<property name="maximumPoolSize" value="5"/>
</bean>
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- DAO -->
<bean id="employeeDao" class="com.example.dao.EmployeeDao">
<constructor-arg ref="jdbcTemplate"/>
</bean>
<!-- Service -->
<bean id="employeeService" class="com.example.service.EmployeeService">
<constructor-arg ref="employeeDao"/>
<constructor-arg ref="transactionManager"/>
</bean>
</beans>
3. DAO 层代码
java
package com.example.dao;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
public class EmployeeDao {
private final JdbcTemplate jdbcTemplate;
public EmployeeDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// 批量插入员工数据
public void batchInsertEmployees(List<Employee> employees) {
String sql = "INSERT INTO employee(name, age, department) VALUES(?, ?, ?)";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Employee emp = employees.get(i);
ps.setString(1, emp.getName());
ps.setInt(2, emp.getAge());
ps.setString(3, emp.getDepartment());
}
@Override
public int getBatchSize() {
return employees.size();
}
});
}
}
4. Service 层代码(编程式事务)
java
package com.example.service;
import com.example.dao.EmployeeDao;
import com.example.model.Employee;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.util.List;
public class EmployeeService {
private final EmployeeDao employeeDao;
private final PlatformTransactionManager transactionManager;
public EmployeeService(EmployeeDao employeeDao, PlatformTransactionManager transactionManager) {
this.employeeDao = employeeDao;
this.transactionManager = transactionManager;
}
// 批量插入数据(事务控制)
public void batchInsertWithTransaction(List<Employee> employees) {
// 定义事务属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
// 开启事务
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 执行批量插入
employeeDao.batchInsertEmployees(employees);
// 模拟业务校验(例如:插入第10条数据时抛出异常)
if (employees.size() >= 10) {
throw new RuntimeException("Simulated failure after 10 records");
}
// 提交事务
transactionManager.commit(status);
} catch (Exception e) {
// 回滚事务
transactionManager.rollback(status);
throw new RuntimeException("Batch insert failed, rolled back", e);
}
}
}
5. Model 类
java
package com.example.model;
public class Employee {
private String name;
private int age;
private String department;
// 构造方法、Getter/Setter 略
}
6. 单元测试
java
import com.example.model.Employee;
import com.example.service.EmployeeService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class EmployeeServiceTest {
@Test
public void testBatchInsertSuccess() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
EmployeeService service = context.getBean(EmployeeService.class);
// 准备测试数据(少于10条)
List<Employee> employees = generateEmployees(5);
// 执行插入
service.batchInsertWithTransaction(employees);
// 验证插入结果(此处需要根据实际数据库查询验证)
// 例如:通过 JdbcTemplate 查询数据库记录数
assertEquals(5, queryEmployeeCount());
context.close();
}
@Test(expected = RuntimeException.class)
public void testBatchInsertFailure() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
EmployeeService service = context.getBean(EmployeeService.class);
// 准备测试数据(触发异常)
List<Employee> employees = generateEmployees(15);
try {
service.batchInsertWithTransaction(employees);
} finally {
// 验证数据未插入(回滚)
assertEquals(0, queryEmployeeCount());
}
context.close();
}
private List<Employee> generateEmployees(int count) {
List<Employee> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
Employee emp = new Employee();
emp.setName("Employee" + i);
emp.setAge(20 + i);
emp.setDepartment("IT");
list.add(emp);
}
return list;
}
private int queryEmployeeCount() {
// 实际项目中通过 JdbcTemplate 查询数据库
// 示例代码:
// return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM employee", Integer.class);
return 0; // 需根据实际情况实现
}
}
关键点说明
-
事务控制:
- 使用
PlatformTransactionManager
手动控制事务边界。 - 在异常时调用
rollback()
,正常时调用commit()
。
- 使用
-
批量插入:
- 使用
JdbcTemplate.batchUpdate()
执行批量操作。 - 通过
BatchPreparedStatementSetter
设置参数。
- 使用
-
测试策略:
- 成功场景:插入少于 10 条数据,验证事务提交。
- 失败场景:插入超过 10 条数据触发异常,验证事务回滚。
-
数据库清理:
- 在
testBatchInsertFailure
的finally
块中验证数据回滚。 - 实际项目中可通过
@Before
和@After
方法初始化和清理数据。
- 在
运行测试
-
确保 MySQL 数据库已启动,创建
test_db
数据库和employee
表:sqlCREATE DATABASE test_db; USE test_db; CREATE TABLE employee ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), age INT, department VARCHAR(50) );
-
运行测试类
EmployeeServiceTest
,观察事务是否按预期工作。
通过编程式事务管理能够确保批量操作的原子性,同时掌握 XML 配置和手动事务控制的完整流程。