Java Web 开发:Spring 事务
[TOC]
前言
学习目标
- 理解Spring事务核心思想
- 熟悉Spring事务的注解@Transactional
前置知识准备
- 事务
- AOP思想
事务的回顾
事务的特性
- 原子性 Atomicity
- 一致性 Consistency
- 隔离性 Isolation
- 持久性 Durability
事务并发引起的问题
- 脏读
- 一个事务读取到另外一个事务还未提交的数据
- 不可重复读
- 一个事务读取到另外一个事务已经提交的数据(修改)
- 虚读/幻读
- 一个事务读取到另外一个事务已经提交的数据(增删)
数据库的隔离级别
- 读未提交(Read uncommitted)
- 读已提交(Read committed)
- 可重复读(Repeatable read)
- 串行化(Serializable)
| 脏读 | 不可重复读 | 虚读/幻读 | |
|---|---|---|---|
| 读未提交 | × | × | × |
| 读已提交 | √ | × | × |
| 可重复读 | √ | √ | × |
| 串行化 | √ | √ | √ |
MySql的默认隔离级别是可重复读,但没有虚读问题的。
MyBatis整合
要使用Spring框架整合MyBatis
引入依赖
<!--Spring整合MyBatis依赖--><dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.15.RELEASE</version></dependency><!--druid--><dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.11</version></dependency>组件注册

@Configuration@ComponentScan("com.cskaoyan.demo2")public class AppConfiguration {
/*@Bean public SqlSessionFactory sqlSessionFactory() { try { SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml")); return sessionFactory; } catch (IOException e) { e.printStackTrace(); } return null; }*/
/** * MyBatis最早没有考虑整合Spring,后面发现有个大腿 * mybatis-spring这个依赖提供了一个类,能够注册SqlSessionFactory组件 * SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory> * */ @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){ SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&characterEncoding=utf-8"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; }
@Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer configurer = new MapperScannerConfigurer(); // 使用set方法提供一些信息 → 它就会根据这些信息向容器中注册mapper组件 configurer.setSqlSessionFactoryBeanName("sqlSessionFactory");// sqlSessionFactory=applicationContext.getBean(beanName) configurer.setBasePackage("com.cskaoyan.demo2.mapper"); return configurer; }}Service中可以直接注入Mapper
@Servicepublic class AccountServiceImpl implements AccountService{
/*@Autowired SqlSessionFactory sqlSessionFactory;*/
@Autowired AccountMapper accountMapper;
@Override public int transfer(Integer fromId, Integer destId, Integer money) { /*SqlSession sqlSession = sqlSessionFactory.openSession(); AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class);*/ Integer fromMoney = accountMapper.selectByPrimaryKey(fromId) - money; Integer destMoney = accountMapper.selectByPrimaryKey(destId) + money;
accountMapper.update(fromId, fromMoney); int i = 1 / 0; accountMapper.update(destId, destMoney); /*sqlSession.commit();*/ return 0; }}Spring在MyBatis执行mapper的方法之后会立即提交事务,而我们有时候需要将多个方法放到一个事务里
事务的核心接口
- PlatformTransactionManager 平台事务管理器
- TransactionStatus 事务状态
- TransactionDefinition 事务定义
PlatformTransactionManager平台事务管理器
平台事务管理器,Spring要管理事务,必须使用事务管理器 有多种实现,通过实现此接口,Spring可以管理任何实现了这些接口的事务。 开发人员可以使用统一的编程模型来控制管理事务。
常见的事务管理器的实现 DataSourceTransactionManager,jdbc开发时事务管理器,使用JdbcTemplate、MyBatis(SSM) HibernateTransactionManager,Hibernate开发时事务管理器,整合Hibernate(SSH)
public interface PlatformTransactionManager extends TransactionManager { // 开启事务 TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException; // 提交事务 void commit(TransactionStatus var1) throws TransactionException; // 回滚事务 void rollback(TransactionStatus var1) throws TransactionException;}TransactionStatus事务状态
获取事务的状态(回滚点、是否完成、是否新事务、是否回滚)属性,是一个过程值
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable { boolean hasSavepoint();
void flush();}提供了关于事务状态的方法
图片缺失:image/Snipaste_2021-07-22_16-14-53.png
TransactionDefinition事务定义
定义事务的名称、隔离级别、传播行为、超时时间长短、只读属性等
public interface TransactionDefinition { int PROPAGATION_REQUIRED = 0; int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; int ISOLATION_DEFAULT = -1; int ISOLATION_READ_UNCOMMITTED = 1; int ISOLATION_READ_COMMITTED = 2; int ISOLATION_REPEATABLE_READ = 4; int ISOLATION_SERIALIZABLE = 8; int TIMEOUT_DEFAULT = -1;
default int getPropagationBehavior() { return 0; }
default int getIsolationLevel() { return -1; }
default int getTimeout() { return -1; }
default boolean isReadOnly() { return false; }
@Nullable default String getName() { return null; }
static TransactionDefinition withDefaults() { return StaticTransactionDefinition.INSTANCE; }}事务的传播行为
事务的传播行为属于TransactionDefinition,这个概念是之前没有讲过的一个概念
指多个方法之间如何来共享事务:发生异常的情况,谁提交谁回滚
- REQUIRED 默认的事务传播行为
- 一荣俱荣,同生共死
- REQUIRES_NEW
- 自私型
- NESTED
- 无私型
REQUIRED
如果当前没有事务,就新建一个事务; 如果当前存在,则以当前事务方式运行。
一荣俱荣,一损俱损。要么一起提交,要么一起回滚。
现在有两个method,methodB(外围)调用了methodA(内部)Q1:如果methodB发生异常,谁回滚?Q2:如果methodA发生异常,谁回滚?Q1/Q2:AB都回滚
REQUIRES_NEW
如果当前没有事务,新建事务; 如果当前存在事务,则新建一个事务。
自私型。外部不会影响内部,内部会影响外围。
现在有两个method,methodB(外围)调用了methodA(内部)Q1:如果methodB发生异常,谁回滚?Q2:如果methodA发生异常,谁回滚?Q1:A不回滚,B回滚
Q2:AB都回滚
内部的方法更重要
NESTED
如果当前没有事务,就新建一个事务; 如果当前存在事务,则以嵌套事务的方式运行。
无私型。外围会影响内部,内部不会影响外围。
现在有两个method,methodB(外围)调用了methodA(内部)Q1:如果methodB发生异常,谁回滚?Q2:如果methodA发生异常,谁回滚?Q1:AB都回滚
Q2:A回滚,B不回滚
外部的方法更重要
UserService → register
CouponService → send
register调用send
其他传播行为
| 传播行为 | 描述 |
|---|---|
| PROPAGATION_SUPPORTS | 支持当前事务,假设当前没有事务,就以非事务方式运行 |
| PROPAGATION_MANDATORY | 支持当前事务,假设当前没有事务,就抛出异常 |
| PROPAGATION_NOT_SUPPORTED | 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起 |
| PROPAGATION_NEVER | 以非事务方式运行,假设当前存在事务,则抛出异常 |
事务的案例
不管使用的是哪一个案例,只要使用Spring事务,必然要使用Spring事务管理器。
@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager;}TransactionTemplate(了解)
事务的模板,采用事务模板提供的方法来使用事务

在容器中注册TransactionTemplate组件
@Configuration@ComponentScan("com.cskaoyan")public class MyBatisConfiguration {
// javaConfig讲过这个组件注册 // MyBatis注册的组件略
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } @Bean public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) { TransactionTemplate transactionTemplate = new TransactionTemplate(); transactionTemplate.setTransactionManager(transactionManager); return transactionTemplate; }}使用TransactionTemplate提供的方法
@Servicepublic class AccountServiceImpl implements AccountService{ @Autowired AccountDao accountDao; @Autowired TransactionTemplate transactionTemplate; @Override public void transfer(String from, String dest, Integer money) { Integer fromMoney = accountDao.selectMoneyByName(from) - money; Integer destMoney = accountDao.selectMoneyByName(dest) + money;
//execute方法的返回值 是doInTransaction方法的返回值 Integer execute = transactionTemplate.execute(new TransactionCallback<Integer>() { //需要增加事务的代码放入到doInTransaction中 //如果需要增加事务的方法需要返回值 → 直接返回对应的值即可 @Override public Integer doInTransaction(TransactionStatus transactionStatus) {
accountDao.updateMoneyByName(from, fromMoney);//操作了数据库中的数据,需要增加事务 int i = 1/0; accountDao.updateMoneyByName(dest, destMoney);//操作了数据库中的数据,需要增加事务 return null; } }); /*transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { accountDao.updateMoneyByName(from, fromMoney);//操作了数据库中的数据,需要增加事务 accountDao.updateMoneyByName(dest, destMoney);//操作了数据库中的数据,需要增加事务 } });*/ }}Transactional注解
思路
//@Component//@Aspectpublic class TransactionAspect {
@Autowired PlatformTransactionManager transactionManager; @Pointcut("") public void transactionPointcut(){}
@Around("transactionPointcut()") public Object transactionAround(ProceedingJoinPoint joinPoint) { Object proceed = null; TransactionStatus status = null; try { status = transactionManager.getTransaction(TransactionDefinition.withDefaults()); proceed = joinPoint.proceed(); transactionManager.commit(status); } catch (Throwable throwable) { transactionManager.rollback(status); throwable.printStackTrace(); } return proceed; }}@Transactional注解的ElementType为TYPE和METHOD,意味着可以写在类上或方法上
@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface Transactional { @AliasFor("transactionManager") String value() default "";
@AliasFor("value") String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};}使用注解前,需要先打开事务的注解驱动
@Configuration@ComponentScan("com.cskaoyan")@EnableTransactionManagementpublic class MyBatisConfiguration {}直接使用@Transactional注解,可以使用注解提供的属性配置事务的TransactionDefinition
/** * 在类上增加@Transactional 意味着该类下的所有方法都增加事务 * @Transactional要增加在容器中的组件或组件中的方法里 */@Transactional@Servicepublic class AccountServiceImpl implements AccountService{ @Autowired AccountDao accountDao;
//对应的方法增加上事务 @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,timeout = 5) @Override public void transfer(String from, String dest, Integer money) { Integer fromMoney = accountDao.selectMoneyByName(from) - money; Integer destMoney = accountDao.selectMoneyByName(dest) + money;
accountDao.updateMoneyByName(from, fromMoney);//操作了数据库中的数据,需要增加事务 int i = 1/0; accountDao.updateMoneyByName(dest, destMoney);//操作了数据库中的数据,需要增加事务
}}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!