1.事务是什么
从场景的角度来说
- 我现在要修改五张表的数据,前四张我都是成功的,最后第五笔失败了,那么理论上来说,我前四笔肯定都是失效的,不然我数据就会有问题。这边就会有事务进行控制
- 第二种情况就是并发,我在操作一张表,你也在操作一张表,理论上来说我们是相互不影响的。这也是事务的隔离性
1.1 事务特征ACID 老生常谈了,老面试题了,这边贴一下
- A 原子性 事务是一个整体,不可切割,一个事务内要么都执行要么都不执行
- C 一致性 事务前后数据的完整性必须保持一致
- I 隔离性,就是不同事务之间是不会相互影响的
- D 持久性,如果一个事务修改了数据库,除非数据库物理损坏数据丢失,不然肯定不会出现问题
1.2 事务的隔离级别
1.2.1 脏读 (表级读锁)
脏读就是我在update 一条数据,改了但是我还没有提交事务,另外一个人在我没提交事务之前读取了这条数据,那么就会出现脏读现象。解决这个问题就是加读锁,就是我在改数据的时候你读不到这个表的数据,只有我commit的时候,你才能读取到数据。
1.2.2 不可重复读 (行级写锁)
这边还是先讲场景,我读取了数据之后你立马改了这条数据,我再次读取的时候发现数据不一致了。解决这个问题就是给这条数据加个锁,我读取的时候加个锁,你是不能操作这条数据的,当然我这边举的例子的时候都是在一个事务里面,这个场景一定要牢记,比如这边我读两次肯定在一个事务内部读了两次。数据不一致肯定是不行的。
1.2.3 幻读/虚读 (表级写锁)
我查一个列表,第一次查询出五条,我点了一下,发现少了四条,在查询又是五条这种情况出现的就是我在查询的时候,你同时也在增删,这样对于我这边的数据就多多少少。解决这个问题就是我读的时候加写锁,我读取的时候不允许你进行写的操作
1.2.4 总结
根据这些东西你仔细观察之后发现,读锁是限制读,写锁是限制写。这个牢记
2. Spring事务管理器
2.1 编程式事务
package com.zx.servcie.impl;
import com.zx.dao.FfxhDao;
import com.zx.domain.FfxhInfo;
import com.zx.servcie.FxxhService;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.sql.DataSource;
import java.lang.management.PlatformManagedObject;
import java.util.List;
public class ffxhServiceImpl implements FxxhService {
public FfxhDao ffxhDao;
public DataSource dataSource;
public FfxhDao getFfxhDao() {
return ffxhDao;
}
public void setFfxhDao(FfxhDao ffxhDao) {
this.ffxhDao = ffxhDao;
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void tansfer() {
//事务管理器,我们拿mybatise适配的事务管理器
//注意这边是有入参的,必须传入一个dataSource
PlatformTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
//事务定义
TransactionDefinition td = new DefaultTransactionDefinition();
//事务状态
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(td);
ffxhDao.inMoney(100,"18078904769");
int i= 1/0;
ffxhDao.outMoney(100,"18161092861");
//注意这边加上了这个事务管理器的话,这两条数据就会同时i失败而不是一条成功一条失败
dataSourceTransactionManager.commit(transactionStatus);
System.out.println("转账成功!");
}
}
2.2 AOP管理事务
上面的方法有了之后,同时会出现一个新的问题就是,每次写个方法都要这么写,不是写吐了?
所以我们自然而然的想到了AOP通知,我用AOP把方法包一下不就都行了吗?下面开始实现。
2.2.1 配置文件配置
<bean id="Tm" class="com.zx.aop.TranstrationManager">
<property name="dataSource" ref="dataSources"></property>
</bean>
<aop:config>
<!--切点-->
<aop:pointcut id="myPt" expression="execution(* *..*(..))"/>
<aop:aspect ref="Tm">
<aop:around method="txAround" pointcut-ref="myPt"></aop:around>
</aop:aspect>
</aop:config>
2.2.2 切面类
package com.zx.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.sql.DataSource;
public class TranstrationManager {
public DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Object txAround(ProceedingJoinPoint point) throws Throwable {
//事务管理器,我们拿mybatise适配的事务管理器
//注意这边是有入参的,必须传入一个dataSource
PlatformTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
//事务定义
TransactionDefinition td = new DefaultTransactionDefinition();
//事务状态
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(td);
Object proceed = point.proceed();
//注意这边加上了这个事务管理器的话,这两条数据就会同时i失败而不是一条成功一条失败
dataSourceTransactionManager.commit(transactionStatus);
return proceed;
}
}
2.3 声明式事务
这边其实是Spring对事务进行单独的封装。直接看代码。
这边是配置头
2.3.1 tx-method 详解
2.4 事务传播机制
下图就是事务的传播行为,这边先解释一下,事务管理员则是一个方法,事务协调员就是该方法内部的方法。这边讲一个后面推导,比如这个required ,如果主方法有事务,那么子方法直接加入主方法的事务,如果主方法没有事务那么子方法自己会建一个事务,这边需要提醒的是主方法和子方法都可以自己定义自己的隔离级别。
下面举个never的场景,可以看下
事务的作用我这边也描述一下,有些时候我们一个事务操作了很多表,其中一个表报错了,我希望是有关的数据进行回滚,而不是所有表的事务都回滚,那么就要在不同事务中解决这个事情,那么现在我们讨论的隔离级别就有相应的作用
2.5 注解实现事务
这边非常简单就不过多阐述了,注意配置文件里面得配上识别事务的驱动
我这边没有编写纯注解的方式,但是也不难,因为也没有手动敲过,这边就不贴图了。