深入浅出boot2.0第六章 数据库事务处理 隔离级别 传播行为

数据库事务包含:acid。
atomic 原子性:最小业务单元,要么全部成功,要么全部失败
consistency 一致性:事务完成时,所有的数据都保持一致
iso lation 隔离性: 多个线程同时访问同一数据,产生丢失更新
dura bility 持久性:事务结束可断电

第一类丢失更新:
一个事务回滚,另外一个事务提交,引发不一致。
100,a -1=99,b-1=99,b提交=99,a回滚=100

第二类丢失更新:
两个事务都提交,引发的不一致。
100,a-1=99,b-1=99并提交。a提交=99
事务a无法感知事务b的提交

隔离级别: 四种。
 @Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1), //未提交读 
    READ_COMMITTED(2),//读写提交 oracle
    REPEATABLE_READ(4),//可重复读 mysql
    SERIALIZABLE(8); //串行化
}

完全避免丢失更新,就是付出锁的代价(性能不好)

1,未提交读:准许一个事务读取 另外一个事务,没有提交的数据
出现脏读:库存2,a-1=1。b-1=0(b读a未提交的结果),b提交,库存为0。a回滚=0(回滚到b提交时)

2,读写提交:是 一个事务 只能读取 另外一个 事务已经提交的数据。
克服脏读:库存2,a-1=1。b读取2,-1=1 提交事务。a回滚为1(回滚到b提交时)
出现不可重复读:库存1,a-1=0(未提交)。b认为库存为1。a提交,变成0。 b此时无法扣减库存。

3,可重复读(mysql):
克服不可重复读:等待事务提交
a读取库存,扣减。b 无法读取,等待a提交。

出现幻读:库存5,交易记录5。b查询记录为5,a库存-1,提交,交易记录为6。b打印交易记录为6(于查询的不一致)
记录数据库的值,是一个统计的值。

4,串行化。SQL 都会按照 顺序执行

未提交读:出现 脏 不可重复 幻 读
读写提交:出现     不可重复 幻 读
可重复读:出现    幻读
串行化:都不会出现。

更高的隔离级别,保证了数据的一致性,但也要付出锁的代价。
  • 掌握数据库事务机制 至关重要

  • 保证数据的一致性

  • 有效提高系统性能,避免系统产生宕机

  • 数据库事务 通过 Aop 技术来提供服务

  • 数据库隔离级别

  • 数据库事务传播行为:一个批处理,在一些交易中发生了异常,不能所有交易都回滚。

    spring.datasource.url=jdbc:mysql://localhost:3306/test
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    
    #指定数据库连接池的类型 (配置dbcp2数据源)
    #spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
    
    #最大等待连接中的数量,设 0 为没有限制
    spring.datasource.dbcp2.max-idle=10
    
    #最大连活动接数
    spring.datasource.dbcp2.max-total=50
    
    #最大等待毫秒数, 单位为 ms, 超过时间会出错误信息
    spring.datasource.dbcp2.max-wait-millis=10000
    
    #数据库连接池初始化连接数
    spring.datasource.dbcp2.initial-size=5
    
    
    #日志配置
    logging.level.root=DEBUG
    logging.level.org.springframework=DEBUG
    logging.level.org.org.mybatis=DEBUG
    
  • 数据库事务

    • 编程式事务:淘汰
    • 声明式事务

jdbc的数据库事务


Connection conn = null;
		int result = 0;
		try {
            
			//先注册驱动
			Class.forName("com.mysql.jdbc.Driver");
			//在通过驱动管理器,获取数据事务连接
			conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
			//非自动提交事务。开启事务
			conn.setAutoCommit(false);
            
 //设置事务的隔离级别     READ_COMMITTED(2),//read_commited 读_提交         		conn.setTransactionIsolation(TransactionIsolationLevel.READ_COMMITTED.getLevel());
            
			//执行层
			PreparedStatement ps = null;
			try {
				//预设sql
				ps = conn.prepareStatement("insert into user(user_name,note) values (?,?)");
				//参数指定
				ps.setString(1, "张三");
				ps.setString(2, "study");
				//执行
				result = ps.executeUpdate();
			} finally {
				//怎样都关闭连接
				ps.close();
			}

			//提交事务
			conn.commit();
            
		} catch (Exception e) {
			try {
				//回滚事务
				conn.rollback();
			} catch (SQLException ex) {
				ex.printStackTrace();
			}
			e.printStackTrace();
		} finally {
			//释放连接池
			if (conn != null && !conn.isClosed() ) {
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}

		if (result == 0) {
		} else {
		}


	//另外一种 获得 dataSource的方法
	@Autowired
	private DataSource dataSource=null;

	public int insertUser(){
		Connection conn = dataSource.getConnection();
	}
public enum TransactionIsolationLevel {
    NONE(0), //none
    READ_COMMITTED(2),//read_commited 读_提交
    READ_UNCOMMITTED(1),//read_uncommitted 读_不提交
    REPEATABLE_READ(4),//repeatable_read 可重复的_读
    SERIALIZABLE(8);//serializable 序列化的

    private final int level;//定义最终的 int 变量

    private TransactionIsolationLevel(int level) {
        this.level = level;//构造对level赋值
    }

    public int getLevel() {
        return this.level;//提供返回 level
    }
}
  • 开启事务——执行SQL——发生异常——回滚事务
  • 不发生异常——提交事务
  • ——释放事务资源

Spring 声明式 事务的使用

  • @Transactional

  • 类上 或 方法上,

  • 类上代表这个类 所有公共 非静态 的方法 都启用事务功能

  • 在此注解上,还可以 配置 事务的隔离级别 和 传播行为 异常类型(发生什么异常回滚 或 不回滚)

  • 配置的内容 Spring Ioc 容器 在加载 时 就会 将这些配置信息 解析出来,

  • 然后把这些信息 存到事务 定义器 TransactionDefinition 接口 的实现类 里

  • 并且 记录 那些类 或者 方法需要启动 事务功能

  • 采取什么策略 去 执行 事务。

  • 我们 只需要 配置属性即可

  • Spring Ioc启动时,解析 和 保存事务配置

  • 开启 和 设置 事务

  • 执行方法逻辑 (开发者提供)

  • 发生异常 且 满足回滚配置 ——回滚事务——释放资源

  • 没异常——提交事务——释放资源

  • Spring数据库 事务 拦截器 通过数据库 事务管理器 实现

  • 转播行为:属于 事务方法之间调用的行为

  • 隔离级别

  • 超时时间

  • 只读

	@Autowired
	private UserDao userDao = null;

	@Override
	@Transactional
	public int insertUser(User user) {
		return userDao.insertUser(user);
	}

@Transactional的配置项

  • 放在接口上 将使得 你的类 基于接口的代理 时 它 才会生效。
@Target({ElementType.METHOD, ElementType.TYPE}) //方法 和 类上(接口 也可以)
@Retention(RetentionPolicy.RUNTIME) //运行时
@Inherited
@Documented
public @interface Transactional {
    
    @AliasFor("transactionManager")//bean name指定 事务管理器
    String value() default "";

    @AliasFor("value")//同 value 属性。都是配置一个Spring的事务管理器
    String transactionManager() default "";

    //指定 传播行为 
    Propagation propagation() default Propagation.REQUIRED;

    //指定 隔离级别
    Isolation isolation() default Isolation.DEFAULT;
    
	//指定超时 时间 单位秒。事务可以允许存在的 时间戳。单位为秒
    int timeout() default -1;

    //是否只读事务。默认不是 只读事务。
    boolean readOnly() default false;

    //发生指定异常回滚,默认所有异常都回滚
    Class<? extends Throwable>[] rollbackFor() default {};

    //方法发生在 指定异常 名字回滚
    String[] rollbackForClassName() default {};

     //发生指定异常不回滚,默认所有异常都回滚
    Class<? extends Throwable>[] noRollbackFor() default {};

    //方法发生在 指定异常 名字不回滚
    String[] noRollbackForClassName() default {};
}

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE, //类上

    /** Field declaration (includes enum constants) */
    FIELD, //字段上

    /** Method declaration */
    METHOD,//方法上

    /** Formal parameter declaration */
    PARAMETER,//参数上

    /** Constructor declaration */
    CONSTRUCTOR,//构造上

    /** Local variable declaration */
    LOCAL_VARIABLE,//本地变量上

    /** Annotation type declaration */
    ANNOTATION_TYPE,//注解 类型上

    /** Package declaration */
    PACKAGE,//包上

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,//类型 参数上

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE//类型 使用上
}


public enum RetentionPolicy {
    /**
     * Annotations are to be discarded丢弃的;废弃的  by the compiler.
     */
    SOURCE, //源码 在编译的时候 丢弃

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,//类上

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME //运行时候
}

Spring的事务管理器

顶层接口为,Platform Transaction Manager

子类:DataSource Transaction Manager。mybatis依赖后,会自动创建

public interface PlatformTransactionManager {
    //获取事务,设置数据 属性
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
	//提交事务
    void commit(TransactionStatus var1) throws TransactionException;
    //回滚事务
    void rollback(TransactionStatus var1) throws TransactionException;
}

  • TransactionDefinition 是一个事务定义器
  • 依赖我们配置的 @Transactional 的配置项生成

测试事务管理器

CREATE TABLE `t_user` (
  `id` int(12) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(60) NOT NULL,
  `sex` int(3) NOT NULL DEFAULT '1',
  `note` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
@Alias(value = "user")//指定别名
@Data
public class User {
	private Long id;
	private String userName;
	private String note;
}

dao

@Repository
public interface UserDao {
    User getUser(Long id);
    int insertUser(User user);
}

userMap.xml


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springboot.chapter6.dao.UserDao">
	<select id="getUser" parameterType="long" resultType="user">
		select id, user_name as userName, note from t_user where id = #{id}
	</select>

	<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
		insert into t_user(user_name, note) value(#{userName}, #{note})
	</insert>
</mapper>
  • useGeneratedKeys=“true” keyProperty=“id” 插入之后,使用数据库生成机制 回填对象的主键

service

public interface UserService {
	// 获取用户信息
	public User getUser(Long id);

	// 新增用户
	public int insertUser(User user);
}
@Service
public class UserServiceImpl implements UserService {
	@Autowired
	private UserDao userDao = null;

	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
	public int insertUser(User user) {
		return userDao.insertUser(user);
	}

	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
	public User getUser(Long id) {
		return userDao.getUser(id);
	}
}
  • Isolation.READ_COMMITTED 读写 的隔离级别
  • 超时时间,设置为了1秒

action

@Controller
@RequestMapping("/user")
public class UserController {
	// 注入Service
	@Autowired
	private UserService userService = null;

	@Autowired
	private DataSource dataSource=null;

	@Transactional
	public int insertUser(){
		Connection conn = dataSource.getConnection();

	}

	// 测试获取用户
	@RequestMapping("/getUser")
	@ResponseBody
	public User getUser(Long id) {
		return userService.getUser(id);
	}

	// 测试插入用户
	@RequestMapping("/insertUser")
	@ResponseBody
	public Map<String, Object> insertUser(String userName, String note) {
		User user = new User();
		user.setUserName(userName);
		user.setNote(note);
		// 结果会回填主键,返回插入条数
		int update = userService.insertUser(user);
		Map<String, Object> result = new HashMap<>();
		result.put("success", update == 1);
		result.put("user", user);
		return result;
	}
}

MyBatis 配置

mybatis.mapper-locations=classpath:com/springboot/chapter6/mapper/*.xml
mybatis.type-aliases-package=com.springboot.chapter6.pojo

#日志配置
logging.level.root=DEBUG
logging.level.org.springframework=DEBUG
logging.level.org.org.mybatis=DEBUG

启动类配置

@MapperScan(
    basePackages = "com.springboot.chapter6", 
    annotationClass = Repository.class)
@SpringBootApplication(scanBasePackages = "com.springboot.chapter6")

public class Chapter6Application {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Chapter6Application.class, args);
    }
    
    // 注入事务管理器,它由Spring Boot自动生成
    @Autowired
    PlatformTransactionManager transactionManager = null; 

    // 使用后初始化方法,观察自动生成的事务管理器
    @PostConstruct
    public void viewTransactionManager() {
        
        // 启动前加入断点观测
        System.out.println(transactionManager.getClass().getName());
    }
}

测试

http://localhost:8080/mybatis/insertUser?userName=李四&note=22222

{
    "success": true,
    "user": {
        "id": 2,
        "userName": "李四",
        "note": "22222",
        "sex": null
    }
}

隔离级别

  • @Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
    
  • 商品库存,机会出现多个事务 同时访问 统一记录 的 情况 (丢失更新)

数据库事务的知识

  • acid
    • atomic 原子性 : 最小业务单元,要么全部成功,要么全部失败
    • consistency 一致性:事务完成时,所有的数据都保持一致状态,在数据库中所有的修改都基于事务,保证了数据的完整性
    • iso lation 隔离性: 多个线程同时访问同一数据,同样的数据 就会在各个不同的事务中 被访问,会产生丢失更新。 为了压制丢失更新,定义了隔离级别。选择不同程度上压制丢失更新的发生。
    • dura bility 持久性:事务结束,数据固化到一个地方,断电也没事
第一类丢失更新
  • 一个事务回滚 另外一个事务提交 而引发的数据不一致 的情况

    • 库存100,
    • 事务1,减去库存,剩余99
    • 事务2,减去库存,剩余99,提交事务剩余99个。(事务2,的结果 丢失了)
    • 事务1,回滚事务,剩余100 个。
    • 大部分数据库已经克服了此种问题,不在讨论
第二类丢失更新
  • 多个事务同时提交

    • 库存100,
    • 事务1,减去库存,剩余99
    • 事务2,减去库存,剩余99,提交事务剩余99个。(事务2,的结果 丢失了)
    • 事务1,提交事务,剩余99 个。
  • 因为事务1,无法感知事务2的操作,事务1提交,引发事务2提交结果的丢失,

    • 为了解决这样的问题,提出了事务之间的隔离级别

隔离级别详情

  • 4类隔离级别
    • 未提交读
    • 读写提交
    • 可重复读
    • 串行化
  • 数据的一致性,数据的性能
  • 完全避免丢失更新,就是付出锁的代价(系统的性能不好)
  • 抢购,必然会导致大量的线程被挂机 和 恢复,整个系统就会十分缓慢,千万用户同时访问,就会宕机
  • 用户体检十分糟糕,互联网的相应时间超过5秒,就会认为不友好。
  • 既要考虑数据的一致性 避免脏数据,又要考虑系统性能 的问题
未提交读 (read uncommitted)
  • 准许一个事务读取 另外一个事务,没有提交的数据

  • 是危险的隔离级别

  • 并发能力高

  • 适应对 数据一致性 没有要求 ,而追求 高并发 的场景

  • 会出现脏读

    • 库存为2

    • 事务1 : 读取库存为2,减去库存,库存为1

    • 事务2: 扣减库存(读的是事务1,未提交的库存数据),库存为0。提交事务,库存为0

    • 事务1:回滚事务,丢失更新已经克服,不会回滚为2,会回滚为 0

    • 未提交读,事务2 可以读取事务1未提交的库存。减库存 提交 库存为0

    • 事务1回滚,第一类丢失更新已经被克服,所以不会回滚为2,结果变成了0.

    • 脏读 是比较危险的隔离级别。 为了克服脏读 还提供了 读写提交

读写提交(read committed)

读写提交 隔离级别,是 一个事务 只能读取 另外一个 事务已经提交的数据,不能读取 未提交的数据

克服脏读:

  • 库存为2
  • 事务1:读取库存2,扣减库存 库存为1
  • 事务2:扣减库存,库存为1,(读取不到事务1 未提交的库存数据)
  • 事务2:提交事务,库存保存为1
  • 事务1:回滚事务,第一类丢失更新已经克服,所以不会回滚为2,会变成库存为1

事务2:扣减库存,库存为1,不能读取到事务1中未提交的库存1

不可重读场景,

  • 商品初始化 为 1

  • 事务1:读取 库存为 1,扣减库存,(未提交)

  • 事务2:读取 库存为1,认为可扣减,库存为0

  • 事务1:提交事务,库存变成0

  • 事务2:扣减库存失败,因为此时 库存为0,无法扣减

    库存对事务2 而言 是一个可变化 的值,称为 不可重复读,

    就是 读写提交的一个不足。

    为了克服这个不足,提出了 可重复读

可重复读

​ 克服读写提交中 出现的不可重复读 的 现象

读写提交的时候,可能出现一些值的变化,

  • 商品初始化为1
  • 事务1:读取库存为1,扣减库存 (事务未提交)
  • 事务2:尝试读取库存,不允许 读取,等待事务1 提交
  • 事务1:提交事务,库存变成0
  • 读取事务,库存为0,无法扣减

​ 数据库会 阻塞 事务2 的读取,直到 事务1提交。

这样会引发新的问题:幻读

  • 事务1:读取库存50件,(最早为100,已经销售了50件)

  • 事务2:查询交易记录50笔

  • 事务1:扣减库存,插入1笔交易记录,提交事务(库存49件,交易记录51笔)

  • 事务2:打印交易记录 51笔,(与查询的不一致,事务2看来 有1笔是虚幻的,与之前查询的不一致)

  • 交易记录50笔,不是数据库的值,而是一个统计的值,商品的库存才是真实的值。

  • 幻读是:针对 数据库 的多条记录的。(这51笔交易数 ,就是多条数据库统计出来的)

  • 可重复读,是针对数据库的单一记录的(商品的库存)。

串行化(serializable)

是:数据库最高级别的隔离级别

要求所有的 SQL 都会按照 顺序执行,就会 克服上述 隔离级别出现的各种问题。

使用 合适的隔离级别

项目类型脏读不可重复读幻读
未提交读
读写提交X
可重复度XX
串行化XXX

更高的隔离级别,保证了数据的一致性,但也要付出锁的代价

在现实中 一般而言,选择隔离级别会 以读写提交为主。能够防止脏读。而 不能避免不可重读(扣减库存失败),和 幻读 (查询和打印交易记录不一致),

为了克服数据不一致问题, 还设计了 乐观锁。

甚至不再使用数据库 使用其他手段。(redis)

oracle 只能支持 读写提交 和 串行化 (默认读写提交,读取不到未提交的,库存可能无法扣减)

mysql 支持 4种,默认是可重复读 (事务未提交,不准许读取)

    @Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
    public int insertUser(User user) {
        return userDao.insertUser(user);
    }
Isolation.READ_COMMITTED 读写提交,读取不到未提交的,库存可能无法扣减
public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8); //序列化隔离级别,将阻塞其他的事务进行并发。用在低并发,又需保证数据一致的场景下,
    private final int value;
    private Isolation(int value) {
        this.value = value;
    }
    public int value() {
        return this.value;
    }
}

#隔离级别数字配置的含义:
第一类丢失更新:第二个事务提交,第一个事务回滚,结果不包含第二个结果
第二类丢失更新:第一,第二事务 同时读取后,扣减库存,同时提交,结果不对
#-1 数据库默认隔离级别
#1  未提交读:允许一个事务读取另一个事务未提交的数据。
脏读:事务1扣减库存,库存为1,事务2扣减库存,库存为0,提交。事务1回滚,变成库存为0
#2  读写提交:只能读取到 另一个事务 已经提交 的数据 (未提交的,读取不到。oracle)
克服脏读:事务1读到结果2,扣减为1。事务2读到结果为2,扣减为1,提交后也为1。事务1回滚为1。
不可重复读:总共1个,A读取了,没提交,B读取了,A提交了变成0。B读取的是错误的。
#4  可重复读:一个事务只能读取,另一个事务提交过的数据(一个事务没提交,另一个等待 mysql)
#8  串行化:按照顺序执行SQL
#tomcat数据源默认隔离级别
spring.datasource.tomcat.default-transaction-isolation=2
#dbcp2数据库连接池默认隔离级别
#spring.datasource.dbcp2.default-transaction-isolation=2

传播行为(propagation = Propagation.REQUIRED)

方法之间调用事务采取的策略问题。

绝大部分情况,要么全部成功,要么全部是失败。

在一个批量任务执行过程中,调用多个交易时,如果有一些交易发生了异常,只回滚那些出现异常的数据。

当一个方法调用另一个方法时,可以让事务 采取不同的策略工作,如:新建事务,挂起当前事务。

这便是 事务的传播行为。

  • 批量任务 ——》 单个交易 独立事务

  • 当前方法,调用子方法的时候。

  • 让每一个子方法 不在 当前事务中执行,

  • 而是创建一个新的 事务去之行子方法。

  • 即:当前方法调用 子方法的传播行为 为新建事务。还可以让子方法在 无事务,独立事务中执行。

传播行为的定义

7种传播行为,通过枚举 propagation定义的

public enum Propagation {
    REQUIRED(0),//需要事务。如果当前事务存在 就沿用当前事务,否则就新建一个事务。 常用
    SUPPORTS(1),//支持事务。如果当前事务存在 就沿用当前事务,如果不存在,采用无事务
    MANDATORY(2),//必须用事务,如果没,就抛异常。如果存在事务,就沿用当前事务。
    REQUIRES_NEW(3),//无论当前事务是否存在,都创建新的事务。新事务有新的锁 和 隔离级别,与当前事务独立。 常用
    NOT_SUPPORTED(4),//不支持事务,当前存在事务时,将挂起事务
    NEVER(5),//不支持事务,如果存在事务,则抛异常,否则继续使用无事务机制 运行
    NESTED(6);//调用子方法,如果子方法发生异常,只回滚子方法执行过的sql,不回滚当前方法的事务。
     常用
         
         required 当前存在就沿用,否则就新建 		常用
         supports 当前存在就沿用,否则就 无事务
         mandatory 没事务就抛异常,存在就沿用
         requires_new 无论与否,都创建新事务		  常用
         not_supporited  不支持事务,如果存在,将挂起
         nerver 不支持事务,如果存在,抛异常 
         nested 子方法发生异常,只回滚子方法			常用

    private final int value;
    private Propagation(int value) {
        this.value = value;
    }
    public int value() {
        return this.value;
    }
}
	//下载了源码,如此表示
	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

public interface TransactionDefinition {
	int PROPAGATION_REQUIRED = 0;
	int PROPAGATION_SUPPORTS = 1;
	int PROPAGATION_MANDATORY = 2;
	int PROPAGATION_REQUIRES_NEW = 3;
	int PROPAGATION_NOT_SUPPORTED = 4;
	int PROPAGATION_NEVER = 5;
	int PROPAGATION_NESTED = 6;
    
	int ISOLATION_DEFAULT = -1;

	int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;

	int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;

	int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

	int getPropagationBehavior();

	int getIsolationLevel();

	int getTimeout();

	boolean isReadOnly();

	@Nullable
	String getName();

}

测试传播行为

  • required 当前存在就沿用,否则就新建 常用
  • requires_new 无论与否,都创建新事务 常用
  • nested 子方法发生异常,只回滚子方法 常用
	@Resource
	private UserBatchService userBatchService;

	// 测试插入用户
	@RequestMapping("/insertUserList")
	@ResponseBody
	public Map<String, Object> insertUserList(String userName, String note) {
		User user = new User();
		user.setUserName(userName);
		user.setNote(note);

		User user1=new User();
		user1.setUserName("我的名字2");
		user1.setNote("哈哈哈,备注一下");

		List<User> userList = new ArrayList<>();
		userList.add(user);
		userList.add(user1);
		
		// 结果会回填主键,返回插入条数
		int update = userBatchService.insertUser(userList);
		
		Map<String, Object> result = new HashMap<>();
		result.put("success", update > 0);
		result.put("user", user);
		return result;
	}
    
public interface UserBatchService {
    public int insertUser(List<User> users);
}

@Service
public class UserBatchServiceImpl implements UserBatchService {
    @Autowired
    private UserService userService = null;
    
    //读 提交事务oracle,只能读取到提交的。
    // 如果当前事务存在 就沿用当前事务
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    public int insertUser(List<User> users) {
        int c=0;
        for(User u:users){
            c+=userService.insertUser(u);
        }
        return c;
    }
}


	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
	public int insertUser(User user) {
		return userDao.insertUser(user);
	}

http://localhost:8080/mybatis/insertUserList?userName=李四哈哈2&note=22222啦啦啦

REQUIRED (主方法上)
  • 当前存在就沿用,否则就新建 常用

  • insertUser (单个) 中没有定义传播行为。沿用当前的事务。

  • Participating in existing transaction 看到的日志是: 沿用已经存在的 当前事务

REQUIRES_NEW (子方法)
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW,timeout = 1)
    public int insertUser(User user) {
        return userDao.insertUser(user);
    }
  • 子方法改成了:requires_new 无论与否,都创建新事务 常用

  • Creating a new SqlSession 创建当前方法事务

  • 设置当前方法事务隔离级别为 读写提交

    Acquired Connection [HikariProxyConnection@1427116246 wrapping com.mysql.jdbc.JDBC4Connection@5c4db693] for JDBC transaction
    
    Changing isolation level of JDBC Connection [HikariProxyConnection@1427116246 wrapping com.mysql.jdbc.JDBC4Connection@5c4db693] to 2
    
    Switching JDBC Connection [HikariProxyConnection@1427116246 wrapping com.mysql.jdbc.JDBC4Connection@5c4db693] to manual commit
    
  • 创建子方法 事务

    Suspending current transaction, creating new transaction with name [com.example.demobenaware.service.UserServiceImpl.insertUser]
    
  • 设置子方法的隔离级别

    Changing isolation level of JDBC Connection [HikariProxyConnection@1427116246 wrapping com.mysql.jdbc.JDBC4Connection@5c4db693] to 2
    

启用了新的数据库事务 去运行每一个insertUser 方法,并且独立提交

这样就完全脱离了原有事务的管控,每一个事务都可以拥有自己独立的隔离级别和锁

NESTED (子方法上)

nested 子方法发生异常,只回滚子方法 常用

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.NESTED,timeout = 1)
    public int insertUser(User user) {
        return userDao.insertUser(user);
    }
Creating nested transaction with name [com.example.demobenaware.service.UserServiceImpl.insertUser]

Releasing transaction savepoint
  • 一段sql语句设置一个标识位
  • 后面的代码如果有异常,只是回滚到这个标识位 的 数据状态
  • 标识位,在数据库成为保存点。spring也是使用此方法。子事务回滚,而不回滚主事务。
  • spring: 当数据库支持保存点技术,就启动。如果不支持,就新建一个事务 去运行你的代码 =REQUIRES_NEW

@Transactional 自调失效问题

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW,timeout = 1)
    public int insertUser(User user) {
        return userDao.insertUser(user);
    }


    //读 提交事务oracle,只能读取到提交的。
    // 如果当前事务存在 就沿用当前事务
    //@Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    public int insertUser(List<User> users) {
        int c=0;
        for(User u:users){
            c+=insertUser(u);
        }
        return c;
    }

自调用,并没有创建任何新的事务 独立运行insertUser方法

我们的子方法 注解 ,事务失效了。

aop的原型是动态代理,在自调用的过程中,是类自身的调用,而不是代理对象去调用。那么就不会产生aop。(Spring就不能把你的代码织入到约定的流程中)

我们可以在抽一层

也可以从 ioc容器中获取 代理对象 去启动 Aop

@Service //集成上下文对象 aware
public class UserServiceImpl implements UserService, ApplicationContextAware {
   @Autowired
   private UserDao userDao = null;

   @Override
   @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW,timeout = 1)
   public int insertUser(User user) {
       return userDao.insertUser(user);
   }
   
   //读 提交事务oracle,只能读取到提交的。
   // 如果当前事务存在 就沿用当前事务
   //@Override
   @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
   public int insertUser(List<User> users) {
       int c=0;
       //从 IOC 容器中获取代理对象
       UserService bean = applicationContext.getBean(UserService.class);

       for(User u:users){
           //使用代理对象 调用方法插入用户,此时会织入 Spring 数据库事务流程中
           c+=bean.insertUser(u);
       }
       return c;
   }

   @Override
   @Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
   public User getUser(Long id) {
       return userDao.getUser(id);
   }


   private ApplicationContext applicationContext=null;
   @Override //用声明周期的方法,设置ioc容器
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
       this.applicationContext=applicationContext;
   }
   
}
  • bean = applicationContext.getBean(UserService.class); 获取到的是代理对象。
  • 它能够克服自调用的问题
  • 这样代码要依赖于 Spring的api ,造成了代码的侵入。在抽取一层更好。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值