目录
Spring 事务管理概述
事务是数据库操作的最基本单元,是逻辑上的一组操作。
典型场景:银行转账。
具有四个特性:
1)原子性
2)一致性
3)隔离性
4)持久性
事务一般添加到 service 层。
在 Spring 中有两种事务管理操作:
1. 编程式(一般不使用)
2. 声明式
2.1 基于 XML配置文件 方式实现(一般不使用)
2.2 基于注解方式实现
声明式事务管理底层原理是 AOP。
Spring 事务管理的 API
1. Spring 提供事务管理器接口,这个接口根据不同的框架提供不同的实现类。
编程式事务管理
//编程式事务管理操作
//Dao
@Override
public void reduceMoney(String id, int money) {
String sql = "update t_account set money=money-? where id=?";
Object[] args = {money, id};
jdbcTemplate.update(sql, args);
}
@Override
public void addMoney(String id, int money) {
String sql = "update t_account set money=money+? where id=?";
Object[] args = {money, id};
jdbcTemplate.update(sql, args);
}
//Service
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void dispote (String outId, String inId, int money) {
//事务管理操作步骤
try {
//开启事务
//业务操作
userDao.reduceMoney("1", money);
//模拟异常
//int i = 10/0;
userDao.addMoney("2", money);
//没有异常,提交事务
} catch (Exception e) {
//出现异常,回滚事务
}
}
}
声明式事务管理
基于注解方式
步骤:
1. 在 Spring 配置文件中创建事务管理器。
2. 在 Spring 配置文件中开启事务注解。
2.1 引入名称空间 tx。
2.2 开启事务注解。
3. 在 Service 类上面或在 Service 类中的特定方法上面添加事务注解 @Transactional。
# 在类上面添加,则类中所有方法都添加事务。
<!-- 配置事务管理器(框架不同,实现类不同) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
//声明式事务管理,基于注解实现
@Service
@Transactional
public class UserService {
@Autowired
private UserDao userDao;
public void dispote (String outId, String inId, int money) {
//不需要再用 try catch 处理。如果用了,则事务不会生效
userDao.reduceMoney("1", money);
//模拟异常
int i = 10/0;
userDao.addMoney("2", money);
}
}
声明式事务管理的参数配置
@Transactional()
1. propagation:事务传播行为,MySQL 默认是 REQUIRED
2. isolation:事务隔离级别
3. timeout:超时
4. readOnly:是否只读
5. rollbackFor:回滚
6. noRollbackFor:不回滚
事务传播行为
指多事务方法之间直接进行调用,这个过程事务是如何进行管理的。
#事务方法:修改了数据库表数据的方法,如增删改。
Spring 框架有 7 种事务传播行为:
* 1)REQUIRED:如果有事务在运行,当前方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行。
* 2)REQUIRED_NEW:当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起。
3)SUPPORTS:如果有事务在运行,当前方法就在这个事务内运行,否则,它可以不在事务内运行。
4)NOT_SUPPORTED:当前的方法不应该运行在事务中,如果有运行的事务,将它挂起。
5)MANDATROY:当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常。
6)NEVER:当前的事务不应该在事务中运行,如果有运行的事务,就抛出异常。
7)NESTED:如果有事务在运行,当前的方法就应该在事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行。
事务隔离级别
事务隔离性:多事务操作之间不会产生影响。
如果不考虑隔离性,会产生很多问题:
#读问题:脏读、不可重复读、虚(幻)读
1. 脏读:一个未提交事务读取到另一个未提交事务的数据。
2. 不可重复读:一个未提交事务读取到另一个提交事务修改后的数据。
3. 虚读:一个未提交事务读取到另一个提交事务添加的数据。
事务的四种隔离级别:MySQL 默认是 REPEATABLE READ
1)READ UNCOMMITTED:读未提交。
#有脏读、有不可重复读、有幻读
2)READ COMMITTED:读已提交。
#无脏读、有不可重复读、有幻读
3)REPEATABLE READ:可重复读。
#无脏读、无不可重复读、有幻读
4)SERIALIZABLE:串行化。
#无脏读、无不可重复读、无幻读
其他参数
timeout:事务需要在规定的时间内提交,否则回滚。默认值是 -1,不超时。单位为秒。
readOnly:是否只读,默认值是 false
rollbackFor:设置出现哪些异常就进行事务回滚。
noRollbackFor:设置出现哪些异常就不进行事务回滚。
基于 XML 方式
步骤:
1. 配置事务
2. 配置通知
3. 配置切入点和切面
<!-- 配置事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置通知 -->
<tx:advice id="txAdvice">
<!-- 配置事务参数 -->
<tx:attributes>
<!-- 指定规则,并在符合规则的方法上面添加事务 -->
<tx:method name="dispote" propagation="REQUIRED"/>
<!-- <tx:method name="dis*" propagation="REQUIRED"/> -->
</tx:attributes>
</tx:advice>
<!-- 配置切入点和切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pt" expression="execution(* work.service.UserService.*(..))"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
</aop:config>
完全注解方式
//TxConfig
@Configuration //定义配置类
@ComponentScan(basePackages = {"work"}) //开启组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
//创建 JdbcTemplate 对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//到 IOC 容器中根据类型找到 dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入 dataSource 到 jdbcTemplate
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager
= new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
//test
@Test
public void testDispote() {
ApplicationContext context
= new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.dispote("1", "2", 100);
}
<!-- 被配置类取代的配置文件 -->
<context:component-scan base-package="jdbctemplate, work"></context:component-scan>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/user_db"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
</bean>
<!-- 配置 JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入 dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>