Spring事务简介
- 事务作用:在数据层保障一系列的数据库操作同成功同失败
- Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
经典案例:银行转账
步骤:
① 在业务层接口上添加Spring事务管理
public interface AccountService {
/**
* 转账操作
*
* @param out 转出方
* @param in 转入方
* @param money 金额
*/
// 开启事务标准写法,写在接口上,而不是实现类上
@Transactional
public void transfer(String out, String in, Double money);
}
- Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合度,注解式事务可以添加到业务方法表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务
② 设置事务管理器
(jdbcConfig.java)
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
- 事务管理要根据实现技术进行选择,MyBatis框架使用的是JDBC事务
③开启注解式事务驱动
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({jdbcConfig.class, MyBatisConfig.class})
//表示用注解式开启事务
@EnableTransactionManagement
public class SpringConfig {
}
源码:
1.导入坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.21</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.19</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.19</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
</dependencies>
2.JDBC配置文件的书写
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=12345678
3.Spring配置类
jdbcConfig.java
package com.itheima.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;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
MyBatisConfig.java
package com.itheima.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.itheima.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
SpringConfig.java
package com.itheima.config;
import org.springframework.context.annotation.*;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({jdbcConfig.class, MyBatisConfig.class})
//表示用注解式开启事务
@EnableTransactionManagement
public class SpringConfig {
}
4.数据层
AccountDao.java
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);
}
5.实体类
package com.itheima.domain;
public class Account {
private Integer id;
private String name;
private Double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
6.业务层
AccountService.java
package com.itheima.service;
import org.springframework.transaction.annotation.Transactional;
public interface AccountService {
/**
* 转账操作
*
* @param out 转出方
* @param in 转入方
* @param money 金额
*/
// 开启事务标准写法,写在接口上,而不是实现类上
// @Transactional(readOnly = true,timeout = -1)
// 只有运行时异常与ERROR类型会自动回滚,其他异常不会
@Transactional
public void transfer(String out, String in, Double money);
}
AccountServiceImpl.java
package com.itheima.service.impl;
import com.itheima.dao.AccountDao;
import com.itheima.domain.Account;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String out, String in, Double money) {
accountDao.outMoney(out, money);
//模拟一个异常出现
// int i = 1 / 0;
accountDao.inMoney(in, money);
}
}
7.测试类
AccountServiceTest.java
package com.itheima.service;
import com.itheima.config.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void textTransfer() {
accountService.transfer("Tom", "Jerry", 100D);
}
}
事务相关配置
属性 | 作用 | 示例 |
---|---|---|
readOnly | 设置是否为只读事务 | readOnly=true 只读事务 |
timeout | 设置事务超时时间 | timeout = -1 (永不超时) |
rollbackFor | 设置事务回滚异常(class) | rellbackFor={NullPointException.class} |
rolbackForClassName | 设置事务回滚异常(String) | 同上格式为字符串 |
noRollbackFor | 设置事务不会滚异常(class) | noRollbackFor={NullPointException.class} |
noRollbackForClassName | 设置事务不回滚异常(String) | 同上格式为字符串 |
propagation | 设置事务传播行为 | ...... . |
- 事务传播行为:事务协调员对事务管理员所携带的事务的态度
事务传播行为
- 开启T是指原本有事务就开启它,如果没有事务就写作无,事务协调员作响应操作
传播属性 | 事务管理员 | 事务协调员 |
---|---|---|
REQUIRED(默认) | 开启T/无 | 加入T/新建T2 |
REQUIRES_NEW | 开启T/无 | 新建T2/新建T2 |
SUPPORTS | 开启T/无 | 加入T/无 |
NOT_SUPPORTED | 开启T/无 | 无/无 |
MANATORY | 开启T/无 | 加入T/ERROR |
NEVER | 开启T/无 | ERROR/无 |
NESTED | 设置savePoint,一旦事务回滚事务将回滚到SavePoint出 | 交由客户响应提交/回滚 |
案例修改:追加日志
1.数据表
/*
Navicat Premium Data Transfer
Source Server : sa
Source Server Type : MySQL
Source Server Version : 80028
Source Host : localhost:3306
Source Schema : test
Target Server Type : MySQL
Target Server Version : 80028
File Encoding : 65001
Date: 24/07/2022 16:57:58
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tb_log
-- ----------------------------
DROP TABLE IF EXISTS `tb_log`;
CREATE TABLE `tb_log` (
`id` int NOT NULL AUTO_INCREMENT,
`info` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`createDate` datetime NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of tb_log
-- ----------------------------
INSERT INTO `tb_log` VALUES (1, '转账操作由Tom到Jerry,金额:100.0', '2022-07-24 20:55:49');
SET FOREIGN_KEY_CHECKS = 1;
2.数据层
@Repository
public interface LogDao {
@Insert("insert into tb_log (info,createDate) values(#{info},now()) ")
void log(String info);
}
3.业务层
public interface LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
void log(String out, String in, Double money);
}
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao;
@Override
public void log(String out, String in, Double money) {
logDao.log("转账操作由" + out + "到" + in + ",金额:" + money);
}
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private LogService logService;
@Override
public void transfer(String out, String in, Double money) {
try {
accountDao.outMoney(out, money);
int i = 1 / 0;
accountDao.inMoney(in, money);
} finally {
//此时log事务和前面事务相同,所以添加失败
logService.log(out, in, money);
}
}
}