一、Spring 学习
5、事务
事务(Transaction)是数据库区别于文件系统的重要特性之一。目前国际认可的数据库设计原则是ACID特性,用以保证数据库事务的正确执行。Mysql的innodb引擎中的事务就完全符合ACID特性。
事务可以这么理解,“有福同享,有难同当”。即完成一项操作,所有在其操作下的方法,要么全部成功,要么全部成功。
5.1 事务的ACID特性
- 原子性(Atomicity):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。--》主要涉及InnoDB事务。相关特性:事务的提交,回滚,信息表。
- 一致性(consistency):数据库总是从一个一致性的状态转换到另一个一致性的状态。在事务开始前后,数据库的完整性约束没有被破坏。例如违反了唯一性,必须撤销事务,返回初始状态。--》主要涉及内部InnoDB处理,以保护数据不受崩溃,相关特性:双写缓冲、崩溃恢复。
- 隔离性(isolation):每个读写事务的对象对其他事务的操作对象能相互分离,即:事务提交前对其他事务是不可见的,通常内部加锁实现。--》主要涉及事务,尤其是事务隔离级别,相关特性:隔离级别、innodb锁的底层实现细节。
- 持久性(durability):一旦事务提交,则其所做的修改会永久保存到数据库。--》涉及到MySQL软件特性与特定硬件配置的相互影响,相关特性:4个配置项:双写缓冲开关、事务提交刷新log的级别、binlog同步频率、表文件;写缓存、操作系统对于fsync()的支持、备份策略等。
5.2事务的属性
要保证事务的ACID特性,spring给事务定义了6个属性,对应于声明式事务注解(org.springframework.transaction.annotation.Transactional)@Transactional(key1=,key2=...)
- 事务名称:用户可手动指定事务的名称,当多个事务的时候,可区分使用哪个事务。对应注解中的属性value、transactionManager
- 隔离级别: 为了解决数据库容易出现的问题,分级加锁处理策略。 对应注解中的属性isolation
- 超时时间: 定义一个事务执行过程多久算超时,以便超时后回滚。可以防止长期运行的事务占用资源.对应注解中的属性timeout
- 是否只读:表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.对应注解中的属性readOnly
- 传播机制: 对事务的传播特性进行定义,共有7种类型。对应注解中的属性propagation
- 回滚机制:定义遇到异常时回滚策略。对应注解中的属性rollbackFor、noRollbackFor、rollbackForClassName、noRollbackForClassName
隔离级别
事务隔离级别指的是一个事务对数据的修改与另一个并行的事务的隔离程度,当多个事务同时访问相同数据时,如果没有采取必要的隔离机制,就可能发生以下问题:
问题 | 描述 |
脏读(Drity Read) | 事务A更新记录但未提交,事务B查询出A未提交记录。 |
不可重复读(Non-repeatable read) | 事务A读取一次,此时事务B对数据进行了更新或删除操作,事务A再次查询数据不一致 |
幻读(Phantom Read) | 事务A读取一次,此时事务B插入一条数据事务A再次查询,记录多了 |
隔离级别
隔离级别 | 描述 |
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别。 |
ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的更改。可能导致脏读、幻影读或不可重复读。 |
ISOLATION_READ_COMMITTED | 允许从已经提交的并发事务读取。可防止脏读,但幻影读和不可重复读仍可能会发生。 |
ISOLATION_REPEATABLE_READ | 对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻影读仍可能发生。 |
ISOLATION_SERIALIZABLE | 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。 |
传播机制
传播机制 | 描述 | 回滚规则 |
PROPAGATION_REQUIRED | 支持当前事务;如果不存在,创建一个新的。类似于同名的EJB事务属性。这通常是事务定义的默认设置,通常定义事务同步作用域。 | 存在一个事务:1.外部有事务加入,异常回滚;2.外部没事务创建新事务,异常回滚 |
PROPAGATION_SUPPORTS | 支持当前事务;如果不存在事务,则以非事务方式执行。类似于同名的EJB事务属性。 | 最多只存在一个事务: 1.外部有事务加入,异常回滚;2.外部没事务,内部非事务,异常不回滚 |
PROPAGATION_MANDATORY | 支持当前事务;如果当前事务不存在,抛出异常。类似于同名的EJB事务属性。 | 最多只存在一个事务: 1.外部存在事务加入,异常回滚;2.外部不存在事务,异常无法回滚 |
PROPAGATION_REQUIRES_NEW | 创建一个新事务,如果存在当前事务,则挂起当前事务。类似于同名的EJB事务属性。 | 可能存在1-2个事务:1.外部存在事务挂起,创建新,异常回滚自己的事务 2.外部不存在事务,创建新, 异常只回滚新事务 |
PROPAGATION_NOT_SUPPORTED | 不支持当前事务,存在事务挂起当前事务;始终以非事务方式执行。类似于同名的EJB事务属性。 | 最多只存在一个事务:1. 外部有事务挂起,外部异常回滚;内部非事务,异常不回滚2.外部无事务,内部非事务,异常不回滚 |
PROPAGATION_NEVER | 不支持当前事务;如果当前事务存在,抛出异常。类似于同名的EJB事务属性。 | 最多只存在一个事务:1.外部有事务,外部异常回滚;内部非事务不回滚 2.外部非事务,内部非事务,异常不回滚 |
PROPAGATION_NESTED | 如果当前事务存在,则在嵌套事务中执行,如果当前没有事务,类似PROPAGATION_REQUIRED(创建一个新的)。EJB中没有类似的功能。 | 存在一个事务:1. 外部有事务,嵌套事务创建保存点,外部异常回滚全部事务;内部嵌套事务异常回滚到保存点;2.外部不存在事务,内部创建新事务,内部异常回滚 |
5.3 代码演示
5.3.1 基于XML配置文件
XML配置文件
<?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:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 开启扫描 -->
<context:component-scan base-package="com.tcwong.*"></context:component-scan>
<!-- 配置数据源 -->
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 配置JdbcTemplate -->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="template">
<!-- 构造注入关联数据源 -->
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<!-- 配置事务 -->
<!-- 配置事务管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
<!-- 关联数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务的行为 -->
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="doThing*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- AOP配置 -->
<aop:config>
<aop:pointcut expression="execution(* com.tcwong.service.impl.*.*(..))" id="myPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
</aop:config>
</beans>
bean 类
public class User {
private Integer id;
private String name;
private String password;
private Integer age;
private String address;
public User() {
super();
}
public User(Integer id, String name, String password, Integer age, String address) {
super();
this.id = id;
this.name = name;
this.password = password;
this.age = age;
this.address = address;
}
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 String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
[@Override]()
public String toString() {
return "User [id=" + id + ", name=" + name + ", password=" + password + ", age=" + age + ", address=" + address
+ "]";
}
}
dao类
public interface IUserDao{
void insert() throws Exception;
void delete() throws Exception;
void update() throws Exception;
}
[@Repository]()
public class UserDaoImpl implements IUserDao<Integer, User> {
[@Resource]()
private JdbcTemplate template;
private String sql;
[@Override]()
public void insert() throws Exception {
sql="INSERT INTO user (name,password,age,address) VALUES(?,?,?,?)";
int num = template.update(sql, "cc","cc",18,"上海");
System.out.println( "增加数据成功"+num);
}
@Override
public void delete() throws Exception {
sql="DELETE FROM user WHERE id=?";
int num = template.update(sql,1002);
System.out.println("删除数据成功"+num);;
}
@Override
public void update() throws Exception {
sql="UPDATE user SET name=?,password=?,age1=?,address=? WHERE id=?";
int num = template.update(sql,"su","123456",20,"杭州" ,1003);
System.out.println("更新数据成功"+num);
}
}
service 类
@Service
public interface IUserService {
void doThing1() throws Exception;
}
@Service
public class UserServiceImpl implements IUserService {
@Resource
private UserDaoImpl userDao;
@Override
public void doThing1() throws Exception {
userDao.insert();
userDao.update();
}
}
test类
public class Test {
@org.junit.Test
public void test() {
ApplicationContext ac= new ClassPathXmlApplicationContext("applicationContext.xml");
IUserService service = ac.getBean(IUserService.class);
try {
service.doThing1();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
5.3.2 基于@Transactional注释
service 改动部分
@Service
public class UserServiceImpl implements IUserService {
@Resource
private UserDaoImpl userDao;
@Override
@Transactional
public void doThing() throws Exception {
userDao.delete();
userDao.insert();
}
}
其他部分未改动
XML配置文件
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 开启扫描 -->
<context:component-scan base-package="com.tcwong.*"></context:component-scan>
<!-- 配置数据源 -->
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 配置JdbcTemplate -->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="template">
<!-- 构造注入关联数据源 -->
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<!-- 配置事务 -->
<!-- 配置事务管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<!-- 关联数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven/>
</beans>
注意事项:Spring AOP 异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认情况下aop只捕获RuntimeException的异常。