【Spring基础系列4】注解@Transactional(一)

简介: 前面已经讲解了IOC的基础知识,以及Spring常用的注解,这篇文章是对上一篇文章《【Spring基础系列3】Spring常用的注解》的补充,由于这个注解需要讲述的内容比较多,一方面该注解非常重要,另一方面非常容易入坑,所以这个注解的内容,就单独放到这篇文章来讲。

Q}~`Y$[9U)A}I5ZG55(Z1EP.jpg

主要讲解注解@Transactional的基础知识、使用姿势,以及事务不生效的几种Case。


前言


前面已经讲解了IOC的基础知识,以及Spring常用的注解,这篇文章是对上一篇文章《【Spring基础系列3】Spring常用的注解》的补充,由于这个注解需要讲述的内容比较多,一方面该注解非常重要,另一方面非常容易入坑,所以这个注解的内容,就单独放到这篇文章来讲。


项目准备


为了更好通过示例讲解注解@Transactional的特性,本文会有大量的示例,这些示例是依赖如下配置和数据,如果只关注基础知识,可以跳过这一部分。

使用的是Mysql + Innodb存储引擎,事务隔离级别设置为可重复读RR。

pom.xml需要添加的依赖包:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.0</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>


使用Spring + MyBatis的方式对DB进行操作,下面是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.mybatis.dao.UserDao">
    <!-- 根据uid查询一个用户信息 -->
    <select id="selectUserById" parameterType="Integer" resultType="com.mybatis.entity.MyUser">
        select * from user_test where uid = #{uid}
    </select>
    <!--修改一个用户 -->
    <update id="updateUser" parameterType="com.mybatis.entity.MyUser">
        update user_test set uname =#{uname},usex = #{usex} where uid = #{uid}
    </update>
</mapper>


提供的接口:

@Repository("userDao")
@Mapper
/*
 * 使用Spring自动扫描MyBatis的接口并装配 (Spring将指定包中所有被@Mapper注解标注的接口自动装配为MyBatis的映射接口
 */
public interface UserDao {
    /**
     * 接口方法对应的SQL映射文件中的id
     */
    public MyUser selectUserById(Integer uid);
    public int updateUser(MyUser user);
}


DB结构:

CREATE TABLE `user_test` (
  `uid` tinyint(2) NOT NULL,
  `uname` varchar(20) DEFAULT NULL,
  `usex` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8


DB初始数据:

uid uname usex
1 张三
2 陈恒
3 楼仔


@Transactional基础知识


概述

  • @Transactional是声明式事务管理编程中使用的注解
  • 可以添加在接口实现类或接口实现方法上,而不是接口类中
  • 访问权限只有public的方法才起作用
  • 当接口与接口中方法上同时带有@Transactional注解时,方法上注解属性会覆盖类注解上的相同属性
  • 系统设计时,将标签放置在需要进行事务管理的方法上,而不是不假思索的放置在接口实现类上
  • 错误使用的方式,会在“事务不生效的几种case”中讲述
  • 多线程下事务管理,因为线程不属于spring托管,故线程不能够默认使用spring的事务,也不能获取spring注入的bean;在被spring声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制,具体可参考“事务不生效的几种case”中多线程事务不生效的场景。


使用姿势

讲解基础知识前,我们先看@Transactional怎么使用,下面是DB数据正常更新的情况:

@Controller("userController")
public class UserController {
    @Autowired
    private UserDao userDao;
    public void update(Integer id) {
        MyUser user = new MyUser();
        user.setUid(id);
        user.setUname("张三-testing"); // 变更数据
        user.setUsex("女");
        userDao.updateUser(user);
    }
    public MyUser query(Integer id) {
        MyUser user = userDao.selectUserById(id);
        return user;
    }
    @Transactional(rollbackFor = Exception.class)
    public void testSuccess() throws Exception {
        Integer id = 1;
        MyUser user = query(id);
        System.out.println("原记录:" + user);
        update(id);
        //throw new Exception("测试事务回滚生效");
    }
}


再看一下测试用例:

public static void main(String[] args) throws Exception {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserController uc = (UserController) applicationContext.getBean("userController");
    try {
        uc.testSuccess();
    } finally {
        MyUser user =  uc.query(1);
        System.out.println("修改后的记录:" + user);
    }
}
// 输出:
// 原记录:User[uid=1,uname=张三,usex=女]
// 修改后的记录:User[uid=1,uname=张三-test,usex=女]


我们发现DB更新成功,现在我们修改一下代码,将DB数据回退到原始状态,然后再把抛出异常放开,看事务是否生效:

public void update(Integer id) {
  MyUser user = new MyUser();
  user.setUid(id);
  user.setUname("张三-testing"); // 变更数据
  user.setUsex("女");
  userDao.updateUser(user);
}
@Transactional(rollbackFor = Exception.class)
public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
    throw new Exception("测试事务回滚生效");
}


再看看执行结果:

原记录:User[uid=1,uname=张三,usex=女]
修改后的记录:User[uid=1,uname=张三,usex=女]

我们发现因为程序抛出异常,DB数据正常回滚,符合预期。


实现原理

  • @Transactional实质是使用了JDBC的事务来进行事务控制的
  • @Transactional基于Spring的动态代理的机制 @Transactional实现原理:
  • 事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与DataSourceTransactionManager相关的某处容器中。在接下来的整个事务中,客户代码都应该使用该connection连接数据库,执行所有数据库命令[不使用该connection连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚](物理连接connection逻辑上新建一个会话session;DataSource与TransactionManager配置相同的数据源)
  • 事务结束时,回滚在第1步骤中得到的代理connection对象上执行的数据库命令,然后关闭该代理connection对象(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)


事务特性

pring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口:

public interface PlatformTransactionManager {
  TransactionStatus getTransaction(TransactionDefinition definition)
  throws TransactionException;
  void commit(TransactionStatus status) throws TransactionException;
  void rollback(TransactionStatus status) throws TransactionException;
}

事务的隔离级别:

  • @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用
  • @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
  • @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
  • @Transactional(isolation = Isolation.SERIALIZABLE):串行化

事务传播行为(如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为):

  • ransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。


属性配置

字段说明:

  • value :主要用来指定不同的事务管理器;主要用来满足在同一个系统中,存在不同的事务管理器。比如在Spring中,声明了两种事务管理器txManager1, txManager2。然后,用户可以根据这个参数来根据需要指定特定的txManager。value 适用场景,即在一个系统中,需要访问多个数据源或者多个数据库,则必然会配置多个事务管理器的
  • isolation:事务的隔离度,默认值采用 DEFAULT。
  • propagation:事务的传播行为,默认值为 REQUIRED,具体取值可参考“事务特性”:
  • 例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
  • readOnly:该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true)。
  • timeout:该属性用于设置事务的超时秒数,如果超过这个时间就强制回滚,默认值为-1表示永不超时。
  • 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"})
相关文章
|
18天前
|
Java 开发者 Spring
【SpringBoot 异步魔法】@Async 注解:揭秘 SpringBoot 中异步方法的终极奥秘!
【8月更文挑战第25天】异步编程对于提升软件应用的性能至关重要,尤其是在高并发环境下。Spring Boot 通过 `@Async` 注解简化了异步方法的实现。本文详细介绍了 `@Async` 的基本用法及配置步骤,并提供了示例代码展示如何在 Spring Boot 项目中创建与管理异步任务,包括自定义线程池、使用 `CompletableFuture` 处理结果及异常情况,帮助开发者更好地理解和运用这一关键特性。
85 1
|
27天前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
14天前
|
缓存 Java 数据库连接
Spring Boot奇迹时刻:@PostConstruct注解如何成为应用初始化的关键先生?
【8月更文挑战第29天】作为一名Java开发工程师,我一直对Spring Boot的便捷性和灵活性着迷。本文将深入探讨@PostConstruct注解在Spring Boot中的应用场景,展示其在资源加载、数据初始化及第三方库初始化等方面的作用。
42 0
|
26天前
|
Java 数据安全/隐私保护 Spring
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
|
27天前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
14天前
|
监控 安全 Java
【开发者必备】Spring Boot中自定义注解与处理器的神奇魔力:一键解锁代码新高度!
【8月更文挑战第29天】本文介绍如何在Spring Boot中利用自定义注解与处理器增强应用功能。通过定义如`@CustomProcessor`注解并结合`BeanPostProcessor`实现特定逻辑处理,如业务逻辑封装、配置管理及元数据分析等,从而提升代码整洁度与可维护性。文章详细展示了从注解定义、处理器编写到实际应用的具体步骤,并提供了实战案例,帮助开发者更好地理解和运用这一强大特性,以实现代码的高效组织与优化。
28 0
|
27天前
|
设计模式 Java 测试技术
公司为何禁止在SpringBoot中使用@Autowired注解?
【8月更文挑战第15天】在Spring Boot的广泛应用中,@Autowired注解作为依赖注入的核心机制之一,极大地简化了Bean之间的装配过程。然而,在某些企业环境下,我们可能会遇到公司政策明确禁止或限制使用@Autowired注解的情况。这一决策背后,往往蕴含着对代码质量、可维护性、测试便利性以及团队开发效率等多方面的考量。以下将从几个方面深入探讨这一决定的合理性及替代方案。
28 0
|
4月前
|
Java API Spring
Spring容器如何使用一个注解来指定一个类型为配置类型
Spring容器如何使用一个注解来指定一个类型为配置类型
44 0
|
3月前
|
XML Java 数据格式
Spring5系列学习文章分享---第三篇(AOP概念+原理+动态代理+术语+Aspect+操作案例(注解与配置方式))
Spring5系列学习文章分享---第三篇(AOP概念+原理+动态代理+术语+Aspect+操作案例(注解与配置方式))
34 0
|
3月前
|
Java Spring
spring基于注解配置数据源
spring基于注解配置数据源