文章目录
第二章、Spring 事务管理
一、Spring事务管理介绍
1、什么是事务
事务是以一种可靠的、一致的方式,访问和操作数据库的程序单元。
说人话:要么把事情做完,要么什么都不做,不要做一半。
事务依赖于数据库实现,MySQL通过事务区作为数据缓冲地带。
二、编程式事务
编程式事务是指通过代码手动提交回滚事务的事务控制方法。
SpringJDBC通过TransactionManager事务管理器实现事务控制。
事务管理器提供commit / rollback方法进行事务提交与回滚。
1、代码演示
为了看清过程,pom.xml中引入logback日志组件
<!--logback日志组件,Spring框架默认集成-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
在com.ql.spring.jdbc.service包下创建带事务的业务代码EmployeeService
package com.ql.spring.jdbc.service;
import com.ql.spring.jdbc.dao.EmployeeDao;
import com.ql.spring.jdbc.entity.Employee;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.util.Date;
public class EmployeeService {
private EmployeeDao employeeDao;
private DataSourceTransactionManager transactionManager;
public void batchImport(){
//定义了事务默认的标准配置
TransactionDefinition definition = new DefaultTransactionDefinition();
//开始一个事务,返回事务状态,事务状态说明当前事务的执行阶段
TransactionStatus status = transactionManager.getTransaction(definition);
try {
for (int i = 1; i <=10 ; i++) {
if(i==3){
throw new RuntimeException("意料之外的异常");
}
Employee employee = new Employee();
employee.setEno(8000+i);
employee.setEname("员工"+i);
employee.setSalary(4000f);
employee.setDname("市场部");
employee.setHiredate(new Date());
employeeDao.insert(employee);
}
//提交事务
transactionManager.commit(status);
} catch (RuntimeException e) {
//回滚事务
transactionManager.rollback(status);
throw e;
}
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
public DataSourceTransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(DataSourceTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
}
然后在applicationContext.xml中配置事务
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="employeeService" class="com.ql.spring.jdbc.service.EmployeeService">
<property name="employeeDao" ref="employeeDao"/>
<property name="transactionManager" ref="transactionManager"/>
</bean>
最后在JdbcTemplateTestor测试类中编写测试方法并运行
@Test
public void testBatchImport(){
employeeService.batchImport();
System.out.println("批量导入成功");
}
运行结果为,数据库并没有插入数据。
三、声明式事务
编程式事务虽然易于理解,但是由于每个程序员的水平不同,可能出现遗忘事务控制的情况,声明式事务解决了这种情况。
声明式事务指在不修改源码情况下通过配置形式,自动实现事务控制,声明式事务本质就是AOP环绕通知。
当目标方法执行成功时,自动提交事务。
当目标方法抛出运行时异常时,自动事务回滚。
1、配置过程
配置TransactionManeger事务管理器。
配置事务通知与事务属性。
为事务通知绑定PointCut切点。
2、代码演示
打开EmployeeService类,去掉编程式事务部分
package com.ql.spring.jdbc.service;
import com.ql.spring.jdbc.dao.EmployeeDao;
import com.ql.spring.jdbc.entity.Employee;
import java.util.Date;
public class EmployeeService {
private EmployeeDao employeeDao;
public void batchImport(){
for (int i = 1; i <=10 ; i++) {
if(i==3){
throw new RuntimeException("意料之外的异常");
}
Employee employee = new Employee();
employee.setEno(8000+i);
employee.setEname("员工"+i);
employee.setSalary(4000f);
employee.setDname("市场部");
employee.setHiredate(new Date());
employeeDao.insert(employee);
}
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
}
因为声明式事务是基于AOP的,所以在pom.xml中引入aspectjweaver依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
然后在配置文件applicationContext.xml中添加事务和AOP的命名空间和声明式事务配置
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
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/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!--数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ql-spring-jdbc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--JdbcTemplate提供数据CRUD的API-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="employeeDao" class="com.ql.spring.jdbc.dao.EmployeeDao">
<!--为Dao注入JdbcTemplate对象-->
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<bean id="employeeService" class="com.ql.spring.jdbc.service.EmployeeService">
<property name="employeeDao" ref="employeeDao"/>
</bean>
<!--1.事务管理器,用于创建事务/提交/回滚-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2.事务通知配置,决定哪些方法使用事务,哪些方法不适用事务-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--目标方法名为batchImport时,启用声明式事务,成功提交,运行时异常回滚-->
<tx:method name="batchImport" propagation="REQUIRED"/>
<tx:method name="batch*" propagation="REQUIRED"/>
<!--设置所有findXXXX方法不需要使用事务-->
<tx:method name="find*" propagation="NOT_SUPPORTED" read-only="true"/>
<tx:method name="get*" propagation="NOT_SUPPORTED" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--3.定义声明式事务的作用范围-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.ql..*Service.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
然后运行测试方法结果为
四、事务传播行为
事务传播行为是指多个拥有事务的方法在嵌套调用时的事务控制方式。
XML:<tx:method name=“…” propagation=“REQUIRED”/>
注解:@Transactional(propagaion=Propagation.REQUIRED)
1、事务传播行为七种类型
2、PROPAGATION_REQUIRED
在com.ql.spring.jdbc.service包下创建BatchService类,编写两个批量处理方法
package com.ql.spring.jdbc.service;
import com.ql.spring.jdbc.dao.EmployeeDao;
import com.ql.spring.jdbc.entity.Employee;
import java.util.Date;
public class BatchService {
private EmployeeDao employeeDao;
public void importJob1(){
for (int i = 1; i <=10 ; i++) {
Employee employee = new Employee();
employee.setEno(8000+i);
employee.setEname("研发部员工"+i);
employee.setSalary(4000f);
employee.setDname("研发部");
employee.setHiredate(new Date());
employeeDao.insert(employee);
}
}
public void importJob2(){
for (int i = 1; i <=10 ; i++) {
Employee employee = new Employee();
employee.setEno(9000+i);
employee.setEname("市场部员工"+i);
employee.setSalary(3000f);
employee.setDname("市场部");
employee.setHiredate(new Date());
employeeDao.insert(employee);
}
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
}
然后在EmployeeService中创建一个方法startImportJob引用上面的两个方法
public void startImportJob(){
batchService.importJob1();
if(1==1){
throw new RuntimeException("意料之外的异常");
}
batchService.importJob2();
System.out.println("批量导入成功");
}
在applicationContext.xml中配置BatchService类的两个方法事务传播行为类型
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--目标方法名为batchImport时,启用声明式事务,成功提交,运行时异常回滚-->
<tx:method name="batchImport" propagation="REQUIRED"/>
<tx:method name="batch*" propagation="REQUIRED"/>
<!--设置所有findXXXX方法不需要使用事务-->
<tx:method name="find*" propagation="NOT_SUPPORTED" read-only="true"/>
<tx:method name="get*" propagation="NOT_SUPPORTED" read-only="true"/>
<tx:method name="importJob1" propagation="REQUIRED"/>
<tx:method name="importJob2" propagation="REQUIRED"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
在测试类JdbcTemplateTestor.java中添加测试方法
@Test
public void testStartImportJob(){
employeeService.startImportJob();
}
运行后数据库并没有插入任何数据。
3、PROPAGATION_REQUIRED_NEW
在applicationContext.xml中修改BatchService类的两个方法事务传播行为类型
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--目标方法名为batchImport时,启用声明式事务,成功提交,运行时异常回滚-->
<tx:method name="batchImport" propagation="REQUIRED"/>
<tx:method name="batch*" propagation="REQUIRED"/>
<!--设置所有findXXXX方法不需要使用事务-->
<tx:method name="find*" propagation="NOT_SUPPORTED" read-only="true"/>
<tx:method name="get*" propagation="NOT_SUPPORTED" read-only="true"/>
<tx:method name="importJob1" propagation="REQUIRES_NEW"/>
<tx:method name="importJob2" propagation="REQUIRES_NEW"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
再次运行后数据库中成功插入了第一个方法的新增数据,第二个方法执行之前因为抛出异常而没被插入数据。
五、注解配置声明式事务
@Transactional - 事务注解
1、代码演示
修改配置文件applicationContext.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"
xmlns:aop="http://www.springframework.org/schema/aop"
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/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.ql"/>
<!--数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ql-spring-jdbc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--JdbcTemplate提供数据CRUD的API-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务管理器,用于创建事务/提交/回滚-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--启用注解形式声明式事务-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
然后在EmployeeDao加上@Repository注解,最后在两个Service类加上Bean注解和事务注解,如下:
package com.ql.spring.jdbc.service;
import com.ql.spring.jdbc.dao.EmployeeDao;
import com.ql.spring.jdbc.entity.Employee;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
@Service
@Transactional(propagation = Propagation.NOT_SUPPORTED,readOnly = true)
public class BatchService {
@Resource
private EmployeeDao employeeDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void importJob1(){
for (int i = 1; i <=10 ; i++) {
Employee employee = new Employee();
employee.setEno(8000+i);
employee.setEname("研发部员工"+i);
employee.setSalary(4000f);
employee.setDname("研发部");
employee.setHiredate(new Date());
employeeDao.insert(employee);
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void importJob2(){
for (int i = 1; i <=10 ; i++) {
Employee employee = new Employee();
employee.setEno(9000+i);
employee.setEname("市场部员工"+i);
employee.setSalary(3000f);
employee.setDname("市场部");
employee.setHiredate(new Date());
employeeDao.insert(employee);
}
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
}
package com.ql.spring.jdbc.service;
import com.ql.spring.jdbc.dao.EmployeeDao;
import com.ql.spring.jdbc.entity.Employee;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
@Service
//声明式事务核心注解
//放在类上,将声明式事务配置应用于当前类所有方法,默认事务传播为 REQUIRED
@Transactional
public class EmployeeService {
@Resource
private EmployeeDao employeeDao;
@Resource
private BatchService batchService;
@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
public Employee findById(Integer eno){
return employeeDao.findById(eno);
}
public void batchImport(){
for (int i = 1; i <=10 ; i++) {
if(i==3){
throw new RuntimeException("意料之外的异常");
}
Employee employee = new Employee();
employee.setEno(8000+i);
employee.setEname("员工"+i);
employee.setSalary(4000f);
employee.setDname("市场部");
employee.setHiredate(new Date());
employeeDao.insert(employee);
}
}
public void startImportJob(){
batchService.importJob1();
if(1==1){
throw new RuntimeException("意料之外的异常");
}
batchService.importJob2();
System.out.println("批量导入成功");
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
public BatchService getBatchService() {
return batchService;
}
public void setBatchService(BatchService batchService) {
this.batchService = batchService;
}
}
运行后效果跟XML配置一致。