spring 事务详解

基于注解方式

事务,一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。

事务应该具有4个属性:原子性、一致性、隔离性、持久性。

 

原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

 

例如:在银行转账的时候,假设要从账户A转账100元到账户B,只允许两种情况发生:①转账成功,即账户A余额减少100并且账户B余额增加100 ②转账失败,即账户A的余额不变并且账户B的余额不变。 除了这两种情况之外的都绝对不允许发生。

wKioL1QizqbQmIU1AAC9-WXs6Zc866.jpg

BookShopDao接口

1
2
3
4
5
6
7
8
9
10
11
public  interface  BookShopDao {
 
     //根据书号获取书的单价
     public  int  findBookPriceByIsbn(String isbn);
     
     //更新书的库存,使书号对应的库存-1
     public  void  updateBookStock(String isbn);
     
     //更新用户的账户余额,是username的balance-price
     public  void  updateUserAccount(String username,  int  price);
}

BookShopDao接口的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Repository ( "bookShopDao" )
public  class  BookShopImpl  implements  BookShopDao{
 
     @Autowired
     private  JdbcTemplate jdbcTemplate;
     @Override
     public  int  findBookPriceByIsbn(String isbn) {
         String sql =  "select price from book where isbn = ?" ;
         return  jdbcTemplate.queryForObject(sql, Integer. class , isbn);
     }
 
     @Override
     public  void  updateBookStock(String isbn) {
         //检查书的库存是否足够,若不够,则抛异常
         String s =  "select stock from book_stock where isbn = ?" ;
         int  stock = jdbcTemplate.queryForObject(s, Integer. class , isbn);
         //System.out.println("stock:" + stock);
         if  (stock <=  0 ) {
             throw  new  BookStockException( "库存不足" );
         }
         String sql =  "update book_stock set stock = stock -1 where isbn = ?" ;
         jdbcTemplate.update(sql, isbn);
     }
 
     @Override
     public  void  updateUserAccount(String username,  int  price) {
         //验证余额不否足够。若余额不足则抛出异常
         String s =  "select balance from account where username = ?" ;
         int  balance = jdbcTemplate.queryForObject(s, Integer. class , username);
         if  (balance < price) {
             //System.out.println("余额不足");
             throw  new  UserAccountException( "余额不足" );
         }
         String sql =  "update account set balance = balance - ? where username = ?" ;
         jdbcTemplate.update(sql, price, username);
     }
 
}

BookStockException.java和UserAccountException.java为自定义的两个异常类,继承RuntimeException。

BookShopService接口

1
2
3
4
public  interface  BookShopService {
 
     public  void  purchase(String username, String isbn);
}

BookShopService接口的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service ( "bookShopService" )
public  class  BookShopServiceImpl  implements  BookShopService{
 
     @Autowired
     private  BookShopDao bookShopDao;
     
     //添加事务注解。
     //当用户的账户余额不足并且书的库存足够多的时候,
     //在执行findBookPriceByIsbn的时候不会抛出异常,在执行updateBookStock的时候也不会抛出异常。
     //而在执行updateUserAccount的时候会抛出“余额不足”的异常。
     //显然这次购买是不成功的。如果没有声明式事务,则书的库存会-1.显然这样是有问题的。
     //添加了声明式事务之后,下面的三个方法要么都执行成功,否则都不会对数据库进行操作。
     //这样就能保证数据的正确性。
     @Transactional
     @Override
     public  void  purchase(String username, String isbn) {
         //获取书的单价
         int  price = bookShopDao.findBookPriceByIsbn(isbn);
         //更新书的库存
         bookShopDao.updateBookStock(isbn);
         //更新用户余额
         bookShopDao.updateUserAccount(username, price);
     }
 
}

applicationContext.xml配置

1
2
3
4
5
6
7
8
9
10
<!-- 配置Spring的JdbcTemplate -->
< bean  id = "jdbcTemplate"  class = "org.springframework.jdbc.core.JdbcTemplate" >
     < property  name = "dataSource"  ref = "dataSource" ></ property >
</ bean >
<!-- 配置事务管理器 -->
< bean  id = "tansactionManager"  class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
     < property  name = "dataSource"  ref = "dataSource" ></ property >
</ bean >
<!-- 启用事务注解 -->
< tx:annotation-driven  transaction-manager = "tansactionManager" />

 

测试

1
2
3
4
5
6
7
8
private  BookShopService bookShopService =  null ;
applicationContext =  new  ClassPathXmlApplicationContext( "applicationContext.xml" );
bookShopService = applicationContext.getBean(BookShopService. class );
 
@Test
public  void  testBookShopService() {
     bookShopService.purchase( "umgsai" "1001" );
}

 

 


事务的传播行为及事务的注解配置

定义接口CashierImpl.java及实现类。实现类如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service ( "cashier" )
public  class  CashierImpl  implements  Cashier {
 
     @Autowired
     private  BookShopService bookShopService;
     @Transactional
     @Override
     public  void  checkout(String username, List<String> isbns) {
         for  (String isbn : isbns) {
             bookShopService.purchase(username, isbn);
         }
     }
}

BookShopServiceImpl实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Service ( "bookShopService" )
public  class  BookShopServiceImpl  implements  BookShopService{
 
     @Autowired
     private  BookShopDao bookShopDao;
     
     /**添加事务注解。假设A方法调用purchase方法。
     *1.使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时
     *如何使用事务,默认取值为REQUIRED,即使用A方法的事务。
     *REQUIRES_NEW:使用purchase方法自己的事务,A方法的事务会被挂起。
     *2.使用isolation指定事务的隔离级别,最常用的取值为READ_COMMITTED。
     *3.默认情况下Spring的声明式事务对所有的运行时异常进行回滚。也可以通过对应的属性进行设置。
      *noRollbackFor={UserAccountException.class}  指定对于UserAccountException异常不回滚。
      *通常情况下回滚属性取默认值即可。
      *4.使用readOnly指定事务是否只读。表示这个事务只读取事务不更新事务,帮助数据库引擎优化事务。
      *如果是一个只读取数据库数据的方法应设置readOnly=true。
      *5.使用timeout指定强制回滚之前事务可以占用的时间。单位为秒。如果超时则会强制回滚。
      */
     @Transactional (propagation=Propagation.REQUIRES_NEW,
             isolation=Isolation.READ_COMMITTED
             )
     @Override
     public  void  purchase(String username, String isbn) {
         /*
         try {
             Thread.sleep(5000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         */
         //获取书的单价
         int  price = bookShopDao.findBookPriceByIsbn(isbn);
         //更新书的库存
         bookShopDao.updateBookStock(isbn);
         //更新用户余额
         bookShopDao.updateUserAccount(username, price);
     }
 
}

测试方法

1
2
3
4
@Test
public  void  testTransactionalPropagation() {
     cashier.checkout( "umgsai" , Arrays.asList( "1001" "1002" ));
}

 

wKiom1QjyYXif4ldAAMsIBRhHw8336.jpg

 

基于配置文件方式

wKioL1Qk0QvxKmdfAADWFY98B1s037.jpg

BookShopDao接口

1
2
3
4
5
6
7
8
9
10
11
public  interface  BookShopDao {
 
     //根据书号获取书的单价
     public  int  findBookPriceByIsbn(String isbn);
     
     //更新书的库存,使书号对应的库存-1
     public  void  updateBookStock(String isbn);
     
     //更新用户的账户余额,是username的balance-price
     public  void  updateUserAccount(String username,  int  price);
}

BookShopDao接口的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public  class  BookShopImpl  implements  BookShopDao{
 
     private  JdbcTemplate jdbcTemplate;
     public  void  setJdbcTemplate(JdbcTemplate jdbcTemplate) {
         this .jdbcTemplate = jdbcTemplate;
     }
     @Override
     public  int  findBookPriceByIsbn(String isbn) {
         String sql =  "select price from book where isbn = ?" ;
         return  jdbcTemplate.queryForObject(sql, Integer. class , isbn);
     }
 
     @Override
     public  void  updateBookStock(String isbn) {
         //检查书的库存是否足够,若不够,则抛异常
         String s =  "select stock from book_stock where isbn = ?" ;
         int  stock = jdbcTemplate.queryForObject(s, Integer. class , isbn);
         //System.out.println("stock:" + stock);
         if  (stock <=  0 ) {
             throw  new  BookStockException( "库存不足" );
         }
         String sql =  "update book_stock set stock = stock -1 where isbn = ?" ;
         jdbcTemplate.update(sql, isbn);
     }
 
     @Override
     public  void  updateUserAccount(String username,  int  price) {
         //验证余额不否足够。若余额不足则抛出异常
         String s =  "select balance from account where username = ?" ;
         int  balance = jdbcTemplate.queryForObject(s, Integer. class , username);
         if  (balance < price) {
             //System.out.println("余额不足");
             throw  new  UserAccountException( "余额不足" );
         }
         String sql =  "update account set balance = balance - ? where username = ?" ;
         jdbcTemplate.update(sql, price, username);
     }
 
}

 

BookStockException.java和UserAccountException.java为自定义的两个异常类,继承RuntimeException。

BookShopService接口

1
2
3
4
public  interface  BookShopService {
 
     public  void  purchase(String username, String isbn);
}

BookShopService接口的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public  class  BookShopServiceImpl  implements  BookShopService{
 
     private  BookShopDao bookShopDao;
     public  void  setBookShopDao(BookShopDao bookShopDao) {
         this .bookShopDao = bookShopDao;
     }
     
     @Override
     public  void  purchase(String username, String isbn) {
         /*
         try {
             Thread.sleep(5000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         */
         //获取书的单价
         int  price = bookShopDao.findBookPriceByIsbn(isbn);
         //更新书的库存
         bookShopDao.updateBookStock(isbn);
         //更新用户余额
         bookShopDao.updateUserAccount(username, price);
     }
 
}

applicationContext.xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<? xml  version = "1.0"  encoding = "UTF-8" ?>
< beans  xmlns = "http://www.springframework.org/schema/beans"
     xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
     xmlns:context = "http://www.springframework.org/schema/context"
     xmlns:tx = "http://www.springframework.org/schema/tx"
     xmlns:aop = "http://www.springframework.org/schema/aop"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
 
     < context:component-scan  base-package = "com.spring" ></ context:component-scan >
     <!-- 导入资源文件 -->
     < context:property-placeholder  location = "classpath:db.properties"  />
     <!-- 配置C3P0数据源 -->
     < bean  id = "dataSource"  class = "com.mchange.v2.c3p0.ComboPooledDataSource" >
         < property  name = "user"  value = "${jdbc.user}" ></ property >
         < property  name = "password"  value = "${jdbc.password}" ></ property >
         < property  name = "driverClass"  value = "${jdbc.driverClass}" ></ property >
         < property  name = "jdbcUrl"  value = "${jdbc.jdbcUrl}" ></ property >
         
         < property  name = "initialPoolSize"  value = "${jdbc.initialPoolSize}" ></ property >
         < property  name = "maxPoolSize"  value = "${jdbc.maxPoolSize}" ></ property >
     </ bean >
     
     <!-- 配置Spring的JdbcTemplate -->
     < bean  id = "jdbcTemplate"  class = "org.springframework.jdbc.core.JdbcTemplate" >
         < property  name = "dataSource"  ref = "dataSource" ></ property >
     </ bean >
     
     <!-- 配置bean -->
     < bean  id = "bookShopDao"  class = "com.spring.tx.xml.BookShopImpl" >
         < property  name = "jdbcTemplate"  ref = "jdbcTemplate" ></ property >
     </ bean >
     
     < bean  id = "bookShopService"  class = "com.spring.tx.xml.BookShopServiceImpl" >
         < property  name = "bookShopDao"  ref = "bookShopDao" ></ property >
     </ bean >
     
     < bean  id = "cashier"  class = "com.spring.tx.xml.CashierImpl" >
         < property  name = "bookShopService"  ref = "bookShopService" ></ property >
     </ bean >
     
     <!-- 1.配置事务管理器 -->
     < bean  id = "transactionManager"  class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
         < property  name = "dataSource"  ref = "dataSource" ></ property >
     </ bean >
     
     <!-- 2.配置事务属性 -->
     < tx:advice  id = "txAdvice"  transaction-manager = "transactionManager" >
          < tx:attributes >
            <!-- 根据方法名指定事务的属性 -->
            < tx:method  name = "purchase"  propagation = "REQUIRES_NEW"  timeout = "3" />
            
            < tx:method  name = "*" />
            < tx:method  name = "get*"  read-only = "true" />
            < tx:method  name = "find*"  read-only = "true" />
            
          </ tx:attributes >
     </ tx:advice >
     
     <!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来 -->
     < aop:config >
         < aop:pointcut  expression = "execution(* com.spring.tx.xml.BookShopService.*(..))"  id = "txPointCut" />
         < aop:advisor  advice-ref = "txAdvice"  pointcut-ref = "txPointCut" />
     </ aop:config >
     
     < aop:config >
         < aop:pointcut  expression = "execution(* com.spring.tx.xml.Cashier.*(..))"  id = "cashierPointCut" />
         < aop:advisor  advice-ref = "txAdvice"  pointcut-ref = "cashierPointCut" />
     </ aop:config >
</ beans >

测试

1
2
3
4
         @Test
     public  void  testTransactionalPropagation() {
         cashier.checkout( "umgsai" , Arrays.asList( "1001" "1002" ));
     }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值