Java Web 开发:Spring 事务

2069 字
10 分钟
Java Web 开发:Spring 事务

[TOC]

前言#

学习目标#

  1. 理解Spring事务核心思想
  2. 熟悉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>

组件注册#

image-20230414113747954
image-20230414113747954

@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#

@Service
public 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事务管理器。

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}

TransactionTemplate(了解)#

事务的模板,采用事务模板提供的方法来使用事务

image-20230414151730753
image-20230414151730753

在容器中注册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提供的方法

@Service
public 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
//@Aspect
public 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
@Documented
public @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")
@EnableTransactionManagement
public class MyBatisConfiguration {}

直接使用@Transactional注解,可以使用注解提供的属性配置事务的TransactionDefinition

/**
* 在类上增加@Transactional 意味着该类下的所有方法都增加事务
* @Transactional要增加在容器中的组件或组件中的方法里
*/
@Transactional
@Service
public 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);//操作了数据库中的数据,需要增加事务
}
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Java Web 开发:Spring 事务
https://firefly-mu-weld.vercel.app/posts/14-spring事务/
作者
Daisy
发布于
2026-06-10
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Daisy
Hello, I'm Daisy.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签

文章目录