事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);
通俗的讲,就是为了达到某个目的,而做的一系列的操作,要么一起成功(事务提交),要么一起失败(事务回滚)。
最常见的例子就是转账:
小明给如花转账:
开启事务-------
- 从小明的账户扣除1000块
- 给如花的账户增加1000块
事务提交-------
上面例子的任何步骤一旦出现问题,都会导致事务回滚。
从搭讪到结婚就是事务提交,女方要求男方重新追求她一次,就是事务回滚。
1.2 事务的四大特性一原持久隔离
1.原子性(Atomicity):事务中所有操作是不可分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
2.一致性(Consistency):事务执行后,数据库状态与其它业务规则保持一致。如转正业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的。
3.隔离性(Isolation):隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。
4.持久性(Durability):一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须你能保证通过某种机制恢复数据。
1.3 原生的JDBC事务处理try {
connection.setAutoCommit(false);
数据库操作... insert / update /delete
connection.commit();
} catch (Exception ex) {
connection.rollback();
} finally {
connection.setAutoCommit(true);
}
二、事务的隔离级别
数据库事务的隔离级别有4种,由低到高分别为Read uncommitted(读未提交)、Read committed(读已提交)、Repeatable read(可重复)、Serializable(可串行化)。而且,在事务的并发操作中可能会出现脏读,不可重复读、幻读、事务丢失。
脏读:读取了未提交的新事物,然后被回滚了
事务A读取了事务B中尚未提交的数据。如果事务B回滚,则A读取使用了错误的数据。
如花和小明两个事务,当前小明5000如花3000,小明事务中小明给如花转账1000,此时,小明4000,如花4000,再次过程中,如花事务中如花查询余额4000,并提交事务,但是,小明事务还未提交,中途因为某些原因,给回滚了,这时,如花事务读到的就是错误数据。
不可重复读:读取了提交的新事务,指更新操作
不可重复读是指对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
刚开始如花事务,查询余额100元,随后,小明事务花掉了50元,并提交,然后,如花事务再次查询余额变为了50元,然后,提交事务。指更新操作。
幻读:也是读取了提交的新事务,指增删操作
在事务A多次读取构成中,事务B对数据进行了新增操作,导致事务A多次读取的数据不一致。
如花事务,查看余额100元,随后,小明事务,又放入了50元,并提交事务,此时,如花事务,再次查询,变成了150元,新增产生的问题。
第一类事务丢失:回滚丢失
对于第一类事务丢失,就是比如A和B同时在执行一个数据,然后B事务已经提交了,然后A事务回滚了,这样B事务的操作就因A事务回滚而丢失了。
现余额100元,小明事务花掉50元,这时,如花事务,放了50元并提交,而小明事务,因为某些原因回滚到了原来的100元,导致如花事务直接给丢失了。
第二轮事务丢失:提交覆盖丢失
对于第二类事务丢失,也称为覆盖丢失,就是A和B一起执行一个数据,两个同时取到一个数据,然后,B事务首先提交,但是,A事务接下来又提交,这样就覆盖了B事务。
小明事务,刚开始拍快照100元,准备放50元,随后,如花事务,很快的放了50元,并提交,这时小明事务,还未结束,看了一下没毛病,就提交事务150,导致应该是200元,变成了150元,如花事务被覆盖。
Read uncommitted
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据,会产生脏读。
Read committed
读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据,会产生不可重复读。
Repeatable read
重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。可能会产生幻读。
Serializable
Serializable是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读,不可重复读、与幻读。但是,这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
大多数数据库默认的事务隔离级别是Read committed,比如Sql Server, Oracle。
Mysql的默认隔离级别是Repeatable read。
- 读未提交:别人改数据的事务尚未提交,我在我的事务中也能读到。
- 读已提交:别人改数据的事务已经提交,我在我的事务中才能读到。
- 可重复读:别人改数据的事务已经提交,我在我的事务中也不去读。
- 串行:我的事务尚未提交,别人就别想改数据。
- 这4种隔离级别,并行性能依次降低,安全性依次提高。
指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
举例子:
小弟出现异常,老大要不要回滚
spring总共给出了7种事务传播特性:
1.propagation_required: 默认事务类型,如果没有,就新建一个事务;如果有,就加入当前事务。适合绝大多数情况。
2.propagation_required_new: 如果没有,就新建一个事务;如果有,就将当前事务挂起。
3.propagation_nested: 如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。
4.propagation_supports: 如果没有,就以非事务方式执行;如果有,就使用当前事务。
5.propagation_not_supported: 如果没有,就以非事务方式执行;如果有,就将当前事务挂起。即无论如何不支持事务。
6.propagation_never: 如果没有,就以非事务方式执行;如果有,就抛出异常。
7.propagation_mandatory: 如果没有,就抛出异常;如果有,就使用当前事务。
总结:
【1】死活不要事务的
propagation_never: 没有就非事务执行,有就抛出异常。
propagation_not_supported: 没有就非事务执行,有就直接挂起,然后非事务执行。
【2】可有可无的
propagation_supports: 有就用,没有就算了
【3】必须有事务的
propagation_requires_new: 有没有都新建事务,如果原来有,就将原来的挂起。里面的事务,和外面事务是完全隔离的,互不影响的,外面事务不影响里面的事务,里面的事务也不影响外面的事务。
propagation_nested: 如果没有,就新建一个事务,如果有,就在当前事务中嵌套其他事务。外面的事务回滚的时候会影响里面的事务,但是,里面的事务不影响外面的事务。
propagation_requered: 如果没有,就新建一个事务,如果有,就加入当前事务。
propagation_mandatory: 如果没有,就抛出异常,如果有,就使用当前事务。
3.2 代码说明 3.2.1 propagation_never1.propagation_never: 死活不要事务,没有就非事务执行,有就抛出异常。
2.如果有事务,老大也报错,小弟也报错,一定会回滚。
----》
3.转账成功了,出现异常没有回滚,说明没有事务
4.假如,没有propagation.never,老大有事务,老大调用小弟,那老大和小弟就是在同一个事务中运行的,而现在的目的就是不想让小弟在事务中运行,所以需要配置。
5.假如,现在小弟配置了propagation.never,而老大又有事务,就会直接抛出异常。
1. 没有就非事务执行,有就直接挂起,然后非事务执行。
2. 出异常后
老大,值没有变化,说明回滚了
小弟,因为使用porpagation.not_supported,没有事务,所以,出错后没有回滚。
1. 有就用,没有就算了
2. propagation.supports有事务,就用,没有事务就算了。目前,老大有事务,没有报错,小弟,为supports类型,那他们应该在同一个事务里面,小弟报错,老大也跟着回滚。
老大,小弟,都回滚了,老大,没有减100,小弟,也没有加100。
3. 老大没有事务,小弟也没有事务,报错后,都没有回滚。
3.2.4 propagation_requires_new1.有没有都新建事务,如果原来有,就将原来的挂起。
2.由结果分析,老大没有事务,报错后,没有回滚,而小弟,新建了一个事务,报错后,回滚了。
3.老大,加了事务,没有报错,所以,没有回滚,成功减了100.
4.小弟,新建了一个事务,报错了,所以回滚了,没有加100,还是1000。两个事务互不影响。
老大有事务,出了问题回滚了,所以还是1000
小弟也有事务,但跟老大不是一个事务,它也没有出错,所以,加了100。
3.2.5 propagation_nested1.如果没有,就新建一个事务,如果有,就在当前事务中嵌套其他事务。
2.老大,有事务,没有出现问题,老大成功减100。小弟,也有事务,但出现了问题,所以回滚了,还是1000,不影响老大的事务。
3.老大出异常,老大和小弟,都回滚了。
嵌套的意思就是,事务嵌套里面了,老大出异常两个事务都回滚,小弟出异常却不影响老大的事务。
3.2.6 propagation_requered1.如果没有,就新建一个事务,如果有,就加入当前事务。
都没有改变,小弟出异常,由于是同一个事务,所以,老大小弟都回滚了。老大出异常,小弟无异常,同理,也都会回滚。
3.2.7 propagation_mandatory如果没有,就抛出异常,如果有,就使用当前事务。
老大没有事务,直接抛出异常
public class User {
private Integer id;
private String name;
private Integer money;
}
4.1.2 Dao
@Repository
public class UserDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 转出
* @param fromName
* @param money
*/
public void out(String fromName, Integer money) {
String sql = "update user set money = money-? where name =? ";
jdbcTemplate.update(sql, money,fromName);
}
/**
* 转入
* @param toName
* @param money
*/
public void in(String toName, Integer money) {
String sql ="update user set money = money+? where name =?";
jdbcTemplate.update(sql,money,toName);
}
}
4.1.3 service
@Service
XML文件配置:
public class UserService {
@Autowired
private UserDAO userDAO;
/**
* 沒有事務的转账的业务
* @param fromName
* @param toName
* @param money
*/
public void transfer(String fromName, String toName, Integer money) {
userDAO.out(fromName, money);// 转出钱
int x = 10;
if(x == 10)
throw new RuntimeException("出错啦!");
userDAO.in(toName, money);// 收入钱
}
4.1.4 xml文件配置
4.1.5 测试
@Test
public void test01(){
ApplicationContext ac = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserService bean = ac.getBean(UserService.class);
bean.transfer("tom","jerry",100);
}
@Test
public void test01_1(){
AnnotationConfigApplicationContext ac = new
AnnotationConfigApplicationContext(AppConfig.class);
UserService bean = ac.getBean(UserService.class);
bean.transfer("tom","jerry",100);
}
4.2 编程式事务
XML配置事务:在applicationContext.xml中添加事务管理器和事务管理器模版的配置
注解方式配置事务管理器和事务管理器模版
@ComponentScan("com.dk")
public class AppConfig {
@Bean
public TransactionTemplate transactionTemplate(DataSourceTransactionManager
transactionManager){
TransactionTemplate transactionTemplate = new TransactionTemplate();
transactionTemplate.setTransactionManager(transactionManager);
return transactionTemplate;
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new
DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public DataSource dataSource(){
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:mysql://192.168.75.131:3306/spring-dk");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
}
修改UserService,使用编程式事务完成事务管理:
@Service
public class UserService {
@Autowired
private UserDAO userDAO;
@Autowired
private TransactionTemplate transactionTemplate;
/**
* 编程式事务 转账的业务
* @param fromName
* @param toName
* @param money
*/
public void transfer(String fromName, String toName, Integer money) {
transactionTemplate.execute(status -> {userDAO.out(fromName, money);// 转出钱
int x = 10;
if(x==10)
throw new RuntimeException("出错啦!!");
userDAO.in(toName, money);// 收入钱
return null;
});
}
}
再测试:
@Test
public void test01(){
ApplicationContext ac = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserService bean = ac.getBean(UserService.class);
bean.transfer("tom","jerry",100);
}
@Test
public void test01_1(){
AnnotationConfigApplicationContext ac = new
AnnotationConfigApplicationContext(AppConfig.class);
UserService bean = ac.getBean(UserService.class);
bean.transfer("tom","jerry",100);
}
4.3 声明值事务-基于AspectJ XML方式
注:基于TransactionProxyFactoryBean.代理的方式是比较古老的方式,我们在这里就不赘述了。 基于XML方式的配置: 删除applicationContext.xml中的事务管理模版的配置,就是下面的配置:
添加事务定义的配置和AOP的配置:
将业务类的方法改回原来的方式:
/**
* 沒有事務的转账的业务
* @param fromName
* @param toName
* @param money
*/
public void transfer(String fromName, String toName, Integer money) {
userDAO.out(fromName, money);// 转出钱
int x = 10;
if(x == 10)
throw new RuntimeException("出错啦!");
userDAO.in(toName, money);// 收入钱
}
删除UserService中的TransactionTemplate申明。 测试: 基于注解的申明式事务: 在配置类上配置@EnableTransactionManagement开启事务。删除注解类中和事务相关的@Bane
@ComponentScan("com.dk")
@EnableTransactionManagement
public class AppConfig {
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new
DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public DataSource dataSource(){
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:mysql://192.168.75.131:3306/spring-dk");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
}
在UserService类上方或者方法上方通过@Transactional完成事务配置:
@Service
@Transactional
public class UserService {}
教学视频