本文目录
事务概述
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
事务特征(ACID)
- 原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。
- 一致性:这表示数据库的引用完整性的一致性,表中唯一的主键等。
- 隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。
- 持久性:一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除。
并发事务带来的问题
- 更新丢失(Lost Update)
多个事务修改同一行记录(都未提交), 后面的修改覆盖了前面的修改. - 脏读(Dirty Reads)
一个事务可以读取另一个事务未提交的数据. - 不可重复读(Non-Repeatable Reads)
同一个事务中执行两次相同的查询, 可能得到不一样的结果. 这是因为在查询间隔内,另一个事务修改了该记录并提交了事务. - 幻读(Phantom Reads)
当某个事务在读取某个范围内的记录时, 另一个事务又在该范围内插入了新的记录, 当之前的事务再次读取该范围的记录时, 会产生幻行.
事务的传播特性
事务传播行为就是多个事务方法调用时,如何定义方法间事务的传播。Spring定义了7中传播行为:
(1)propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。
(2)propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
(3)propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
(4)propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
(5)propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
(6)propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
(7)propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。
事务的隔离级别
在MySQL常用的存储引擎中, 只有InnoDB支持事务, 所以这里说的隔离级别指的是InnoDB下的事务隔离级别.
READ UNCOMMITTED(读未提交)
在该隔离级别, 事务中的修改即使没有提交, 对其他事务也都是可见的. 避免了更新丢失的发生.
READ COMMITTED(读已提交)
在该隔离级别, 一个事务只能看见已经提交的事务所做的修改. 避免了更新丢失和脏读.
REPEATABLE READ(可重复读)
MySQL默认的隔离级别, 该级别保证了在同一个事务中多次读取同样的记录的结果是一致的. 避免了更新丢失、脏读、不可重复读和幻读. (注意看MySQL官网, RR隔离级别下解决了幻读问题)
SERIALIZABLE(可串行化)
SERIALIZABLE是最高的隔离级别, 它通过强制事务串行化执行, 避免了并发事务带来的问题.
事务几种实现方式
(1)编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
(2)基于 TransactionProxyFactoryBean的声明式事务管理
(3)基于 @Transactional 的声明式事务管理
(4)基于Aspectj AOP配置事务
基于底层 API 的编程式事务管理
第一步:创建maven工程和数据库表
打开mysql,创建一张user
表
CREATE TABLE user(
ID INT NOT NULL AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
AGE INT NOT NULL,
PRIMARY KEY (ID)
);
MYISAM不支持事务,INNODB支持事务处理,Mysql版本从5.5.8开始,默认使用INNODB存储引擎,mysql创建数据库时应选择InnoDB
创建一个maven工程【Spring】Spring入门案例
在pom.xml
中加入Spring
和mysql
相关的包
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
</dependencies>
第二步:添加数据库处理事务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
@Repository("userTransaction")
public class UserTransaction {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private DataSourceTransactionManager txManager;
public void updateUser() {
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
TransactionStatus status = txManager.getTransaction( transactionDefinition);
try {
String sql = "insert into user values (?,?,?)";
String sql1 = "insert into user values (?,?,?)";
Object[] para = {1, "张三", 21};
jdbcTemplate.update("delete from user");
jdbcTemplate.update(sql, para);
jdbcTemplate.update(sql1, para);
txManager.commit(status);
} catch (Exception e) {
txManager.rollback(status);
System.out.println("回滚事务" + e.getMessage());
e.printStackTrace();
}
}
}
applicationContext.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.lucas"/>
<!-- 1.配置数据源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- 1.1.数据库驱动 -->
<property name="driverClassName"
value="com.mysql.cj.jdbc.Driver"></property>
<!-- 1.2.连接数据库的url -->
<property name="url"
value="jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true"></property>
<!-- 1.3.连接数据库的用户名 -->
<property name="username" value="root"></property>
<!-- 1.4.连接数据库的密码 -->
<property name="password" value="123456"></property>
</bean>
<!-- 2配置JDBC模板 -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 默认必须使用数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源的事务管理-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
第三步:添加测试代码
public class App {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserTransaction userTransaction = (UserTransaction) applicationContext.getBean("userTransaction");
userTransaction.updateUser();
}
}
运行结果:
从执行的结果来看,由于发生异常导致代码回滚,数据库中并没有插入数据
基于 TransactionTemplate 的编程式事务管理
基于 TransactionTemplate
的编程式事务管跟前面的基于API事务使用区别不大
在applicationContext.xml
中添加bean
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"/>
</bean>
修改事务处理代码 ,执行结果同上
package com.lucas;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
@Repository("userTransaction")
public class UserTransaction {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private TransactionTemplate transactionTemplate;
public void updateUser1() {
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
String result = "";
try {
String sql = "insert into user values (?,?,?)";
String sql1 = "insert into user values (?,?,?)";
Object[] para = {1, "张三", 21};
jdbcTemplate.update(sql, para);
jdbcTemplate.update(sql1, para);
}catch (Exception e){
System.out.println("回滚事务" + e.getMessage());
status.setRollbackOnly();
}
return result;
}
});
}
}