事务四大特性?
1,原子性:是不可分割的最小操作单位,要么同时成功,要么同时失败。
执行多条增删改查sql语句时,要么同时成功,要么同时失败。
2,持久性:当事务提交或回滚后,数据库会持久化的保存数据。
3,隔离性:多个事务之间。相互独立。
一个事务能否看到另外一个事务未提交的数据。通过隔离级别判断。
4,一致性:就是事务前后,数据在逻辑上都是合理的。
转账,一个减了100另外一个一定就加了100
CAP & BASE?
CAP
C:强一致性;A:高可用性;P:分区容错性
CA传统oracle数据库
AP大多数网站架构的选择
CP Redis、MongoDB
BASE
让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观
基本可用 basically available
软状态 soft state
最终一致 eventually consistent
事务隔离级别?
read uncommitted:读未提交
产生的问题:脏读、不可重复读、幻读
事务a,转账了,但是没有提交;事务b查询到的是转账后的数据
read committed:读已提交 (Oracle)
产生的问题:不可重复读、幻读
事务a,转账了,但是没有提交;事务b查询到的是转账前的数据;事务a提交后,事务b查到的转账后数据
事务a提交了事务,事务b读取到事务a提交前后的不同数据
repeatable read:可重复读 (MySQL默认)
可重复读:事务a读取某条数据同时,事务b修改了这条数据,事务a再修改前后读取到结果一致。
给某条数据加一个行锁可以解决"读已提交",也就是别的事务不能修改这条数据了,但是还是存在幻读问题。
产生的问题:幻读
幻读:事务a读取范围数据比如age>18读取到一条数据,事务b新增数据,事务a再次读取范围数据,读取到两条数据
serializable:串行化
可以解决所有的问题,事务a操作表b时,表b被锁定,别的事务就不能操作表b了,但是效率最低
事务a读取过程中,事务b不能读写;所有的事务排队执行
mybatis事务提交、回滚
@Service
public class TransactionTest{
@Autowired
private SqlSessionFactory sqlSessionFactory;
public void performTransaction() {
//1,打开session,默认关闭自动提交,所以false也可以不写
SqlSession sqlSession = sqlSessionFactory.openSession(false);
try {
//2,执行数据库操作
//3,提交事务
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
//4,出现异常,回滚事务
sqlSession.rollback();
} finally {
//5,关闭SqlSession
sqlSession.close();
}
}
}
Spring事务
Spring事务的传播机制?
Spring的事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法对事务的态度。
举例:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
spring事务传播特性?
事务传播特性 | 解释1 | 解释2 |
---|---|---|
required必需的 | 默认事务传播特性,如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务 | A调用B,B发现A有事务,就加入A的事务,内部事务会融入外部事务,无论内外出现异常都会回滚;B发现A没有事务就新建事务 |
requires_new | 新建事务,如果当前在事务中,把当前事务挂起。 | 内部新建一个事务与外部事务无关,内部或外部事务回滚,不会影响另一个事务。适用于日志。内外互不影响 |
nested嵌套 | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,就创建一个新事务。 | 在外部事务基础上,内部嵌套一个事务,也就是使用savepoint,外部事务回滚,内部也就回滚了;内部事务回滚,会回滚到savepoint所以外部事务没有影响 |
supports支持 | 支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。 | 适用于查询 |
not_supported | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 | |
mandatory强制性的 | 支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常 | |
never | 以非事务方式执行,如果当前存在事务,则抛出异常 |
nested与requires_new的区别
requires_new是内部新建一个事务与外部事务无关,内部或外部事务回滚,不会影响另一个事务
nested在外部事务基础上,内部嵌套一个事务,也就是使用savepoint,外部事务回滚,内部也就回滚了;内部事务回滚,会回滚到savepoint所以外部事务没有影响。
nested与required的区别
required情况下,内部事务会融入外部事务,无论内外出现异常都会回滚。
nested在外部事务基础上,内部嵌套一个事务,也就是使用savepoint,外部事务回滚,内部也就回滚了;内部事务回滚,会回滚到savepoint所以外部事务没有影响。
required:
必需的:默认事务传播特性
如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。适用于增删改查
解释:A调用B,B发现A有事务,就加入A的事务;B发现A没有事务就新建事务
-- 要挂内外都挂
begin;
update table1 set score=100 where id=1;
融入外界事务
update table1 set score=100 where id=3;
update table1 set score=100 where id=2;
commit;
requires_new:
新建事务,如果当前在事务中,把当前事务挂起。适用于日志。
-- 内外互不影响
连接1,begin;
update table1 set score=100 where id=1;
连接1挂起
连接2,update table1 set score=100 where id=3;
连接2释放
连接1恢复
update table1 set score=100 where id=2;
commit;
nested嵌套:
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,就创建一个新事务。
```java
-- 内部受外部影响:外部出错,内部也挂
-- 外部独立:内部出错,外部回滚到保存点,外部不受影响
begin;
update table1 set score=100 where id=1;
savepoint a; -- 保存点
update table1 set score=100 where id=3;
rollback to a;
//以上事务如果存储问题回滚到保存点
update table1 set score=100 where id=2;
commit;
supports支持:
支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。适用于查询
not_supported:
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
mandatory强制性的:
支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
never:
以非事务方式执行,如果当前存在事务,则抛出异常。
事务隔离级别
spring事务隔离级别就是数据库的隔离级别,当数据库和spring代码中隔离级别不同时,以spring的配置为主。
事务隔离级别 | 中文名 | 产生的问题 | 解析 | 默认级别 |
---|---|---|---|---|
ISOLATION_READ_UNCOMMITTED | 读未提交 | 脏读、不可重复读、幻读 | 事务a,转账了,但是没有提交;事务b查询到的是转账后的数据 | |
ISOLATION_READ_COMMITTED | 读已提交 | 不可重复读、幻读 | 事务a,转账了,但是没有提交;事务b查询到的是转账前的数据;事务a提交后,事务b查到的转账后数据,事务b读取到事务a提交前后的不同数据 | Oracle默认 |
ISOLATION_REPEATABLE_READ | 可重复读 | 幻读 | 事务a读取范围数据比如age>18读取到一条数据,事务b新增数据,事务a再次读取范围数据,读取到两条数据 | MySQL默认 |
ISOLATION_SERIALIZABLE | 串行化 | 解决所有的问题 | 事务a读取过程中,事务b不能读写;所有的事务排队执行 |
Spring事务实现方式原理
在使用spring框架时,可以有两种事务的实现方式,一种是编程式事务,通过代码来控制事务的处理逻辑,一种是声明式事务,通过@Transactional注解来实现。
其实事务操作本来应该由数据库来控制,为了方便用户进行业务逻辑操作,spring对事务功能进行了扩展实现,一般我们很少用编程式事务,更多的是添加@Transactional注解来进行实现,当添加此注解之后事务的自动功能就会关闭,由spring框架来帮助进行控制。
事务操作是AOP的一个核心体现,当方法添加@Transactional注解之后,spring会基于这个类生成一个代理对象,会将这个代码对象作为bean,当使用这个代理对象的方法的时候,如果有事务处理,那么会先把事务自动提交给关闭,然后去执行具体的业务逻辑,如果执行逻辑没有出现异常,那么代理逻辑就会直接提交,如果出现任何异常情况,那么直接进行回滚操作,当然用户可以控制对哪些异常进行回滚操作。
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public void methodA() {
}
Spring事务什么情况下会失效
失效场景 | ||
---|---|---|
访问权限问题 | if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) | 只支持public修饰符 |
方法使用final修饰 | 方法被final修饰了,那么在它的代理类中,就无法重写该方法去添加事务功能 | 如果方法使用static修饰,也无法添加事务(静态方法由类调用,不由对象调用) |
方法使用static修饰 | 如果方法使用static修饰,也无法添加事务(静态方法由类调用,不由对象调用) | |
方法内部调用 | 方法拥有事务的能力是因为Spring AOP生成了代理对象,this对象调用的方法不能生成代理对象 | 解决方法:1新加一个Service类,实现该方法;2在该Service中注入自己;3通过AopContent类,AopContent.currentProxy()获取当前代理对象 |
未被Spring管理 | 类没有添加@Service等注解,那么该类就不会交给Spring进行统一管理,同时它的方法也不会生成事务 | |
多线程调用 | 同一个事务,其实指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程中,拿到的数据库连接肯定是不一样的。所以事务也是不同的 | |
表不支持事务 | MyIsam引擎不支持事务 | |
数据源没有配置事务管理器 | SpringBoot已经通过DataSoureTransactionManagerAutoConfiguration类,默认开启了事务,你只需要配置spring.datasource的相关参数即可 | |
RuntimeException异常在方法内部通过try…catch处理掉了 | ||
手动抛出了别的异常 | 默认情况下只会回滚RunTimeException,和Error(错误),对于普通的Exception(非运行时异常),它是不会回滚的 | |
自定义了回滚异常 | @Transactional(rollbackFor = BusinessException.class) | |
嵌套事务回滚多了 | 需要将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务 |
1,数据库引擎不支持事务:这里以MySQL为例,其MyISAM引擎是不支持事务操作的,InnoDB才是支持事务的引擎,一般要支持事务都会使用InnoDB。
2,bean没有被Spring管理。如果此时把 @Service注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被Spring 管理了,事务自然就失效了。
3,方法不是public的:@Transactional只能用于public的方法上,否则事务不会失效。
比如方法是private的,代理类也就是子类就不能拿到private类型的方法,final类型的方法,也不能被子类重写
方法使用final修饰,事务会失效
方法使用static修饰,事务会失效
4,使用this在方法内调用当前类其他方法时,事务失效。可以将当前类也注入进来,就可以使用代理对象的方法调用
@Service
public class MyService {
@Autowired
private MyRepository repository;
@Transactional
public void doTransactionalWork() {
// 这个方法会被事务管理
repository.save(someEntity);
// 内部调用,事务不生效,this调用内部方法相当于new一个对象调用,区别与aop动态代理方法生成对象,没有办法将事务增强进去
this.doNonTransactionalWork();
}
}
5,数据源没有配置事务管理器
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydatabase"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 启用注解驱动事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
6,RuntimeException异常在方法内部通过try…catch处理掉了
@Service
public class MyService {
@Autowired
private MyRepository repository;
@Transactional
public void doTransactionalWork() throws Exception {
// 这个方法会被事务管理
repository.save(someEntity);
// 抛出受检查异常,事务会回滚
throw new RuntimeException("Some checked exception");
}
@Transactional
public void doAnotherTransactionalWork() {
try {
// 这个方法事务不会生效
repository.save(anotherEntity);
} catch (RuntimeException e) {
// 在这里捕获了异常,事务不会回滚
// 可能导致事务失效
// 可以在这里记录日志或者进行其他处理
}
}
}
7,异常类型错误:事务默认回滚的是:RuntimeException
@Service
public class MyService {
@Autowired
private MyRepository repository;
//事务默认回滚的是:RuntimeException,也可以指定Exception类型
//事务会回滚
@Transactional(rollbackFor = Exception.class)
public void doTransactionalWork() throws Exception {
// 这个方法会被事务管理
repository.save(someEntity);
//事务会回滚
throw new Exception("Some checked exception");
}
//事务不会回滚
@Transactional(rollbackFor = RuntimeException.class)
public void doTransactionalWork() throws Exception {
// 这个方法会被事务管理
repository.save(someEntity);
//事务不会回滚
throw new Exception("Some checked exception");
}
}
8,多线程也会导致事务失效,当mybatis或jdbcTemplate执行sql时,会从threadLocal中获取数据库连接对象,如果开启事务的线程和执行sql的线程是一个,那么就能拿到数据库连接对象,如果不是同一个线程,那就拿不到数据库连接对象,这样,mybatis或jdbcTemplate就会自己去新建一个数据库连接来执行sql,此数据库连接的autocommit=true。执行完sql就会提交。后续再抛出异常就不能再回滚之前已经提交的sql了
9,嵌套事务回滚多了
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel user) throws Exception {
userMapper.insertUser(user);
roleService.doOtherThing();
}
}
@Service
public class RoleService {
//doOtherThing()方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。
@Transactional(propagation = Propagation.NESTED)
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(User user) throws Exception {
userMapper.insertUser(userModel);
//将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务
try {
roleService.doOtherThing();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
spring事务是如何回滚的?
spring的事务是由aop来实现的,首先要生成具体的代理对象,然后按照aop的整套流程来执行具体的操作逻辑,正常情况下要通过通知来完成核心功能,但是事务不是通过通知来实现的,而是通过TransactionInterceptor来实现的,然后调用invoke来实现具体的逻辑
1,启动事务获取数据库连接,关闭自动提交功能,开启事务
2,执行具体的sql逻辑操作
3,在操作过程中如果执行失败了,通过completeTransacationAfterThrowing来完成事务回滚操作,回滚具体逻辑通过doRollBack方法实现的,实现的时候也会先获取数据库连接,通过连接对象connection来回滚
4,在执行成功后,通过completeTransacationAfterReturning来完成事务的提交操作,提交的具体逻辑通过doCommit方法来实现,实现的时候也是先获取数据库连接,通过连接对象connection来提交
5,当事务执行完毕之后,需要清理相关事务信息cleanupTransacationInfo