前言
为了巩固所学的知识,作者尝试着开始发布一些学习笔记类的博客,方便日后回顾。当然,如果能帮到一些萌新进行新技术的学习那也是极好的。作者菜菜一枚,文章中如果有记录错误,欢迎读者朋友们批评指正。
(博客的参考源码可以在我主页的资源里找到,如果在学习的过程中有什么疑问欢迎大家在评论区向我提出)
六、Spring事务
1.Spring事务简介
一个数据库事务是一个被视为单一的工作单元的操作序列。这些操作应该要么完整地执行,要么完全不执行。事务管理是一个重要组成部分,RDBMS 面向企业应用程序,以确保数据完整性和一致性。事务的概念可以描述为具有以下四个关键属性说成是 ACID:
原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。 一致性:这表示数据库的引用完整性的一致性,表中唯一的主键等。 隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。 持久性:一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除。
一个真正的 RDBMS 数据库系统将为每个事务保证所有的四个属性。使用 SQL 发布到数据库中的事务的简单视图如下:
使用 begin transaction 命令开始事务。 使用 SQL 查询语句执行各种删除、更新或插入操作。 如果所有的操作都成功,则执行提交操作,否则回滚所有操作。
Spring 框架在不同的底层事务管理 APIs 的顶部提供了一个抽象层。Spring 的事务支持旨在通过添加事务能力到 POJOs 来提供给 EJB 事务一个选择方案。Spring 支持编程式和声明式事务管理。EJBs 需要一个应用程序服务器,但 Spring 事务管理可以在不需要应用程序服务器的情况下实现,实现接口及实现类如下:
2.入门案例–模拟银行间转账业务
- 案例需求以及实现思路
- 环境准备
- 创建数据库表tbl_account
建表查询语句示例
create database spring_db; use spring_db; drop table if exists tbl_account; create table tbl_account( id int primary key auto_increment, name varchar(20), money varchar(20) ); INSERT INTO tbl_account VALUES (1, 'Tom', 1000); INSERT INTO tbl_account VALUES (2, 'Jerry', 1000);
效果
- 在pom.xml文件中导入坐标
<dependencies> //spring框架 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> //druid数据源 <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> //mybatisa框架 <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> //spring操作jdbc <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> //spring整合mybatis <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> //spring整合junit单元测试 <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>
- 创建spring全局配置文件SpringConfig
@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) //开启注解式事务驱动 @EnableTransactionManagement public class SpringConfig { }
- 创建数据库信息文件jdbc.properties,填写数据库连接信息
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false jdbc.username=root jdbc.password=root
- 创建数据库连接配置类JdbcConfig
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; } //配置事务管理器,mybatis使用的是jdbc事务 @Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
- 创建mybatis创建SqlSessionFactory Bean 和 Mapper 代理开发映射对象配置文件MybatisConfig
public class MybatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){ SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean(); ssfb.setTypeAliasesPackage("org.example.domain"); ssfb.setDataSource(dataSource); return ssfb; } @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("org.example.dao"); return msc; } }
- 根据数据库表创建对应的实体类Account
public class Account{ //此处省略getter、setter和toString方法 private Integer id; private String name; private Double money; }
- 创建AccontDao接口,用注解的方式定义mapper代理开发业务接口,分别为加钱的操作方法 inmoney 和 减钱的操作 outmoney
public interface AccountDao { @Update("update tbl_account set money = money + #{money} where name = #{name}") void inMoney(@Param("name") String name, @Param("money") Double money); @Update("update tbl_account set money = money - #{money} where name = #{name}") void outMoney(@Param("name") String name, @Param("money") Double money); }
- 创建业务层接口AccountService,定义转账操作方法transfer
public interface AccountService { /** * 转账操作 * @param out 传出方 * @param in 转入方 * @param money 金额 */ //配置当前接口方法具有事务 @Transactional public void transfer(String out,String in ,Double money) ; }
- 创建业务层接口 AccountService 的实现类AccountServiceImpl,调用数据层功能的加钱和减钱方法 inmoney 和 outmoney
@Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; public void transfer(String out,String in ,Double money) { //减钱 accountDao.outMoney(out,money); //加钱 accountDao.inMoney(in,money); } }
- 编写测试类
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceTest { @Autowired private AccountService accountService; @Test public void testTransfer() throws IOException { //Tom 向 Jerry 转100元 accountService.transfer("Tom","Jerry",100D); } }
- 程序运行结果
- 文件结构参考
- 入门案例分析
- 入门案例完成了简单的模拟转账操作,此时我们在转账接口的实现类AccountServiceImpl减钱和加钱的操作中间添加一行模拟业务运行异常的代码,此时我们希望业务异常,终止操作,即数据库的账户余额不变。
- 添加了模拟异常的代码及运行结果
- 通过观察数据库中的表格数据我们发现异常前面的减钱操作运行了,而异常后面的加钱操作没有运行,导致整体业务功能失败。我们可以通过开启Spring事务来解决这个问题。
- 小结
- Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合
- 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务
- 事务管理器需要根据实现技术进行选择,mybatis使用的是jdbc事务
3.开启Spring事务的一般步骤
- @Transactional注解配置对应的接口方法具有事务
@Transactional public void transfer(String out,String in ,Double money) ; }
- 配置事务管理器,mybatis使用的是jdbc事务
public class JdbcConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
- @EnableTransactionManagement注解开启注解式事务驱动
@EnableTransactionManagement public class SpringConfig { }
此时在存在模拟异常的情况下再次运行模拟测试类,数据库中的数据不变,业务功能运行正常,Spring事务开启成功。