Spring 事务快速入门详解
Posted 流楚丶格念
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 事务快速入门详解相关的知识,希望对你有一定的参考价值。
文章目录
Spring事务简介
什么是事务?
事务指数据库中多个操作合并在一起形成的操作序列
事务特征(ACID)
事务有四个特性:
-
原子性(Atomicity)指事务是一个不可分割的整体,其中的操作要么全执行或全不执行
-
一致性(Consistency)事务前后数据的完整性必须保持一致
-
隔离性(Isolation)事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
-
持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
Spring事务的作用
- 事务作用:在数据层保障一系列的数据库操作同成功同失败
- Spring事务作用:在数据层或**业务层**保障一系列的数据库操作同成功同失败
当数据库操作序列中个别操作失败时,提供一种方式使数据库状态恢复到正常状态(A),保障数据库即使在异常状态下仍能保持数据一致性(C)(要么操作前状态,要么操作后状态)。
当出现并发访问数据库时,在多个访问间进行相互隔离,防止并发访问操作结果互相干扰(I),从而最终保持数据的持久性(D)。
Spring事务核心接口
在Spring中,数据层的Mybatis使用的是DataSourceTransactionManager实现类来进行事务控制,它实现的是Spring中的事务管理的核心接口PlatformTransactionManager
Spring事务角色
你可以看完案例再来看这个事务角色的概念
- 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
- 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
例如下面事务流程案例:业务层的方法就充当事务管理员,负责开启事务,调用的两个DAO方法加入事务,是业务协调员
Spring事务实战分析
需求和分析
需求:实现任意两个账户间转账操作
例如:A账户像B账户转账,那么就是A账户减钱,B账户加钱
分析:
- 数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)
- 业务层提供转账操作(transfer),调用减钱与加钱的操作
- 提供2个账号和操作金额执行转账操作
- 基于Spring整合MyBatis环境搭建上述操作
结果分析:
- 程序正常执行时,账户金额A减B加,没有问题
- 程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败
代码实现
测试环境准备
项目结构如下:
创建一个数据库:transaction_test,建一个简单的测试表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tb_account
-- ----------------------------
DROP TABLE IF EXISTS `tb_account`;
CREATE TABLE `tb_account` (
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
`money` DOUBLE NULL DEFAULT NULL
) ENGINE = MyISAM CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of tb_account
-- ----------------------------
INSERT INTO `tb_account` VALUES ('Tong', 1000);
INSERT INTO `tb_account` VALUES ('Meng', 200);
SET FOREIGN_KEY_CHECKS = 1;
Spring整合Mybatis相关代码(依赖、JdbcConfig、MybatisConfig、SpringConfig)
相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yyl</groupId>
<artifactId>spring_24_case_transfer</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
</project>
jdbc配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/transaction_test?useSSL=false
jdbc.username=root
jdbc.password=root
配置类
// JdbcConfig
package com.yyl.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
public class JdbcConfig
@Value("$jdbc.driver")
private String driver;
@Value("$jdbc.url")
private String url;
@Value("$jdbc.username")
private String userName;
@Value("$jdbc.password")
private String password;
@Bean
public DataSource dataSource()
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
package com.yyl.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MybatisConfig
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource)
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.yyl.domain");
ssfb.setDataSource(dataSource);
return ssfb;
@Bean
public MapperScannerConfigurer mapperScannerConfigurer()
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.yyl.dao");
return msc;
package com.yyl.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ComponentScan("com.yyl")
@PropertySource("classpath:jdbc.properties")
@Import(JdbcConfig.class,MybatisConfig.class)
public class SpringConfig
这里我们先不开启事务
编写DAO层
public interface AccountDao
@Update("update tb_account set money = money + #money where name = #name")
void inMoney(@Param("name") String name, @Param("money") Double money);
@Update("update tb_account set money = money - #money where name = #name")
void outMoney(@Param("name") String name, @Param("money") Double money);
public interface AccountService
/**
* 转账操作
* @param out 传出方
* @param in 转入方
* @param money 金额
*/
public void transfer(String out,String in ,Double money) ;
@Service
public class AccountServiceImpl implements AccountService
@Autowired
private AccountDao accountDao;
public void transfer(String out,String in ,Double money)
accountDao.outMoney(out,money);
int i = 1/0;
accountDao.inMoney(in,money);
此时运行结果,Tong的钱少了200,Meng的钱没加,因为执行到除0操作,程序挂掉了
上面的实现应用到现实中肯定是不行的,那么下面我们引入事务再来改善一下代码
使用Spring事务管理修改案例
在业务层接口上添加Spring事务管理
public interface AccountService
//配置当前接口方法具有事务
@Transactional
public void transfer(String out,String in ,Double money) ;
注意事项
- Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合
- 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务
设置事务管理器(将事务管理器添加到IOC容器中)
说明:可以在JdbcConfig中配置事务管理器
//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource)
DataSourceTransactionManager dtm = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
注意事项
- 事务管理器要根据实现技术进行选择
- MyBatis框架使用的是JDBC事务
开启注解式事务驱动
@Configuration
@ComponentScan("com.yyl")
@PropertySource("classpath:jdbc.properties")
@Import(JdbcConfig.class,MybatisConfig.class)
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig
运行测试类,查看结果
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest
@Autowired
private AccountService accountService;
@Test
public void testTransfer() throws IOException
accountService.transfer("Tong","Meng",100D);
运行结果如下:即使报错也不会发生数据库误操作,因为进行了事务回滚操作
去掉除0操作,再运行结果如下:一个减钱,一个价钱,没有问题
还有一个问题
问题1:Spring提供的事务管理是数据层的事务还是业务层的事务?
首先那肯定是业务层。Spring事务为业务逻辑进行事务管理,保证业务逻辑上数据的原子性。
扩展:事务分类
事务得根据项目性质来细分:事务可以设置到三个层面(dao层、service层和web层)。
第一:web层事务,这一般是针对那些安全性要求较高的系统来说的。例如电子商务网站。粒度小,一般系统用不着这么细。
第二:service层事务,这是一常见的事务划分, 将事务设置在业务逻辑上,只要业务逻辑出错或异常就事务回滚。粒度较小,一般推荐这种方式。
第三:数据持久层数据务,也就是常说的数据库事务。这种事务在安全性方面要求低。就是给一个简单的增删改之类的操作增加事务操作。粒度大
问题2:为什么只用服务层的事务?
给Service层配置事务,因为一个Service层方法操作可以关联到多个DAO的操作。在Service层执行这些Dao操作,多DAO操作有失败全部回滚,成功则全部提交。
事务分为业务事务和系统事务,业务事务也就是业务逻辑上操作的一致性,系统事务自然就是指真正的数据库事务,
Dao层是数据访问层,是不应该包含业务逻辑的,这就是和Service层的不同;
Service层就是业务逻辑层,事务的管理就是为Service层上的保证。
以上是关于Spring 事务快速入门详解的主要内容,如果未能解决你的问题,请参考以下文章
Spring Cloud Alibaba Seata 分布式事务使用快速入门,Nacos做Seata的注册中心和配置中心
Spring Cloud Alibaba Seata 分布式事务使用快速入门,Nacos做Seata的注册中心和配置中心
Spring Cloud Alibaba Seata 分布式事务使用快速入门,Nacos做Seata的注册中心和配置中心