spring事务

您所在的位置:网站首页 spring不支持对数据库的事务管理 spring事务

spring事务

#spring事务| 来源: 网络整理| 查看: 265

一、什么是事务 1.1 事务的概念

事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);

通俗的讲,就是为了达到某个目的,而做的一系列的操作,要么一起成功(事务提交),要么一起失败(事务回滚)。

最常见的例子就是转账:

小明给如花转账:

开启事务-------

从小明的账户扣除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事务的传播特性 3.1 传播特性 3.1.1 什么是传播特性?

指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

举例子:

小弟出现异常,老大要不要回滚

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_never

1.propagation_never: 死活不要事务,没有就非事务执行,有就抛出异常。

2.如果有事务,老大也报错,小弟也报错,一定会回滚。

----》

3.转账成功了,出现异常没有回滚,说明没有事务

4.假如,没有propagation.never,老大有事务,老大调用小弟,那老大和小弟就是在同一个事务中运行的,而现在的目的就是不想让小弟在事务中运行,所以需要配置。

5.假如,现在小弟配置了propagation.never,而老大又有事务,就会直接抛出异常。

3.2.2 propagation_not_supported

1. 没有就非事务执行,有就直接挂起,然后非事务执行。

2. 出异常后

  老大,值没有变化,说明回滚了

小弟,因为使用porpagation.not_supported,没有事务,所以,出错后没有回滚。

3.2.3 propagation_supports

1. 有就用,没有就算了

2. propagation.supports有事务,就用,没有事务就算了。目前,老大有事务,没有报错,小弟,为supports类型,那他们应该在同一个事务里面,小弟报错,老大也跟着回滚。

老大,小弟,都回滚了,老大,没有减100,小弟,也没有加100。

3. 老大没有事务,小弟也没有事务,报错后,都没有回滚。

3.2.4 propagation_requires_new

1.有没有都新建事务,如果原来有,就将原来的挂起。

2.由结果分析,老大没有事务,报错后,没有回滚,而小弟,新建了一个事务,报错后,回滚了。

3.老大,加了事务,没有报错,所以,没有回滚,成功减了100.

4.小弟,新建了一个事务,报错了,所以回滚了,没有加100,还是1000。两个事务互不影响。

老大有事务,出了问题回滚了,所以还是1000

小弟也有事务,但跟老大不是一个事务,它也没有出错,所以,加了100。

3.2.5 propagation_nested

1.如果没有,就新建一个事务,如果有,就在当前事务中嵌套其他事务。

2.老大,有事务,没有出现问题,老大成功减100。小弟,也有事务,但出现了问题,所以回滚了,还是1000,不影响老大的事务。

3.老大出异常,老大和小弟,都回滚了。

嵌套的意思就是,事务嵌套里面了,老大出异常两个事务都回滚,小弟出异常却不影响老大的事务。

3.2.6 propagation_requered

1.如果没有,就新建一个事务,如果有,就加入当前事务。

都没有改变,小弟出异常,由于是同一个事务,所以,老大小弟都回滚了。老大出异常,小弟无异常,同理,也都会回滚。

3.2.7 propagation_mandatory

如果没有,就抛出异常,如果有,就使用当前事务。

老大没有事务,直接抛出异常

四、spring配置事务 4.1 基础代码

4.1.1 实体类 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 {}

教学视频 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3