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');
最新同步更新地址:https://www.sunnyblog.top/
感谢您花时间阅读此篇文章,如果您觉得这篇文章你学到了东西也是为了犒劳下博主的码字不易不妨打赏一下吧,让博主能喝上一杯咖啡,在此谢过了!
如果您觉得阅读本文对您有帮助,请点一下左下角“推荐”按钮,您的“推荐”将是我最大的写作动力!另外您也可以选择【关注我】,可以很方便找到我!
本文版权归作者和博客园共有,来源网址:https://www.cnblogs.com/sunny1009 欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利!