目录
1、什么是事务
要么一起成功,要么一起失败。
1.1事务的四大特征(ACID)
原子性、一致性、隔离性、持久性
1.2事务并发出现的问题:
脏读:A事务读取了 B事务未提交的数据,然后B回滚了
不可重复读:事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据操作并提交,导致事务A读取到的数据不一致
幻读:一个事务读取到了别的事务插入的数据 导致前后不一致 一般就是多了一行(系统管理员A将学生成绩从具体分数划分为ABCDE等级,但是这个时候系统管理B又插入了一条具体分数的记录,当A操作结束后发现还有一条记录没有改过来,就好像发生了幻觉 )
2、Spring事务
2.1事务三大接口
PlatformtransactionManager:事务管理器
TransactionDefinition:事务的基础信息,如设置超时时间、隔离基本、传播属性
TransactionStatus:事务的状态信息,如是否是个新事务。是否被标记为回滚。
3、编程式事务管理和声明式事务管理
3.1编程式事务管理
创建一个maven项目首先在pom.xml中导入包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springtran</groupId>
<artifactId>spring_tran</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!--xml配置 声明式 事务使用 transfer3()-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.4</version>
</dependency>
</dependencies>
</project>
resources下面创建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"
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">
<!--
注解 扫描包
-->
<context:component-scan base-package="com.javastatus.demo"/>
<!--
1、配置数据源
-->
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="username" value="tk"/>
<property name="password" value="tk"/>
<property name="url" value="jdbc:mysql://172.16.196.56:3306/test?serverTimezone=Asia/Shanghai"/>
</bean>
<!--
2、提供事务管理器(编程式事务对应 方式一)
-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
3、配置TransactionTemplate(编程式事务对应 方式二)
-->
<bean class="org.springframework.transaction.support.TransactionTemplate" id="transactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
<!--
4、配置JdbcTemplate
-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
创建UserService测试
@Component
public class UserService {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
PlatformTransactionManager transactionManager;
@Autowired
TransactionTemplate transactionTemplate;
public void transfer2() {//编程式事务 使用TransactionTemplate 方式二
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
jdbcTemplate.update("update user set money=? where username =? ", 9, "张三");
} catch (DataAccessException e) {
e.printStackTrace();
status.setRollbackOnly();
}
}
});
}
public void transfer() {//编程式事务 使用TransactionManager 方式一
//定义默认的事务
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
//获取TransactionStatus
TransactionStatus transaction = transactionManager.getTransaction(definition);
try {
jdbcTemplate.update("update user set money=? where username =? ", 9, "张三");
transactionManager.commit(transaction);
} catch (DataAccessException e) {
e.printStackTrace();
transactionManager.rollback(transaction);
}
}
}
创建测试类UserDemo
public class UserDemo {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx =new ClassPathXmlApplicationContext("applicationContext.xml");
UserService bean = ctx.getBean(UserService.class);
bean.transfer3();
}
}
完成测试
3.2声明式事务管理
实现方式可以xml配置、java配置、xml+java混合配置。
xml配置
事务管理器上面配置过了,直接第二步,第三步。在applictionContext.xml追加
<!--
xml配置三个步骤
1.配置事务管理器
2.配置事务通知
3.配置AOP
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*"/>
<tx:method name="insert*"/>
<tx:method name="update*"/>
<tx:method name="transf*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pc1" expression="execution(* com.javastatus.demo.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc1"/>
</aop:config>
测试方法
public void transfer3() {
jdbcTemplate.update("update user set age=? where username =? ", 91, "3");
}
java配置
创建一个配置类JavaConfig
@Configuration
@ComponentScan(basePackages = "com.javastatus.demo")
@EnableTransactionManagement//开始事务的注解支持
public class JavaConfig {
@Bean
DataSource dataSource(){
DriverManagerDataSource ds =new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUsername("adm");
ds.setPassword("adm");
ds.setUrl("jdbc:mysql://172.16.196.56:3306/test?serverTimezone=Asia/Shanghai");
return ds;
}
@Bean
JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(dataSource());
}
@Bean
PlatformTransactionManager transactionManager(){
return new DataSourceTransactionManager(dataSource());
}
}
创建一个UserService2
@Component
public class UserService2 {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
PlatformTransactionManager transactionManager;
@Transactional
public void transfer4() {
jdbcTemplate.update("update user set age=? where username =? ", 99, "3");
}
}
创建测试类UserDemo2
public class UserDemo2 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
UserService2 bean = context.getBean(UserService2.class);
bean.transfer4();
}
}
测试的时候需要将UserService中的@componet注释掉
//@Component
public class UserService {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
PlatformTransactionManager transactionManager;
@Autowired
TransactionTemplate transactionTemplate;
public void transfer3() {
jdbcTemplate.update("update user set age=? where username =? ", 91, "3");
}
public void transfer2() {//编程式事务 方式二
}
。。。。。
}
xml+java混合配置
在applictionContext.xml追加,然后在方法上添加@transactional
<!--
javv+xml配置两个步骤
1.配置事务管理器
配置事务通知(不需要)
配置AOP(不需要)
2.<tx:annotation-driven/> 开启事务注解支持 在方法上添加@Transactional 就直接添加事务
这一个标签 替代了配置事务通知、配置AOP这两步
<tx:annotation-driven/> 等于 <tx:advice></tx:advice>+<aop:config></aop:config>
-->
<tx:annotation-driven/>
4.事务的传播性
约束条件 | 说明 |
REQUIRED | 如果当前没有事务,则新建事务,如果当前存在事务,则加入当前事务,合并成一个事务 |
REQUIRES_NEW | 新建事务,如果当前存在事务,则把当前事务挂起,新建事务执行完后再恢复当前事务 |
NESTED | 如果当前没有事务,则新建事务,如果当前存在事务,则创建一个当前事务的子事务(嵌套事务),子事务不能单独提交,只能和父事务一起提交 |
SUPPORTS | 支持当前事务,如果当前没有事务,以非事务的方式执行 |
NOT_SUPPORTED | 以非事务方式执行,如果存在当前事务就把当前事务挂起 |
NEVER | 以非事务方式执行,如果当前存在事务就抛异常 |
MANDATORY | 使用当前事务,如果当前没有事务,就抛异常 |
5.事务失效的场景
1、事务方法访问修饰符非public,导致事务失效
有final或者static关键字,导致事务失效
2、方法内部调用
- test1 有事务,test2 无事务,事务生效
- test1 有事务,test2 有事务,事务生效
- test1 无事务,test2 有事务,事务不生效
- test1 无事务,test2 无事务,事务不生效
3、异常类型错误
public class ServiceA {
@Transactional
public void add(UserModel userModel) throws Exception {
try {
addUser();
addTeacher();
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception(e);
}
}
}
可以看到发生异常时,被 try-catch 补获并抛出 exception 异常,但是因为 spring 事务,默认情况下只会回滚 RuntimeException(运行时异常)和 Error(错误),对于普通的 Exception(非运行时异常),它不会回滚。所以一般都要求 Transactional 指定 rollbackfor。
4、抛出的异常被吃了
@Service
public class ServiceA {
@Transactional(rollbackFor=Exception.class)
public void add(UserModel userModel) throws Exception {
try {
addUser();
addTeacher();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
上面异常被 try-catch 吃掉了,且没有往外抛异常,所以这个时候事务是无法回滚。
5、数据表本身是不支持事务,导致事务失效
1、实例
如果使用MySQL且存储引擎是MyISAM,则事务是不起作用的,原因是MyIASM不支持事务。
2、解决
数据表可以改为InnoDB存储引擎,支持事务
6.多线程调用
这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。
7.错误的传播特性
@Transactional 的 propagation 参数设置不对,spring 目前支持 7 种传播特性。
目前只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED。
8.未被spring容器管理
视频推荐:江南一点雨