SpringBoot入门系列~事务管理

1、什么是事务

  我们在开发企业应用时,对于业务人员的一个操作实际是对数据读写的多步操作的结合。由于数据操作在顺序执行的过程中,任何一步操作都有可能发生异常,异常会导致后续操作无法完成,此时由于业务逻辑并未正确的完成,之前成功操作数据的并不可靠,需要在这种情况下进行回退。

事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就回退到事务开始未进行操作的状态。

事务管理是Spring框架中最为常用的功能之一,我们在使用Spring Boot开发应用时,大部分情况下也都需要使用事务。

2、事务的四个特性(ACID)

  • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
  • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中     

3、业务场景模拟

  以转账为案例模拟事务控制:用户1向用户2转账,转账成功,用户1的账户金额扣减、用户2的账户金额增加

4、创建账户实体,启动容器生成账户表

 1 package com.sun.spring.boot.pojo;
 2 
 3 import java.math.BigDecimal;
 4 
 5 import javax.persistence.Column;
 6 import javax.persistence.Entity;
 7 import javax.persistence.GeneratedValue;
 8 import javax.persistence.Id;
 9 import javax.persistence.Table;
10 
11 /**
12  * 账户基本信息
13 * @ClassName: AccountInfoBean  
14 * @author sunt  
15 * @date 2017年11月9日 
16 * @version V1.0
17  */
18 @Entity
19 @Table(name = "T_ACCOUNT")
20 public class AccountInfoBean {
21 
22     /**
23      * 账户编码
24      */
25     @Id
26     @GeneratedValue
27     @Column(name = "F_ACCOUNT_ID")
28     private Integer accountId;
29     
30     /**
31      * 户主姓名
32      */
33     @Column(name = "F_ACCOUNT_NAME", length = 50,nullable = false)
34     private String accountName;
35     
36     /**
37      * 账户余额
38      */
39     @Column(name = "F_BALANCE",precision = 10,scale = 2,nullable = false)
40     private BigDecimal balance;
41 
42     public Integer getAccountId() {
43         return accountId;
44     }
45 
46     public void setAccountId(Integer accountId) {
47         this.accountId = accountId;
48     }
49 
50     public String getAccountName() {
51         return accountName;
52     }
53 
54     public void setAccountName(String accountName) {
55         this.accountName = accountName;
56     }
57 
58     public BigDecimal getBalance() {
59         return balance;
60     }
61 
62     public void setBalance(BigDecimal balance) {
63         this.balance = balance;
64     }
65 
66     
67 }

5、定义账户Dao接口

 1 package com.sun.spring.boot.dao;
 2 
 3 import org.springframework.data.jpa.repository.JpaRepository;
 4 import org.springframework.stereotype.Repository;
 5 
 6 import com.sun.spring.boot.pojo.AccountInfoBean;
 7 /**
 8  * 账户dao接口
 9 * @ClassName: IAccountInfoDao  
10 * @author sunt  
11 * @date 2017年11月9日 
12 * @version V1.0
13  */
14 @Repository
15 public interface IAccountInfoDao extends JpaRepository<AccountInfoBean, Integer>{
16 
17 }

6、创建账户业务层接口和实现类

 1 package com.sun.spring.boot.service;
 2 
 3 import java.math.BigDecimal;
 4 
 5 /**
 6  * 账户service接口
 7 * @ClassName: IAccountInfoService  
 8 * @author sunt  
 9 * @date 2017年11月9日 
10 * @version V1.0
11  */
12 public interface IAccountInfoService {
13 
14     /**
15      * 转账接口
16     * @Title: transferAccounts 
17     * @author sunt  
18     * @date 2017年11月9日
19     * @param fromAccountId 转出方账户,扣减
20     * @param toAccountId 转入方账户,入账
21     * @param balance 交易金额数
22     * @return void
23      */
24     public void transferAccounts(int fromAccountId, int toAccountId, BigDecimal balance);
25 }
 1 package com.sun.spring.boot.service.impl;
 2 
 3 import java.math.BigDecimal;
 4 
 5 import javax.transaction.Transactional;
 6 
 7 import org.apache.log4j.Logger;
 8 import org.springframework.beans.factory.annotation.Autowired;
 9 import org.springframework.stereotype.Service;
10 
11 import com.sun.spring.boot.dao.IAccountInfoDao;
12 import com.sun.spring.boot.pojo.AccountInfoBean;
13 import com.sun.spring.boot.service.IAccountInfoService;
14 /**
15  * 账户service实现
16 * @ClassName: AccountInfoServiceImpl  
17 * @author sunt  
18 * @date 2017年11月9日 
19 * @version V1.0
20  */
21 @Service
22 public class AccountInfoServiceImpl implements IAccountInfoService {
23 
24     /**
25      * 日志
26      */
27     private Logger logger = Logger.getLogger(AccountInfoServiceImpl.class);
28     
29     @Autowired
30     private IAccountInfoDao accountInfoDao;
31     
32     @Override
33     @Transactional
34     public void transferAccounts(int fromAccountId, int toAccountId, BigDecimal balance) {
35         logger.info("转出账户:【" + fromAccountId + "】,转入账户:【" + toAccountId + "】,交易金额:" + balance);
36         
37         //A账户余额扣减、B账户余额新增的过程
38         AccountInfoBean fromAccount = accountInfoDao.getOne(fromAccountId);
39         fromAccount.setBalance(fromAccount.getBalance().subtract(balance));
40         logger.info("转出操作...");
41         accountInfoDao.save(fromAccount);
42         
43         AccountInfoBean toAccount = accountInfoDao.getOne(toAccountId);
44         toAccount.setBalance(toAccount.getBalance().add(balance));
45         logger.info("转入操作...");
46         //模拟异常信息
47         //int flag = 8/0;
48         accountInfoDao.save(toAccount);
49         
50     }
51 
52 }

7、创建账户Controller

 1 package com.sun.spring.boot.controller;
 2 
 3 import java.math.BigDecimal;
 4 
 5 import org.springframework.beans.factory.annotation.Autowired;
 6 import org.springframework.web.bind.annotation.RequestMapping;
 7 import org.springframework.web.bind.annotation.RestController;
 8 
 9 import com.sun.spring.boot.service.IAccountInfoService;
10 
11 /**
12  * 账户操作Controller
13 * @ClassName: AccountInfoController  
14 * @author sunt  
15 * @date 2017年11月9日 
16 * @version V1.0
17  */
18 @RestController
19 @RequestMapping(value = "/account")
20 public class AccountInfoController {
21 
22     @Autowired
23     private IAccountInfoService accountInfoService;
24     /**
25      * 模拟转账操作
26      *     A用户向B用户转账600
27      * @Title: transferAccounts 
28      * @author sunt  
29      * @date 2017年11月9日
30      * @return String
31      */
32     @RequestMapping(value = "/transferAccounts")
33     public String transferAccounts() {
34         try {
35             //编码对应数据库账户的编码
36             accountInfoService.transferAccounts(1, 2, new BigDecimal(600));
37             return "转账成功!";
38         } catch (Exception e) {
39             e.printStackTrace();
40             return "转账异常!";
41         }
42     }
43 }

浏览器发送请求:http://127.0.0.1:8088/account/transferAccounts实现用户1向用户2转账600元,

  • 转账成功返回:转账成功到浏览器响应

,转账之前数据库数据如下:,预计成功之后的数据:1400;1100

发送完请求浏览器响应的数据

  • 模拟转账异常: service加入异常操作:
    目前账户1400,1100,如果出现异常应该数据保持不变,现在浏览器重新发送请求

数据库数据:出现1用户的钱被扣了,2用户的账户实际没到账,这个问题是特别严重的问题  

所以这种情况就是因为事务没有控制,出现异常数据没有回滚导致的,解决方案,在业务层加事务控制的注解

再次模拟异常操作:现在表里面的数据金额:800  1100元结果如下

    没有出现多扣钱没到帐的情况,所以事务控制成功

8、源码下载地址:svn://gitee.com/SunnySVN/SpringBoot

https://gitee.com/SunnySVN/SpringBoot/

9、表结构和数据

 

 1 /*
 2 Navicat MySQL Data Transfer
 3 
 4 Source Server         : mysql
 5 Source Server Version : 50528
 6 Source Host           : 127.0.0.1:3306
 7 Source Database       : spring-boot
 8 
 9 Target Server Type    : MYSQL
10 Target Server Version : 50528
11 File Encoding         : 65001
12 
13 Date: 2017-11-09 16:53:40
14 */
15 
16 SET FOREIGN_KEY_CHECKS=0;
17 
18 -- ----------------------------
19 -- Table structure for t_account
20 -- ----------------------------
21 DROP TABLE IF EXISTS `t_account`;
22 CREATE TABLE `t_account` (
23   `f_account_id` int(11) NOT NULL AUTO_INCREMENT,
24   `f_account_name` varchar(50) NOT NULL,
25   `f_balance` decimal(10,2) NOT NULL,
26   PRIMARY KEY (`f_account_id`)
27 ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
28 
29 -- ----------------------------
30 -- Records of t_account
31 -- ----------------------------
32 INSERT INTO `t_account` VALUES ('1', '小明', '800.00');
33 INSERT INTO `t_account` VALUES ('2', '小红', '1100.00');

 

posted @ 2017-11-09 16:55  sunny1009  阅读(296)  评论(0)    收藏  举报