博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
声明式事务
阅读量:4660 次
发布时间:2019-06-09

本文共 9540 字,大约阅读时间需要 31 分钟。

1.编程式事务

1 //1.获取Connection对象 2 Connection conn = JDBCUtils.getConnection(); 3   try { 4 //2.开启事务:取消自动提交 5     conn.setAutoCommit(false); 6 //3.执行数据库操作 7     chain.doFilter(req,resp); 8 //4.提交事务 9     conn.commit();10   }catch(Exception e) {11 //5.回滚事务12     conn.rollBack();13 14   }finally{15 //6.释放资源16 }

 

2.情景举例

①导入SQL文件:declaration_transaction.sql
②创建一个动态web工程
1.加入jar包
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar
然后是mysql驱动包即C3P0的jar包:
c3p0-0.9.1.2.jar
mysql-connector-java-5.1.37-bin.jar
2.创建一份jdbc.properties文件
jdbc.user=root
jdbc.passowrd=123456
jdbc.url=jdbc:mysql://localhost:3306/tx
jdbc.driver=com.mysql.jdbc.Driver
3.在spring配置文件中配置数据源
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 -->

1 
2   
3   
4   
5   
6

4.测试数据源:

1 public class TestDataSource {2   private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");3   @Test4   public void test() throws SQLException {    5     DataSource bean = ioc.getBean(DataSource.class);6     System.out.println(bean.getConnection());7   }8 9 }

5.配置jdbcTemplate:

1 
2   
3

6.创建Dao类

<!-- 设置扫描的包 -->
 1 <context:component-scan base-package="com.neuedu"></context:component-scan> 

1 @Repository 2 public class BookDao { 3   @Autowired 4   private JdbcTemplate jdbcTemplate; 5 /** 6 * [1]根据isbn的值查询书的价格 7 [2]根据isbn的值减少书的库存,假设每次都只买1本书 8 [3]根据用户名减少用户账户中的余额,减少的额度就是书的价格 9 */10 11   public int findPriceByIsbn(String isbn){12     String sql = "SELECT price FROM book WHERE isbn = ?";13     Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);14     return price;15   }16 }17 18 测试上面的该方法:19 public class TestDataSource {20   private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");21   private BookDao bean = ioc.getBean(BookDao.class);22   @Test23   public void test01() throws SQLException {    24 25     int findPriceByIsbn = bean.findPriceByIsbn("ISBN-005");26     System.out.println(findPriceByIsbn);27   }28 }

然后在dao类中继续编写如下方法:

1 //[2]根据isbn的值减少书的库存,假设每次都只买1本书2 public void updateStockByIsbn(String isbn){3   String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";4   jdbcTemplate.update(sql, isbn);5 }

继续测试:

1 public class TestDataSource { 2   private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); 3   private BookDao bean = ioc.getBean(BookDao.class); 4  5   @Test 6   public void test02() throws SQLException {     7     bean.updateStockByIsbn("ISBN-004"); 8   } 9 }

继续编写Dao类中的方法:

1 //[3]根据用户名减少用户账户中的余额,减少的额度就是书的价格2 public void updateBalance(String userName,int price){3   String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";4   jdbcTemplate.update(sql, price,userName);5 }

继续测试:

1 @Test2 public void test03() throws SQLException {    3   bean.updateBalance("Tom", 1000);4 }

7.创建Service层:

1 @Service 2 public class BookService { 3   @Autowired 4   private BookDao bookDao; 5  6   public void doCash(String isbn,String username){ 7     int price = bookDao.findPriceByIsbn(isbn); 8     bookDao.updateStockByIsbn(isbn); 9     bookDao.updateBalance(username, price);10   }11 }

8.注意哦:上面doCash方法中调用的三个方法应该在同一个事务中,要么同时成功,要么同时失败!

先统一设置一下:
然后先正常测试一下service中的doCash方法:

1 public class TestDataSource { 2   private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); 3   private BookDao bean = ioc.getBean(BookDao.class); 4   private BookService bookService = ioc.getBean(BookService.class); 5  6   @Test 7   public void test04() throws SQLException {     8     bookService.doCash("ISBN-001","Tom"); 9   }10 }

由于此时该方法没在事务中,如果doCash方法调用dao层的方法的时候,在中间位置出现了错误,此时就会造成一部分数据改掉了,而

另一部分数据没有改掉,这就麻烦了,所以此时应该加入事务机制!
9.如果想开启事务,就需要在spring的配置文件中配置事务管理器
<!-- 配置事务管理器,并为事务管理器配置数据源!-->

1 
2   
3

<!-- 开启基于注解的声明式事务功能,需要设置transaction-manager属性-->

<!-- 如果 事务管理器的id正好是transaction-manager的默认值transactionManager,则可以省略-->
 1 <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/> 
然后在service层的doCash方法上加上:@Transactional注解这样就开启了事务!需要注意的是事务一般是加在service层的!
③数据库操作
[1]根据isbn的值查询书的价格
[2]根据isbn的值减少书的库存,假设每次都只买1本书
[3]根据用户名减少用户账户中的余额,减少的额度就是书的价格

总结:事务可以分为编程式事务和声明式事务

2.声明式事务

①基本原理:AOP
[1]前置通知:开启事务
[2]返回通知:提交事务
[3]异常通知:回滚事务
[4]后置通知:释放资源
②事务管理器
③导入jar包
[1]IOC容器需要的jar包
[2]AOP需要的jar包
[3]JdbcTemplate操作需要的jar包
[5]MySQL驱动和C3P0

④配置

[1]配置数据源
[2]配置JdbcTemplate,并装配数据源
[3]配置事务管理器,并装配数据源

1 
2   
3

[4]开启基于注解的声明式事务功能

<tx:annotation-driven transaction-manager ="dataSourceTransactionManager"/>
如果事务管理器的bean的id正好是transaction-manager的默认值transactionManager,则可以省略

[5]在事务方法上加@Transactional注解

4.事务属性的设置
①事务的传播行为
②事务的隔离级别
③事务根据什么异常不进行回滚
④事务的超时属性
⑤事务的只读属性
①事务的传播行为[参见第8章world文档]
[1]解释:一个事务运行在另一个有事务的方法中,那么当前方法是开启新事务还是在原有的事务中运行。
[2]设置事务方法在调用其他事务方法时,自己的事务如何传播给被调用的方法。[默认是required]
[3]设置方式
案例演示:
在上面的dao中增加一个方法,用于演示事务!

1 //为了测试事务的传播行为,需要增加一个数据库操作 2 public void updatePrice(String isbn, int price){ 3   String sql = "UPDATE book SET price = ? WHERE isbn = ?"; 4   jdbcTemplate.update(sql, price,isbn); 5 } 6    同样,在service类中添加一个方法,如下所示: 8 @Transactional 9 public void updatePrice(String isbn, int price){10   bookDao.updatePrice(isbn, price);11 }

此时当前service类中就有两个事务方法了,然后我们现在新创建一个service类,加入到IOC容器中,并将bookService注入,

然后新建一个事务方法,如下所示:

1 @Component 2 public class MultiTX { 3   @Autowired 4   private BookService bookService; 5  6   @Transactional 7   public void multiTx(){ 8     bookService.doCash("ISBN-003","Tom"); 9     bookService.updatePrice("ISBN-005",888);10   }11 }

为了演示该service方法中调用的另外两个方法是不是使用了当前service方法的事务,这里我们将该service方法调用的第二个方法

弄出一个异常,然后看看该service方法调用的第一个service方法是不是回滚了!
先统一设置一下表中的数据:
UPDATE `account` SET balance = 10000;
UPDATE book_stock SET stock = 1000;
UPDATE book SET price = 1000;
用测试类进行测试:先正常测试一遍,然后再整出个异常测试一下:

1 public class TestDataSource { 2   private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); 3   private BookDao bean = ioc.getBean(BookDao.class); 4   private BookService bookService = ioc.getBean(BookService.class); 5   private MultiTX multiTx = ioc.getBean(MultiTX.class); 6   @Test 7   public void test04() throws SQLException {     8     multiTx.multiTx(); 9   }10 }

会发现@Transactional注解默认使用的传播属性就是required!

此时可以将BookService类中的两个方法都加上propagation=Propagation.REQUIRES_NEW 属性,也就是如下所示:
@Transactional(propagation=Propagation.REQUIRES_NEW)
然后在执行刚才的有异常的测试方法!
②事务的隔离级别 :用于解决并发问题[在两次获取价格的方法中间打断点]
isolation=Isolation. READ_COMMITTED
先恢复数据表中的数据:
UPDATE `account` SET balance = 10000;
UPDATE book_stock SET stock = 1000;
UPDATE book SET price = 1000;
修改service类中的doCash方法为:【即两次获取价格!】

1 @Transactional(propagation=Propagation.REQUIRES_NEW) 2 public void doCash(String isbn,String username){ 3   int price = bookDao.findPriceByIsbn(isbn); 4   System.out.println("price1:"+price); 5   bookDao.updateStockByIsbn(isbn); 6   bookDao.updateBalance(username, price); 7  8   price = bookDao.findPriceByIsbn(isbn); 9   System.out.println("price2:"+price);10 }

在测试方法中调用BookService类中的doCash方法,然后在doCash方法中两次获取价格的方法中间打断点,就会发现

第一次是1000元,然后我们修改了数据库之后,读出来的还是1000元,这是因为数据库默认的隔离级别就是可重复读!
如果我们在doCash方法的事务注解上加一个isolation属性,如下所示:

1 @Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED) 2 public void doCash(String isbn,String username){ 3   int price = bookDao.findPriceByIsbn(isbn); 4   System.out.println("price1:"+price); 5   bookDao.updateStockByIsbn(isbn); 6   bookDao.updateBalance(username, price); 7  8   price = bookDao.findPriceByIsbn(isbn); 9   System.out.println("price2:"+price);10 }

就会发现如果在两次获取价格之间打了断点,然后修改了值的话就会读出不一样的效果,说明事务的隔离级别设置就生效了!

③事务根据什么异常不进行回滚
默认情况下,出现异常就回滚,noRollbackFor属性可以设置出现什么异常不进行回滚:
 1 noRollbackFor=ArithmeticException.class 

1 @Transactional(propagation=Propagation.REQUIRES_NEW, 2 isolation=Isolation.READ_COMMITTED, 3 noRollbackFor=ArithmeticException.class) 4 public void doCash(String isbn,String username){ 5   int price = bookDao.findPriceByIsbn(isbn); 6   System.out.println("price1:"+price); 7   bookDao.updateStockByIsbn(isbn); 8   bookDao.updateBalance(username, price); 9   System.out.println(10/0);//出了异常信息10 }

④事务的超时属性【timeout=3】

[1]数据库事务在执行过程中,会占用数据库资源,所以如果某一个事务执行的时间太长,
那么就会导致资源被长时间占用,影响其他事务执行。[死循环,网络问题]
[2]可以通过设置超时属性,将超时没有执行完的事务回滚,相当于对该操作进行撤销。

1 @Transactional(propagation=Propagation.REQUIRES_NEW, 2 isolation=Isolation.READ_COMMITTED, 3 noRollbackFor=ArithmeticException.class, 4 timeout=3) 5 public void doCash(String isbn,String username){ 6   int price = bookDao.findPriceByIsbn(isbn); 7   System.out.println("price1:"+price); 8   bookDao.updateStockByIsbn(isbn); 9   try {10     Thread.sleep(1000*5);11   } catch (InterruptedException e) {12     // TODO Auto-generated catch block13     e.printStackTrace();14   }15   bookDao.updateBalance(username, price);16 }

⑤事务的只读属性【readOnly=true】

数据库会对事务进行优化,如果是一个查询操作,那么数据库可以有针对性的进行优化。我们可以通过设置事务属性,
告诉数据库当前操作是一个只读操作,便于数据库进行优化。

5.基于XML的声明式事务

1 
2
3   
4   
5   
6   
7
8
9
10   
11     
17     
18     
19     
20     
21   
22

转载于:https://www.cnblogs.com/a791551107/p/7459923.html

你可能感兴趣的文章
unity, access material
查看>>
unity, surface shader access world position and localposition
查看>>
微表面分布函数(Microfacet Distribution Function)确切含义
查看>>
.net webapi接收form-data类型
查看>>
Nginx安装
查看>>
岛屿问题(LeetCode200)
查看>>
Nodejs
查看>>
VUE【一、概述】
查看>>
Realm使用
查看>>
C#中运算符的优先级
查看>>
UITableView常用属性和方法 - 永不退缩的小白菜
查看>>
Windows下安装PHP及开发环境配置
查看>>
【BZOJ】【4152】【AMPZZ2014】The Captain
查看>>
仓库管理系统
查看>>
SQL 中的正则函数
查看>>
8 MySQL--单表查询
查看>>
【清北前紧急补课4】国王游戏
查看>>
Clang安装配置解释
查看>>
水题练手POJ2739
查看>>
[SharePoint] SharePoint 错误集 3
查看>>