五、事务概念
1、什么是事务
- 事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
- 典型场景:银行转账(A钱少100,B多100, 但是A少100时断电,则需要使用事务机制!两者都失败)
2、事物的四个特性(ACID)
- 原子性;要么都成功。要么都失败。
- 一致性:操作之前和操作之后总量不变:A,B个有100;A转100给B;A0,B200;结果总量不变。
- 隔离性:多事务操作之间不会相互影响。
- 持久性:事务提交之后,表中数据才发生变化。
3、事务操作(搭建事务操作环境)
3.1 不开启事务,在转账业务出现问题
(1)创建数据表,添加记录
(2)创建Service,搭建Dao,完成对象创建和注入关系。
Service 注入 Dao,Dao注入JdbcTemplate,JdbcTemplate注入DataSource
@Service
public class UserService {
// 注入dao
@Autowired
private UserDao userDao;
}
@Repository
public class UserDaoImpl implements UserDao{
// 注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
(3)在dao创建两个方法:多钱和少钱的方法,在service创建方法(转账方法)
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addMoney() {
String sql = "update account set money=money+? wherer username=?";
jdbcTemplate.update(sql, 100, "mary");
}
@Override
public void reduceMoney() {
String sql = "update account set money=money-? wherer username=?";
jdbcTemplate.update(sql, 100, "lucy");
}
}
@Service
public class UserService {
// 注入dao
@Autowired
private UserDao userDao;
// 转账方法
public void accountMoney(){
userDao.reduceMoney();
userDao.addMoney();
}
}
(4)测试类
@Test
public void testAccount(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}
(5)上面代码,如果正常秩序没有问题的,但是如果代码执行过程中出现异常,有问题
public void accountMoney(){
userDao.reduceMoney();
// 模拟异常
int i = 10/0;
userDao.addMoney();
}
3.2 问题处理解决
- 第一步 开启事务
- 第二步 进行业务操作
- 第三步 没有发生异常,事务提交
- 第四步 出现异常,事件回滚
4、事务操作(Spring事务管理介绍)
(1)事务添加到javaEE三层结构里面Service层(业务逻辑层)
(2)Spring进行事务管理操作 两类
- 编程式事务管理:按照3.2解决:第一步到第四步一步步执行
- 声明式事务管理(使用):在开发一般使用声明式管理,通过配置/注解的方式完成。
(3)声明式事务管理
- 基于注解方式(使用)
- 基于xml配置文件方式
(4)在Spring进行声明式事务管理,底层使用AOP原理
(5)Spring事务管理API
- 提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
5、事务操作(注解声明式事务管理)
(1)在Spring配置文件,配置事务管理器,开启事务注解
-
在Spring配置文件引入名称空间 xmlns:tx....
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd "> <beans> <!--数据源的配置--> ...... <!--创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!--开启事务注解--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> </beans>
(2)在Service类上面(获取service类里面方法上面)添加事务注解
-
@Transactional 这个注解添加到类上面,也可以添加方法上面
-
如果把这个注解添加到类上面,这个类里面所有的方法都添加事务(一般使用)
-
如果把这个注解添加到方法上面,为这个方法添加事务
@Service @Transactional public class UserService { }
6、事务操作(声明式事务管理参数配置)
(1)在service类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数,参数如下:
6.1 propagation:事务传播行为**
传播行为:多事务方法之间进行调用,这个过程中事务是如何进行管理的
事务的7中传播行为:懂前俩个,后面的了解,默认的传播行为是REQUIRED
@Service
@Transactional(propagation = Propagation.REQUIRED)
当没有事务调用由事务方法时,有事务的方法执行自身的事务。
当有事务的调用其他方法,其他方法会加入到该事务方法中,无论是事务方法,还是调用方法出现异常,两者都要进行回滚。
7中事务传播属性:
6.2 ioslation:事务隔离级别**
解决事务间的隔离性(并发操作中产生的问题),而出现的隔离级别的概念
事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
**(1)三个读问题:**脏读、不可重复读、虚(幻)读
1)脏读:
一个未提交事务读取到另一个未提交事务的数据。(读取未提交数据)
案例截取:https://cloud.tencent.com/developer/article/1450773
2)不可以重复读:
一个未提交事务读取到另一提交事务中修改的数据 (数据内容不一致)
**例子:**事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统不可以读取到重复的数据,成为不可重复读。
https://cloud.tencent.com/developer/article/1450773
3)虚读:
一个未提交事务读取到另一个提交事务添加数据(数据总量不一致)
事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。
https://cloud.tencent.com/developer/article/1450773
不可重复读和幻读到底有什么区别呢?
(1) 不可重复读是读取了其他事务更改的数据,针对update操作
解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。
(2) 幻读是读取了其他事务新增的数据,针对insert和delete操作
解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。
这时候再理解事务隔离级别就简单多了呢。
(2)解决三个读问题:
通过设置事务隔离级别,解决读问题
(无:表示可以解决问题-网络截图)
// 添加事务注解
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
// mysql默认的隔离级别为:REPEATABLE_READ
6.3 timeout:超时时间
- 事务需要在一定时间内进行提交,如果不提交进行回滚
- 默认值:-1(默认不超时),且可设置时间以秒单位进行计算
@Transactional(timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
6.4 readOnly:是否只读
- 读:查询操作,写:添加修改删除操作
- readOnly默认值false,表示可以查询,可以添加修改删除操作,设置readOnly值为true后,只能查询
@Transactional(readOnly = true,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
6.5 rollbackFor:回滚
- 设置出现哪些异常进行事务回滚
6.6 noRollbackFor:不回滚
- 设置出现哪些异常不进行事务回滚
7、事务操作(XML声明式事务管理 了解+)
(1)在Spring配置文件中进行配置
-
第一步 配置事务管理器
<!--开启组件扫描--> <!--Jdbc Template对象配置--> <!--1. 创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean>
-
第二步 配置通知
<!--2. 配置通知--> <tx:advice id="txadvice"> <!--配置事务参数--> <tx:attributes> <!--指定那种规则的方法上面添加事务 accountMoney为方法名, 也可以写成account*--> <tx:method name="accountMoney" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
-
第三步 配置切入点和切面
<!--3. 配置切入点和切面--> <aop:config> <!--配置切入点--> <aop:pointcut id="pt" expression="execution(* cn.spring5.Aop.Service.UserService.*(..))"/> <!--配置切面--> <aop:advisor advice-ref="txadvice" pointcut-ref="pt"></aop:advisor> </aop:config>
8、事务操作(完全注解声明式事务管理)
(1)创建配置类,使用配置类替代xml配置文件
@Configuration // 配置类
@ComponentScan(basePackages = "cn.spring5.packageName") // 组件扫描
@EnableTransactionManagement // 开启事务
public class TxConfig {
// 创建数据库连接池
@Bean //在配置类中创建Bean实例的注释@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/spring5");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
//创建JdbcTemplate对象
// 形参DataSource dataSource:因为getDruidDataSource()方法在IOC容器中已经创建了dataSource对象,所以形参这边是根据类型注入dataSource属性。
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入DataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
(2)测试类
@Test
public void testAccountConfig(){
ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}