目录
一、事务概述
1、什么是事务
2、事务的四个特性(ACID)
二、搭建事务操作环境
1、dao、service 两层结构
2、示例
3、模拟异常(事务场景引入)
三、Spring 事务管理
1、事务管理介绍
2、声明式事务管理——注解方式
3、声明式事务管理——注解的参数配置
4、声明式事务管理——XML方式
5、声明式事务管理——完全注解开发(详细)
(1)事务是数据库操作最基本单元:逻辑上对于一组操作,要么都成功,如果有一个失败所有操作都失败。
(1)原子性
(2)一致性
(3)隔离性
(4)持久性
通过模拟银行转账这一例子,来演示在 Spring5 中如何进行事务操作
JavaEE 中有三层结构,分别是:web、service、dao。其中数据库操作由 dao 实现,业务逻辑由 service 实现,因此我们主要关注这两层结构。
收入和支出的方法都执行后,在构成转账的方法,才是一个完整操作。
(1)创建数据库、表,添加记录
(2)创建 UserDao 和 UserService,并完成注入
(3)在 dao 中创建两个方法:收入和支出
package com.demo.dao.impl;
import com.demo.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int income(String username, BigDecimal dif) {
String sql = "update \"BankUser\" set balance=balance+? where username=?";
return jdbcTemplate.update(sql, dif, username);
}
@Override
public int expense(String username, BigDecimal dif) {
String sql = "update \"BankUser\" set balance=balance-? where username=?";
return jdbcTemplate.update(sql, dif, username);
}
}
(4)在 service 中创建方法:转账
package com.demo.service.impl;
import com.demo.dao.UserDao;
import com.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void remittance(String name1, String name2, BigDecimal dif) {
// name1 减少 dif
userDao.expense(name1, dif);
// name2 增加 dif
userDao.income(name2, dif);
}
}
(5)测试代码
import com.demo.pojo.User;
import com.demo.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.math.BigDecimal;
public class remittanceTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("JdbcTemplate.xml");
UserService userService = context.getBean("userServiceImpl", UserService.class);
userService.remittance("wyt", "gyt", new BigDecimal(25));
}
}
(6)运行结果
假设在转出一方转出后,出现了异常,那么按照事务的特性,就应当将操作回滚。
而在上面的代码中,就会使得转出一方出了钱,而接收一方没有收到钱。
(1)由于异常一般出现在业务逻辑处理,所以一般将事务管理放在 service 层
(2)Spring 事务管理有两种方式:
(3)声明式事务管理有两种实现方式:
(4)在 Spring 进行声明式事务管理时,需要使用 AOP 原理
(5)Spring 事务管理 API
(1)在 spring 配置文件中配置事务管理器
(2)在 spring 配置文件中开启事务注解
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
(3)为 service 类或 service 类方法添加事务注解 @Transactional
(4)输出结果
在声明式的事务处理中,要配置一个切面,其中就用到了 propagation 等多个参数。需要明确的是,他们本质上都是对类方法被调用时的一种描述。
(1)propagation 事务传播行为
propagation 表示这些方法怎么使用事务,是用还是不用。其常用属性有 3 种:
@Transactional(propagation = Propagation.REQUIRED)
(2)isolation 事务隔离级别
多事务的并发操作容易引发读问题,事务的隔离级别会产生不同的读问题:脏读、不可重复读、幻读。
(2-1)名词解释
(2-2)设置事务隔离级别解决读问题
(2-3)示例
@Transactional(isolation = Isolation.SERIALIZABLE)
(3)timeout 超时时间
(4)readOnly 是否只读
(5)rollbackFor 回滚
(6)noRollbackFor 不回滚
(1)配置事务管理器
与注解方式配置事务管理器一致。
(2)配置通知
在 AOP 中讲过,通知就是增强的逻辑部分,加上源代码部分,构成动态代理。
(3)配置切入点和切面
把事务加到哪些类、哪些方法。作用与注解方式的 @Transactional 一致。
前面的注解方式中,还是有一部分需要编写配置文件。
(1)创建数据库配置文件
prop.driverClassName = org.postgresql.Driver
prop.url = jdbc:postgresql://localhost:5432/MyDatabase
prop.username = postgres
prop.password = 123456
(2)创建 jdbc 配置类,替代 xml 配置文件中的数据库连接池
package com.demo.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.sql.Connection;
@PropertySource("classpath:dataSource.properties")
public class JdbcConfig {
@Value("${prop.driverClassName}")
private String driver;
@Value("${prop.url}")
private String url;
@Value("${prop.username}")
private String username;
@Value("${prop.password}")
private String password;
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
// 创建 jdbcTemplate 对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
// 到 IOC 容器中,根据类型找到 dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入 dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建 connection 对象
@Bean
public Connection getConnection(DataSource dataSource) {
Connection conn = null;
try {
conn = dataSource.getConnection();
} catch (Exception e) {
throw new RuntimeException(e);
}
return conn;
}
}
(3)创建 transaction 配置类,替代 xml 中的事务管理
package com.demo.config;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@EnableTransactionManagement // 开启事务
public class TransactionConfig {
// 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
(4)创建 Spring 配置类
package com.demo.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan({"com.demo"})
@Import({JdbcConfig.class})
public class SpringConfig {
}
(5)测试代码
import com.demo.config.transactionConfig;
import com.demo.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.math.BigDecimal;
public class remittanceTest {
@Test
public void completeAnnotation() {
ApplicationContext context = new AnnotationConfigApplicationContext(transactionConfig.class);
UserService userService = context.getBean("userServiceImpl", UserService.class);
userService.remittance("wyt", "gyt", new BigDecimal(25));
}
}
(6)运行结果
原本两个 user 的 balance 都是 50,现在转账了 25:
若给转账功能手动引入一个异常,观察事务管理是否起作用:
显然遇到异常成功回滚,没有破坏一致性。