Spring的事务管理
事务是什么
是用户定义的一组数据库操作的集合,要么全成功,要么全失败
事务特征
特征 | 含义 |
原子性 | 事务是不可分割的一组操作要么全做,要么全不做 |
一致性 | 数据库中数据从一个一致的状态转为另一个一致的状态 |
隔离性 | 不同的事务之间互不影响 |
持久性 | 事务一旦提交对数据的影响是持久的 |
事务隔离级别
查看MySQL默认隔离级别
SHOW VARIABLES LIKE ‘%tx_isolation%’
级别类型
类型 | 概念 |
读未提交 | 事务A和B,事务A未提交的数据,事务B可以读到。读取到的数据是脏数据 |
读已提交 | 事务A提交的数据,事务B才能读取到。避免了脏读 |
可重复读 | 直到事务A结束前,一直能读到相同的数据。避免了脏读和不可重复读(MySQL默认隔离级别) |
串行化 | 不允许读写并发操作,是最高隔离级别,写时读必须等待,效率低。避免幻读 |
事务并发问题
前提
不考虑隔离级别引发的安全问题
并发问题
并发问题 | 含义 |
脏读 | A事务读取到B事务未提交的数据(只要没提交就是脏读),因为A事务可能回滚 |
不可重复读 | A事务多次读取某数据,期间B事务对该数据做了更改并已提交,导致多次读的数据不一致UPDATE、DELETE |
幻读 | 事务A读取到了另外一个事务已经提交的数据,导致多次查询结果不一致INSERT |
Spring事务管理
- 编程式事务:将事务管理代码嵌入到业务方法中来控制事务提交和回滚
- 声明式事务:将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理(思想是AOP)
事务管理的核心接口
- Spring针对不同的持久层框架,提供接口不同的实现类。
- 事务管理器:PlatformTransactionManager
– getTranscation:获取事务状态
– commit:提交事务
– rollback:回滚事务 - Spring并不是直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给了Mybatis或者其他框架来实现事务管理
Spring中的声明式事务管理方式
基于XML的事务管理
步骤
- 配置事务管理器DataSourceTransactionManager,配置数据源
- 配置事务增强(设置事务操作的方法匹配)
- 配置切面(配置切入点,配置切面advice-ref,pointcut-ref)
通过在配置文件中配置事务相关的规则
AccountMapper.java
public interface AccountMapper {
// 转出
int outMoney(@Param("username")String username,@Param("money")int money);
// 转入
int inMoney(@Param("username")String username,@Param("money")int money);
}
AccountMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hpe.mapper.AccountMapper">
<!-- 转出 -->
<update id="outMoney">
UPDATE account SET money = money-#{money} WHERE username=#{username}
</update>
<!-- 转入 -->
<update id="inMoney">
UPDATE account SET money = money+#{money} WHERE username=#{username}
</update>
</mapper>
AccountService.java
public interface AccountService {
/**
* 转账的方法
* @param out 转出人
* @param in 转入人
* @param money 转账金额
*/
void updateAccount(String out,String in,int money);
}
AccountServiceImpl.java
package com.hpe.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hpe.mapper.AccountMapper;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
/* 实现转账的操作:
* 1.编程式事务
* 2.声明式事务
*/
@Autowired
private AccountMapper accountMapper;
@Override
public void updateAccount(String out, String in, int money) {
// 转出
accountMapper.outMoney(out, money);
// 模拟异常(算数异常)
//int i = 10/0;
// 转入
accountMapper.inMoney(in, money);
}
}
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 http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.hpe"></context:component-scan>
<!-- 加载外部资源文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置c3p0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 通过Spring管理SqlSessionFactory mapper接口 -->
<!-- 配置SqlSessionFactory为了使用 Spring来管理SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置mybatis核心配置文件的路径 -->
<property name="configLocation" value="classpath:SqlMapConfig.xml"></property>
<!-- 指定数据源 -->
<property name="dataSource" ref="dataSource"></property>
<!-- 指定批量创建别名的包 -->
<property name="typeAliasesPackage" value="com.hpe.po"></property>
</bean>
<!-- 批量创建Mapper接口实现类的Bean,可以不指定id
默认Bean是由id的,是Mapper接口的名称且首字母小写-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定需要创建实现类的Mapper接口所在的包
可以指定多个包,使用逗号隔开即可-->
<property name="basePackage" value="com.hpe.mapper"></property>
<!-- 需要指定SqlSessionFactory,使用sqlSessionFactoryBeanName
通过这种方式就可以等到Spring初始化完成后,再去构造SqlSessionFactory,否则无法连接数据库 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- Spring声明式事务管理:XML方式 -->
<!-- 1.配置事务管理器(XML及注解方式均需配置) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.配置事务增强:需要编写切入点及细节 -->
<!-- propagation:事务的传播行为
REQUIRED:必须的,如果有事务则使用,没有则创建
SUPPORTS:可选的,如果有事务则使用,没有不使用
isolation:事务的隔离级别
DEFAULT:mysql默认隔离级别(可重复读)
读未提交
读已提交
可串行化:可避免幻读
rollback-for:回滚(对哪写异常回滚)
no-rollback-for:不回滚(对哪些异常不回滚)
注意: 如果不设置默认的话,是对运行时异常进行回滚
read-only:如果将select设置为true,则告诉数据库引擎查询是只读的。
timeout=-1:过期时间(设置多长时间不回滚)
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 设置进行事务操作方法匹配的规则(规范程序员的命名规则) -->
<tx:method name="insert*" propagation="REQUIRED" isolation="DEFAULT"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 3.配置切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution(* com.hpe.service.*.*(..))" id="pointcut1"/>
<!-- 配置切面(应用增强)
advice-ref:使用哪个增强
pointcut-ref:将增强应用到哪个切入点上面(也可以使用pointcut指定相应的表达式即可)
-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
</beans>
Test.java
public class SpringTest {
// 使用事务模拟转账
@Test
public void test1(){
// 1.加载Spring的配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2.创建UserService对象
AccountService accountService = context.getBean(AccountService.class);
// 3.调用mapper中的方法
accountService.updateAccount("张三", "李四", 100);
}
}
基于注解的事务管理
步骤
- 配置事务管理器DataSourceTransactionManager,配置数据源
- 配置事务相关的注解
- 在使用事务的方法或者类上面添加注解
代码实战
AccountMapper.java、AccountMapper.xml、AccountService.java相比于上面并未改动(此处不再贴重复代码)
AccountServiceImpl2.java
package com.hpe.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.hpe.mapper.AccountMapper;
@Service("accountService2")
// @Transactional:表示事务的设置对这个类的所有方法都起作用
//@Transactional
public class AccountServiceImpl2 implements AccountService {
@Autowired
private AccountMapper accountMapper;
// @Transactional:表示事务的设置只对这个方法起作用
@Transactional
@Override
public void updateAccount(String out, String in, int money) {
// 转出
accountMapper.outMoney(out, money);
// 模拟异常(算数异常)
//int i = 10/0;
// 转入
accountMapper.inMoney(in, money);
}
}
applicationContext2.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 http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.hpe"></context:component-scan>
<!-- 加载外部资源文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置c3p0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 通过Spring管理SqlSessionFactory mapper接口 -->
<!-- 配置SqlSessionFactory为了使用 Spring来管理SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置mybatis核心配置文件的路径 -->
<property name="configLocation" value="classpath:SqlMapConfig.xml"></property>
<!-- 指定数据源 -->
<property name="dataSource" ref="dataSource"></property>
<!-- 指定批量创建别名的包 -->
<property name="typeAliasesPackage" value="com.hpe.po"></property>
</bean>
<!-- 批量创建Mapper接口实现类的Bean,可以不指定id
默认Bean是由id的,是Mapper接口的名称且首字母小写-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定需要创建实现类的Mapper接口所在的包
可以指定多个包,使用逗号隔开即可-->
<property name="basePackage" value="com.hpe.mapper"></property>
<!-- 需要指定SqlSessionFactory,使用sqlSessionFactoryBeanName
通过这种方式就可以等到Spring初始化完成后,再去构造SqlSessionFactory,否则无法连接数据库 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- Spring声明式事务管理:注解方式 -->
<!-- 1.配置事务管理器(XML及注解方式均需配置) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.注册事务注解驱动
transaction-manager:自动检测事务处理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
测试方法
@Test
public void test2(){
// 1.加载Spring的配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
// 2.创建UserService对象
AccountService accountService = context.getBean("accountService2",AccountService.class);
// 3.调用mapper中的方法
accountService.updateAccount("张三", "李四", 100);
}