- 什么是事务">1:什么是事务
- 事务四个特性(ACID)">2:事务四个特性(ACID)
- 3:事务操作(搭建事务操作环境)
- 4:事务操作(场景引入)
- 5:注解式事务操作
- 事务参数(传播行为)-Spring声明式事务管理">6:事务参数(传播行为)-Spring声明式事务管理
1:什么是事务
(1):事务是数据库操作的最基本单元,是在逻辑上的一组操作,要么都成功,要么都失败,如果有一个失败所有操作都失败
(2):经典场景 银行转账
张三 转账给 李四 100元
张三 再转账的时候少100块钱
李四 转账后多100块钱
当转账失败,张三不会少100块钱,李四不会多100块钱
2:事务四个特性(ACID)
(1):原子性( Atomicity )
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
(2):一致性(Consistency )
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
(3):隔离性( Isolation )
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
(4):持久性( Durability )
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
3:事务操作(搭建事务操作环境)
(1):模拟银行转账
1:创建数据库表,添加记录
2:创建service和dao接口,以及对象创建和注入关系
(1):service注入dao接口,在dao中注入jdbctemplate接口,在jdbctemplate注入datasoure
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
">
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.junjay.spring5"></context:component-scan>
<!-- 直接通过druidjar配置数据库链接 -->
<bean id="dataSource"
class="com.alibaba.druid.pool.DruidDataSource">
<!-- 直接配置 *********************************************************** -->
<property name="driverClassName"
value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 创建JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 用set注入dataSource数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
package com.junjay.spring5.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.junjay.spring5.dao.UserDao;
@Service
public class UserService {
@Autowired
private UserDao userDao;
/**
* @param name 转账人名称
* @param money 转账金额
* @param receiveName 接收转账人名称
*/
public void transfer(String name,int money,String receiveName) {
System.err.println("转账前--------------");
userDao.reduceMoney(receiveName,money);
System.err.println("转账中--------------");
userDao.addMoney(name,money);
System.err.println("转账成功--------------");
}
}
package com.junjay.spring5.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import com.junjay.spring5.entity.User;
@Repository
public class UserDaoImpl implements UserDao {
// 注入jdbctemplate
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addMoney(String name, int money) {
// 多钱方法
String querySql=" SELECT * FROM t_account where USERNAME =? ";
User user = jdbcTemplate.queryForObject(querySql, new BeanPropertyRowMapper<User>(User.class), name);
System.out.println("转账前,"+name+"账户中有:"+user.getMoney());
String sql=" UPDATE test.T_ACCOUNT SET MONEY=MONEY+? WHERE (USERNAME=?) ";
int i = jdbcTemplate.update(sql, money,name);
System.out.println("影响条数:"+i);
User user1 = jdbcTemplate.queryForObject(querySql, new BeanPropertyRowMapper<User>(User.class), name);
System.out.println("转账后,"+name+"账户中有:"+user1.getMoney()+",本次转账金额:"+money);
}
@Override
public void reduceMoney(String name, int money) {
// 少钱方法
String querySql=" SELECT * FROM t_account where USERNAME =? ";
User user = jdbcTemplate.queryForObject(querySql, new BeanPropertyRowMapper<User>(User.class), name);
System.out.println("转账前,"+name+"账户中有:"+user.getMoney());
String sql=" UPDATE test.T_ACCOUNT SET MONEY=MONEY-? WHERE (USERNAME=?) ";
int i = jdbcTemplate.update(sql, money,name);
System.out.println("影响条数:"+i);
User user1 = jdbcTemplate.queryForObject(querySql, new BeanPropertyRowMapper<User>(User.class), name);
System.out.println("转账后,"+name+"账户中有:"+user1.getMoney()+",本次转账金额:"+money);
}
}
package com.junjay.spring5.entity;
public class User {
private String id;
private String userName;
private String money;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getMoney() {
return money;
}
public void setMoney(String money) {
this.money = money;
}
@Override
public String toString() {
return "User [id=" + id + ", userName=" + userName + ", money=" + money + "]";
}
public User(String id, String userName, String money) {
super();
this.id = id;
this.userName = userName;
this.money = money;
}
public User() {
super();
}
}
测试:
@Test
public void testJdabTransfer() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService",UserService.class);
// 参数1:转账人 参数2:转账金额 参数3:接收转账人
userService.transfer("张三",100,"李四");
}
4:事务操作(场景引入)
(2):转账失败,利用事务解决
在转账(代码执行)过程中出现异常,导致数据错误,利用事务进行解决
3:上面代码,如果正常执行是没有问题,但是如果代码执行过程中出现异常时改怎么处理。
/**
* @param name 转账人名称
* @param money 转账金额
* @param receiveName 接收转账人名称
*/
public void transfer(String name,int money,String receiveName) {
System.err.println("转账前--------------");
userDao.reduceMoney(receiveName,money);
System.err.println("转账中--------------");
// 其他代码不变;模拟异常 在转账中突然停电
System.err.println("转账中系统停电--------------");
int a = 10/0;
userDao.addMoney(name,money);
System.err.println("转账成功--------------");
}
执行测试方法:
数据库中,李四账户金额已经减少100元,但张三账户并没有增加100元,钱丢了100,问题出现,如何解决问题?
解决上面问题,使用事务进行解决;
(1):事务操作基本流程
package com.junjay.spring5.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.junjay.spring5.dao.UserDao;
@Service
public class UserService {
@Autowired
private UserDao userDao;
/**
* @param name 转账人名称
* @param money 转账金额
* @param receiveName 接收转账人名称
*/
public void transfer(String name, int money, String receiveName) {
// 第一步:开启事务操作
try {
// 第二步:进行业务操作
System.err.println("转账前--------------");
userDao.reduceMoney(receiveName, money);
System.err.println("转账中--------------");
// 模拟异常 在转账中突然停电
System.err.println("转账中系统停电--------------");
int a = 10 / 0;
userDao.addMoney(name, money);
System.err.println("转账成功--------------");
// 第三步:没有异常则提交事务(数据库修改)
} catch (Exception e) {
// try{} 内为具体的业务操作,catch{} 内则是在业务操作中如果有异常或错误就会进入
// Exception e : Exception 异常类型
// 第四步:出现异常,则事务回滚(回复之前的数据操作)
}
}
}
5:注解式事务操作
1:事务一般添加在javaEE三层结构中的 Service层(业务逻辑层)
2:在Spring进行事务管理操作,有俩种管理方式
(1):编程式事务,在代码中硬编码。(不推荐使用)
(2):声明式事务,在配置文件中配置(推荐使用)
3:声明式事务又分为两种:
a、基于XML的声明式事务
b、基于注解的声明式事务(一般使用注解,因为简单)
4:在Spring进行声明式事务管理,底层使用AOP原理
5:Spring事务管理Api
(1):提供接口,代表事务管理器,接口针对不同框架提供不同实现类
1:事务管理(注解式声明管理)
(1):在spring中配置文件中配置事务管理器
<!-- 创建事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
(2):在spring配置文件中,开启事务注解扫描
1:在spring配置文件中引入名称空间tx
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
<!-- 开启事务注解,开启哪个事务注解 -->
<!-- 开启事务注解,开启哪个事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager" />
(3):在service类中添加事务注解@Transactional,可加类上面也可以加在方法上面
1:@Transactional注解添加在类上,表示里面所有方法都添加事务
2:@Transactional注解添加在方法上,表示只有此方法添加了事务
3:@Transactional注解失效
(4):添加事务注解后测试
添加queryUser查询回滚后的数据,因代码异常后就不在执行,所以在junit测试中捕获异常,当出现异常时候查询”张三”账户看是否进行回滚,既注解是否生效
// UserService
public void queryUser(String name) {
userDao.queryUser(name);
}
// UserDaoImpl
@Override
public void queryUser(String name) {
String querySql=" SELECT * FROM t_account where USERNAME =? ";
User user = jdbcTemplate.queryForObject(querySql, new BeanPropertyRowMapper<User>(User.class), name);
System.out.println("转账回滚,"+name+"账户中有:"+user.getMoney());
}
int a=10/0; 不提示异常,异常被放在junit中
6:事务参数(传播行为)-Spring声明式事务管理
1:在service类上面添加@Transactional注解,在这个注解里面可以配置事务相关参数
参数含义:
1:@Transactional注解中常用参数说明
参 数 名 称 | 功 能 描 述 |
---|---|
readOnly | 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) |
rollbackFor | 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如: 指定单一异常类:@Transactional(rollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
rollbackForClassName | 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如: 指定单一异常类名称:@Transactional(rollbackForClassName=”RuntimeException”) 指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”}) |
noRollbackFor | 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如: 指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
noRollbackForClassName | 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如: 指定单一异常类名称:@Transactional(noRollbackForClassName=”RuntimeException”) 指定多个异常类名称: @Transactional(noRollbackForClassName={“RuntimeException”,”Exception”}) |
propagation | 该属性用于设置事务的传播行为,多事务方法直接进行调用,在这个过程中事务是如何进行管理;事务方法是指对数据库表数据进行变化的操作;具体取值可参考表6-7。 例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation | 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置;不考虑隔离性会产生出很多问题,如脏读,不可重复读,幻读 |
timeout | 该属性用于设置事务的超时秒数,默认值为-1表示永不超时 |
2:propagation事务传播行为
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
3:isolation事务隔离级别
(1):事务有特性称为隔离性,多事务操作之间不会产生影响。不考虑隔离性会产生很多问题
(2):三个读取问题:脏读,不可重复读,幻读
脏读:一个未提交事务读取到另一个未提交的数据
不可重复读:一个未提交事务读取到另一个已提交修改的数据
幻读:一个未提交事务读取到另一个已添加的数据
(3)事务4种隔离级别分析,数据库四种事务情况
事务隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
---|---|---|---|---|
读未提交(Read uncommitted) | 是 | 是 | 是 | ①定义:就是一个事务读取到其他事务未提交的数据,是级别最低的隔离机制。 ②缺点:会产生脏读、不可重复读、幻读。 ④解决方案:采用更高级的隔离机制,如提交读。 |
不可重复读(Read committed) | 否 | 是 | 是 | ①定义:就是一个事务读取到其他事务提交后的数据。Oracle默认隔离级别。 ②缺点:会产生不可重复读、幻读。 ④解决方案:采用更高级的隔离机制,如可重复读。 |
可重复读(Repeatable read) | 否 | 否 | 是 | ①定义:就是一个事务对同一份数据读取到的相同,不在乎其他事务对数据的修改。MySQL默认的隔离级别。 ②缺点:会产生幻读。 ④解决方案:采用更高级的隔离机制,序列化。 |
序列化(Serializable) | 否 | 否 | 否 | ①定义:事务串行化执行,隔离级别最高,牺牲了系统的并发性。 ②缺点:可以解决并发事务的所有问题。但是效率地下,消耗数据库性能,一般不使用。 |
4:timeout事务超时时间
(1):事务需要在一定时间内进行提交,如果不提交则进行回滚
(2):默认值为-1,为不超时;如果设置时间则是以秒为单位
5:readOnly是否只读
(1):读:查询操作;写:添加修改删除操作
(2):readOnly默认值为false(可以查询,也可以增删改)
(3):readOnly默认值为true(只能进行查询,不可增删改)
6:rollbackFor回滚
7:noRollbackFor不回滚
(1):设置出现那些异常不进行事务回滚