关于"详解Springboot事务管理"的攻略,我可以给出以下的完整解析:
什么是事务管理
事务(Transaction)是指作为一个不可分割的工作单位所需要执行的一系列操作,这些操作要么全部都执行成功,要么全部都执行失败。对于一些需要多步操作的业务中,我们需要保证其中的每一步都可以正确执行,并且在其中任何一步出错的情况下,都可以撤回所有操作以保证数据的一致性。
事务管理就是针对这种业务场景,进行的一种统一的事务处理机制,通过某种方式,能够确保整个事务的原子性、一致性以及隔离性和持久性。
在Spring框架中,为了支持事务,提供了一种事务操作的管理机制。当我们配置了Spring的事务管理后,在开启事务的情况下,Spring会自动创建一个事务管理器,并为每一个事务绑定一个单独的数据库连接。在操作数据库过程中,如果其中有任何一步执行失败,整个事务都会回滚,确保数据的一致性。
好了,了解了什么是事务管理,接下来我们来看看如何在Springboot中进行配置,实现一个基本的事务案例。
Springboot事务管理配置
在Springboot应用的pom文件中,需要导入以下的对应的依赖项
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--添加事务支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--添加MySQL支持-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
配置文件:application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&autoReconnect=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace
这里我们配置了连接到本地 mysql 数据库,在 application.properties 文件中,指定了数据库连接信息和用户名密码。并且为了使JPA与数据库表进行同步,配置了JPA的 hibernate.ddl-auto 属性。
除此之外,在依赖项中,需要添加 spring-boot-starter-data-jpa , 它提供了数据库操作的事务支持。
示例一:转账处理
考虑这样一个业务场景,一个应用中涉及到了转账操作。其中涉及到了两个数据表, account 表表示账户信息,内含列 id、name 和 balance;transfer_log 表表示转账流程信息,是一个记录转账历史的表,内含列 id、out_account、in_account 和 money 。那么要在不同转账户之间进行转账的操作,我们肯定需要管理它的事务,即将它的转账操作封装到一个事务中,保证在转账出现异常时也能保证数据的一致性。
我们先看一下account表的定义,这里只列出了关键字段,数据表结构如下
CREATE TABLE `account` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '自动编号',
`name` VARCHAR(50) DEFAULT NULL COMMENT '名称',
`balance` DECIMAL(20,2) DEFAULT NULL COMMENT '余额',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
定义transfer_log表的定义,数据表结构如下
CREATE TABLE `transfer_log` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '自动编号',
`out_account` INT(11) NOT NULL COMMENT '转出账户',
`in_account` INT(11) NOT NULL COMMENT '转入账户',
`money` DECIMAL(10,2) NOT NULL COMMENT '转账金额',
PRIMARY KEY (`id`),
) ENGINE=INNODB DEFAULT CHARSET=utf8;
上述代码中,transfer_log中的 out_account、in_account 字段需要赋值账户表account中的id,即关联查询。
针对转账场景下的事务管理,下面我们提供一个演示程序:
@Service
public class TransferService {
@Autowired
private AccountRepository accountRepository;
@Autowired
private TransferLogRepository transferLogRepository;
/**
* 转账
* @param fromUser
* @param toUser
* @param money
* @throws ServiceException 转账异常
*/
@Transactional(rollbackFor = ServiceException.class)
public void transfer(String fromUser, String toUser, Double money) throws ServiceException {
Account fromAccount = accountRepository.findByUsername(fromUser);
Account toAccount = accountRepository.findByUsername(toUser);
if(fromAccount.getBalance() < money){
throw new ServiceException("账户余额不足");
}
fromAccount.setBalance(fromAccount.getBalance() - money);
toAccount.setBalance(toAccount.getBalance() + money);
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
TransferLog log = new TransferLog();
log.setOutAccount(fromAccount.getId());
log.setInAccount(toAccount.getId());
log.setMoney(money);
transferLogRepository.save(log);
}
}
这里具体说明一下。
首先将 AccountRepository 和 TransferLogRepository 作为依赖项,因为涉及到数据库操作,我们需要配置数据的查询和保存方式。这两个类分别是用来对应account和transfer_log两张表的。
再看以下@Transactional注解,这个注释在这里保证了整个转账操作的执行序列,如果其中任何一步发生了错误,那么整个过程将自动回滚,每步的操作都将撤回,以确保数据的一致性。
在进行转账操作时,需要查询出相关账户信息,若出现转账失败的情况,则通过throw new ServiceException(...) 抛出异常,会被上面@Transactional中的rollbackFor = ServiceException.class截获。并将异常信息记录到日志中。
在转账操作都执行完毕之后,我们会在transfer_log表中,增加一条记录,以便于我们后续的查询。
No.2也是我们在实际业务场景中常常遇到的需求:同时对多张表进行事务管理。接下来我们快速了解一下这个业务需求并提供代码示例。
示例2:订单以及订单详情记录
在实际的业务场景中,经常涉及到多张表之间的联动操作、数据交互,如下这辑种情况:
- 创建一张订单表 order_info,记录订单基础信息
- 创建一张订单详情表 detail_info,记录订单明细信息
- 在对应订单明细页面中,用户可以添加多条明细记录,每条记录都要持久化到detail_info表中并且记录到order_info表中。
为了高效地完成这一系列操作我们需要使用到 Springboot的事务管理机制来确保数据的一致性、隔离性和原子性,确保当任何一步操作失败时,被整个事务自动回滚。
代码实现中,我们通过新增一个 order_info 的记录,并将其生成的主键作为 detail_info 中的外键关联起来,我们来看以下两个 Repository 的代码。
order_info 表:
CREATE TABLE `order_info` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '自动编号',
`price` DECIMAL(10,2) NOT NULL COMMENT '订单总价',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
detail_info 表:
CREATE TABLE `detail_info` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '自动编号',
`order_id` INT(11) NOT NULL COMMENT '对应订单编号',
`detail` CHAR(20) NOT NULL COMMENT '订单详情',
PRIMARY KEY (`id`),
KEY `fk_order_id` (`order_id`),
CONSTRAINT `fk_order_id` FOREIGN KEY (`order_id`) REFERENCES `order_info` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
上面的代码中,detail_info的 order_id 字段和order_info表的主键相连。主键[id]与外键[order_id]形成了主外键约束。
使用Springboot事务管理机制,我们只需要通过@Transactional注释,简单地对方法进行标记,就可以确保整个过程的原子性。
我们看一下订单处理服务类:
@Service
public class OrderService {
@Autowired
OrderRepository orderRepository;
@Autowired
DetailRepository detailRepository;
@Transactional(rollbackFor = ServiceException.class)
public void saveOrder(OrderInfo order, List<DetailInfo> detailList) throws ServiceException {
order = orderRepository.save(order);
int orderId = order.getId();
Iterator ftr = detailList.iterator();
while(ftr.hasNext()) {
DetailInfo detail = (DetailInfo)ftr.next();
detail.setOrderId(orderId);
detailRepository.save(detail);
}
}
}
这里,我们注入 OrderRepository 和 DetailRepository ,并且使用 @Transactional 这个注释,对整个方法进行事务支持。在操作的过程中,保证业务的完整性,同时也消除了中间出现的任何问题。
至此,我们已经对 “详解Springboot事务管理”的内容进行了完整的讲解。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解Springboot事务管理 - Python技术站