如果一个业务操作中多次访问了数据库,必须保证每条SQL语句都执行成功。如果其中有一条执行失败,所有已经执行过的代码必须回滚。回到没有执行前的状态。称为事务。要么所有的SQL语句全部执行成功,要么全部失败。
| 事务特性 | 含义 |
|---|---|
| 原子性(Atomicity) | 事务是工作的最小单元,整个工作单元要么全部执行成功,要么全部执行失败 |
| 一致性(Consistency) | 事务执行前与执行后,数据库中数据应该保持相同的状态。如:转账前总金额与转账后总金额相同。 |
| 隔离性(Isolation) | 事务与事务之间不能互相影响,必须保持隔离性。 |
| 持久性(Durability) | 如果事务执行成功,对数据库的操作是持久的。 |
并发访问下事务产生的问题:
当同时有多个用户在访问同一张表中的记录,每个用户在访问的时候都是一个单独的事务。
事务在操作时的理想状态是:事务之间不应该相互影响,实际应用的时候会引发下面三种问题。应该尽量避免这些问题的发生。通过数据库本身的功能去避免,设置不同的隔离级别。
| 级别 | 名字 | 隔离级别 | 脏读 | 不可重复读 | 幻读 | 数据库默认隔离级别 |
|---|---|---|---|---|---|---|
| 1 | 读未提交 | read uncommitted | 是 | 是 | 是 | |
| 2 | 读已提交 | read committed | 否 | 是 | 是 | Oracle和SQL Server |
| 3 | 可重复读 | repeatable read | 否 | 否 | 是 | MySQL |
| 4 | 串行化 | serializable | 否 | 否 | 否 |
1)Read uncommitted (读未提交): 简称RU隔离级别,所有事务中的并发访问问题都会发生,读取的是其他事务没有提交的数据
2)Read committed (读已提交):简称RC隔离级别,会引发不可重复读和幻读的问题,读取的永远是其他事务提交的数据
3)Repeatable read (可重复读):简称RR隔离级别,有可能会引发幻读的问题,一次事务读取到的同一行数据,永远是一样
4)Serializable (串行化): 可以避免所有事务产生的并发访问的问题 效率及其低下
drop table if exists account;-- 创建数据表
CREATE TABLE account ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(10), money DOUBLE
); -- 添加数据
INSERT INTO account (name, money) VALUES ('a', 1000), ('b', 1000);
select @@tx_isolation;
set global transaction isolation level 四种隔离;set global transaction isolation level read uncommitted;
set global transaction isolation level read committed;
set global transaction isolation level repeatable read;
set global transaction isolation level serializable;
需要重启客户端会话
start transaction;
或者
begin;
commit;
rollback;
编程式事务简单的来说就是采用编程的方式来管理事务,编程式事务需要将事务管理的代码写入到业务方法中,相对于核心业务而言,事务管理的代码显然属于非核心业务,对核心业务代码的侵入性太大;而且如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。
我们之前使用的JDBC API来操作事务就是编程式事务;
声明式事务以声明的方式来实现事务管理,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务简单的来说就是通过一些表达式声明(拦截)一堆需要被事务管理的方法,然后被声明(拦截)的方法通过AOP的方式进行事务管理(执行目标方法之前开启事务,执行目标方法之后提交或者回滚事务)
显然声明式事务管理要优于编程式事务管理:它将事务管理代码从业务方法中分离出来,这正是Spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受干扰;一个普通的对象,只要加上注解就可以获得完全的事务支持。
声明式事务唯一不足地方是,它的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口。
它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
DataSourceTransactionManager:使用Spring JDBC 或 Mybatis 等基于DataSource数据源的持久化技术时,使用 该事务管理器JpaTransactionManager:使用JPA时采用的事务管理器JtaTransactionManager:具有多个数据源的全局事务使用该事务管理器JdoTransactionManager:使用JDO进行持久化时 ,使用该事务管理器HibernateTransactionManager:使用Hibernate进行持久化时,使用该事务管理器当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为,在org.springframework.transaction.TransactionDefinition类中被定义。
| 静态常量 | 说明 |
|---|---|
| PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择,也是Spring的默认值。 |
| PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
| PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
| PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
| PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
| PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
| PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
TransactionDefinition类中不仅定义了事务的传播行为,也定义了很多事务的隔离级别:
| 静态常量 | 说明 |
|---|---|
| ISOLATION_DEFAULT | 使用数据库默认的隔离级别,Spring默认值 |
| ISOLATION_READ_UNCOMMITTED | 读未提交 |
| ISOLATION_READ_COMMITTED | 读已提交 |
| ISOLATION_REPEATABLE_READ | 可重复读 |
| ISOLATION_SERIALIZABLE | 串行化 |
drop database if exists spring;create database spring;use spring;-- 创建数据表
CREATE TABLE account ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(10), money DOUBLE
); -- 添加数据
INSERT INTO account (name, money) VALUES ('a', 1000), ('b', 1000);
4.0.0 com.dfbz 01_Spring 1.0-SNAPSHOT org.springframework spring-context 5.2.9.RELEASE org.springframework spring-jdbc 5.2.9.RELEASE org.springframework spring-tx 5.2.9.RELEASE org.springframework spring-aspects 5.2.9.RELEASE org.springframework spring-test 5.2.9.RELEASE mysql mysql-connector-java 5.1.48 com.alibaba druid 1.2.1 junit junit 4.12 test org.projectlombok lombok 1.18.18
jdbc.username=root
jdbc.password=admin
jdbc.url=jdbc:mysql:///spring
jdbc.driverClassName=com.mysql.jdbc.Driver
package com.dfbz.entity;import lombok.Data;/*** @author lscl* @version 1.0* @intro:*/
@Data
public class Account {private Integer id;private String name;private Double money;
}
**默认情况下,任何的运行时异常事务都会回滚,但编译时异常都不会进行回滚;**我们可以通过一些配置来调整
no-rollback-for:指定什么异常不回滚,可以写多个,以逗号隔开;
rollback-for:该配置针对于编译时异常;可以写多个,以逗号隔开;
上述两个参数在指定异常时,如果指定某个异常的父类,包括这个异常的所有子类异常都会回滚;
如:no-rollback-for指定的参数是Exception则代表Java中的任何异常都不回滚,
rollback-for指定的参数是Exception则Java中任何的异常都回滚;
package com.dfbz.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;/*** @author lscl* @version 1.0* @intro:*/
@Service
public class AccountService {@Autowiredprivate JdbcTemplate jdbcTemplate;/*** @param str : 谁要转账* @param target: 转账给谁* @param flag: 模拟异常 true:出现异常 false:不出现异常*/public void transfer(String str, String target, Boolean flag) {jdbcTemplate.update("update account set money=money-500 where name=?", str);if (flag) {// 模拟异常int i = 1 / 0;}jdbcTemplate.update("update account set money=money+500 where name=?", target);}/**** @param id*/public void findById(Integer id) {Account account = jdbcTemplate.queryForObject("select * from account where id=?",new BeanPropertyRowMapper<>(Account.class),id);}
}
package com.dfbz.test;import com.dfbz.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;/*** @author lscl* @version 1.0* @intro:*/
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class Demo01 {@Autowiredprivate AccountService accountService;@Testpublic void test1(){accountService.transfer("a","b",false);}
}
package com.dfbz.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;/*** @author lscl* @version 1.0* @intro:*/
@Service
public class AccountService {@Autowiredprivate JdbcTemplate jdbcTemplate;/*** @param str : 谁要转账* @param target: 转账给谁* @param flag: 模拟异常 true:出现异常 false:不出现异常*/
/* @Transactional(transactionManager = "transactionManager", // 事务管理器的名称propagation = Propagation.REQUIRED, // 传播行为isolation = Isolation.DEFAULT, // 隔离级别timeout = 3000, // 超时时间readOnly = false, // 是否只读rollbackFor = {MyException.class}, // 出现该异常回滚noRollbackFor = {ArithmeticException.class} // 出现指定异常不回滚) */@Transactional // 使用注解方式管理事务public void transfer(String str, String target, Boolean flag) {jdbcTemplate.update("update account set money=money-500 where name=?", str);if (flag) {// 模拟异常int i = 1 / 0;}jdbcTemplate.update("update account set money=money+500 where name=?", target);}
}
package com.dfbz.test;import com.dfbz.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;/*** @author lscl* @version 1.0* @intro:*/
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:spring2.xml")
public class Demo02 {@Autowiredprivate AccountService accountService;@Testpublic void test1(){accountService.transfer("a","b",false);}
}