Spring的事务使用教程

什么是事务?

事务(Transaction)是数据库操作最基本单元,逻辑上一组操作,要么都成功,要么都失败,如果操作之间有一个失败所有操作都失败 。

事务四个特性(ACID)

  • 原子性
    一组操作要么都成功,要么都失败。
  • 一致性
    一组数据从事务1合法状态转为事务2的另一种合法状态,就是一致。
  • 隔离性
    事务1操作数据,不影响事务2的操作,每一个事务之间都是隔离状态。
  • 持久性
    数据从内存加载到磁盘文件系统中,就是持久化。

事务的传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

传播行为 描述
REQUIRED 如果有事务在运行,当前的方法就在这个事务中运行,否则,启动新事务并在其中运行
REQUIRED_NEW 当前的方法必须启动新事务,并在它自己的事务内运行,如果有其它事务正在运行,则将它挂起
SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
NOT_SUPPORTED 当前的方法不应该运行在事务中,如果有事务在运行,将它挂起
MANDATORY 当前方法必须运行在事务内,如果没有正在运行的事务,就抛出异常
NEVER 当前的方法不应该运行在事务内,如果有运行的事务,就抛出异常
NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动新事务,在其内部运行

事务的隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
隔离级别一共有四种:

  • 读未提交:READ UNCOMMITTED
    允许Transaction01读取Transaction02未提交的修改。

  • 读已提交:READ COMMITTED
    要求Transaction01只能读取Transaction02已提交的修改。

  • 可重复读:REPEATABLE READ
    确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

  • 串行化:SERIALIZABLE
    确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

事务的特性称为隔离性,多事务操作之间不会产生影响。不考虑隔离性会产生很多问题。
主要有三个读问题:脏读、不可重复读、虚(幻)读:

  • 脏读:一个未提交事务读取到另一个未提交事务的数据
    脏读是指一个事务在处理数据的过程中,读取到另一个未提交事务的数据。这可能导致读取到的是一个未提交的数据,而这些数据可能在后续的操作中被回滚,从而造成数据的不一致或丢失。脏读又称为无效数据读出,因为它可能读到的是一个最终不会被保存到数据库中的数据。
  • 不可重复读:一个未提交事务读取到另一提交事务修改数据
    不可重复读(Non-Repeatable Read, NRR)是指在某个事务内的多次读取操作中,由于其他事务的并发修改导致该事务读取到的数据状态发生变化,从而不能保持数据的可见性。具体来说,当一个事务多次读取相同的数据,但在每次读取之间,其他事务对其进行了修改,使得该事务在两次读取之间的数据状态不一致,这就是不可重复读的情况。
  • 幻读:一个未提交事务读取到另一提交事务添加数据
    幻读(Phantom Read, PHR)是指在某个事务的执行过程中,它依据某些查询条件读取了一些记录,随后另一个事务在该查询条件下的插入操作导致原本不会出现在结果集中的记录也被读取出来了。这种情况通常发生在事务A首先读取了一批符合特定查询条件的记录,然后在事务B插入新的记录之前,事务A再次读取这些记录,这次读取的结果包含了新插入的记录,这显然超出了事务A原来的预期,这就是所谓的幻读。
各个隔离级别解决并发问题的能力见下表:

image

各种数据库产品对事务隔离级别的支持程度:

image

设置事务的隔离级别
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化

timeout:超时时间

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。

此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常 程序可以执行。

概括来说就是一句话:超时回滚,释放资源。

默认值是 -1 ,设置时间以秒单位进行计算。
举例:

@Transactional(timeout = 3)
public void buyBook(Integer bookId, Integer userId) {
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    //System.out.println(1/0);
}

如果超时,则抛出异常:

org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022

readOnly:是否只读

对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。
readOnly 默认值 false,表示可以查询,可以添加修改删除操作。

设置 readOnly 值是 true,设置成 true 之后,只能查询。
举例:

@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) {
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    //System.out.println(1/0);
}

对增删改操作设置只读会抛出下面异常:

Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

rollback:回滚策略

声明式事务默认只针对运行时异常回滚,编译时异常不回滚。
可以通过@Transactional中相关属性设置回滚策略:

  • rollbackFor属性:设置出现哪些异常进行事务回滚
  • noRollbackFor属性:设置出现哪些异常不进行事务回滚
    举例:
@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void buyBook(Integer bookId, Integer userId) {
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    System.out.println(1/0);
}

虽然购买图书功能中出现了数学运算异常ArithmeticException,但是我们设置的回滚策略是,当出现ArithmeticException不发生回滚,因此购买图书的操作正常执行。

Spring事务管理操作

  1. 事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
  2. 在 Spring 进行事务管理操作
    1. 有两种方式:编程式事务管理和声明式事务管理
  3. 声明式事务管理
    1. 基于注解方式
    2. 基于 xml 配置文件方式
  4. 在 Spring 进行声明式事务管理,底层使用 AOP 原理
  5. Spring 事务管理 API
    提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类

    image

声明式事务:基于注解方式

既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出 来,进行相关的封装。

封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。

  • 好处1:提高开发效率
  • 好处2:消除了冗余的代码
  • 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性 能等各个方面的优化
    所以,我们可以总结下面两个概念:
  • 编程式:自己写代码实现功能
  • 声明式:通过配置让框架实现功能
准备工作

创建数据表

CREATE TABLE `t_account` (
  `id` int NOT NULL,
  `username` varchar(15) NOT NULL,
  `money` decimal(10,0) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

创建jdbc.properties文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/dbtest1?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

配置Spring的配置文件

<?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" xmlns:tx="http://www.springframework.org/schema/tx"
       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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--开启组件扫码-->
    <context:component-scan base-package="com.evan.spring5"/>

    <!-- 引入外部配置文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 配置数据源 -->
    <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 配置jdbc模板 -->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

创建实体类、service类和dao类

//实体类
public class Account {

    private int id;
    private String username;
    private BigDecimal money;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public BigDecimal getMoney() {
        return money;
    }

    public void setMoney(BigDecimal money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", money=" + money +
                '}';
    }
}
//dao接口
public interface AccountDao {

    void reduceMoney();

    void addMoney();
}
//dao接口实现类
@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Override
    public void reduceMoney() {
        String sql = "update t_account set money = money - ? where username = ?";
        jdbcTemplate.update(sql,100,"luck");
    }

    @Override
    public void addMoney() {
        String sql = "update t_account set money = money + ? where username = ?";
        jdbcTemplate.update(sql,100,"mary");
    }
}
//service类
@Service
public class AccountService {
    @Autowired
    private AccountDao accountDao;


    public void accountMoney() {
        accountDao.reduceMoney();

        int number = 10 / 0;

        accountDao.addMoney();
    }
}
测试

此时在没有开启事务的情况下进行转账测试,可以看出在转账过程中出现异常,转账金额不一致,此时需要事务来解决,当出现转账中发送异常就会立即立即回滚数据,保证数据的一致性。

开启事务

  1. 在 spring 配置文件配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager" 		
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 <!--注入数据源-->
 <property name="dataSource" ref="dataSource"></property>
</bean>
  1. 在 spring 配置文件,开启事务注解
  • 引入命名空间tx
<beans xmlns="http://www.springframework.org/schema/beans" 
 	   xmlns:tx="http://www.springframework.org/schema/tx" 
       xsi:schemaLocation="http://www.springframework.org/schema/tx 
                           http://www.springframework.org/schema/tx/spring-tx.xsd"
  • 开启事务注解
<!--
开启事务的注解驱动
通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就
是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
  1. 添加事务注解
    因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理。

在 service 类上面(或者 service 类里面方法上面)添加事务注解:

(1) @Transactional这个注解添加到类上面,也可以添加方法上面

(2) 如果把这个注解添加类上面,这个类里面所有的方法都会受到影响

(3) 如果把这个注解添加方法上面,则只会影响该方法

@Service
@Transactional(propagation = Propagation.REQUIRED) //Spring默认行为
public class AccountService {

    @Autowired
    private AccountDao accountDao;


    public void accountMoney() {
        accountDao.reduceMoney();

        int number = 10 / 0;

        accountDao.addMoney();
    }
}
测试

此时开始事务后,测试上述转账过程中出现数据不一致的问题,经测试转账过程出现问题,事务回滚操作,数据没有变化。

声明式事务:基于XML方式

修改Spring配置文件
将Spring配置文件中去掉tx:annotation-driven标签,并添加配置:

  • 第一步 配置事务管理器
  • 第二步 配置通知
  • 第三步 配置切入点和切面
<!-- 引入外部properties -->
<context:property-placeholder location="jdbc.properties"/>
<!-- 开启组件扫描 -->
<context:component-scan base-package="cn.evan.spring5"/>
<!-- 创建数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <!--注入数据源-->
     <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 配置事务通知-->
<tx:advice id="txadvice">
     <!--配置事务属性参数-->
     <tx:attributes>
        <!-- 设置在哪些方法上配置相关事务 -->
        <!-- tx:method标签:配置具体的事务方法 -->
        <!-- name属性:指定方法名,可以使用星号代表多个字符 -->
        <tx:method name="get*" read-only="true"/>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="find*" read-only="true"/>
        <!-- read-only属性:设置只读属性 -->
        <!-- rollback-for属性:设置回滚的异常 -->
        <!-- no-rollback-for属性:设置不回滚的异常 -->
        <!-- isolation属性:设置事务的隔离级别 -->
        <!-- timeout属性:设置事务的超时属性 -->
        <!-- propagation属性:设置事务的传播行为 -->
        <tx:method name="save*" read-only="false" rollbackfor="
java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="update*" read-only="false" rollbackfor="
java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="delete*" read-only="false" rollbackfor="
java.lang.Exception" propagation="REQUIRES_NEW"/>
     </tx:attributes>
</tx:advice>

<!-- 配置切入点和切面-->
<aop:config>
     <!--配置切入表达式-->
    <aop:pointcut id="pt" expression="execution(* 
                                 com.atguigu.spring5.service.UserService.*(..))"/>
     <!--配置事务通知切面-->
     <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>

声明式事务:纯注解方式

创建配置类,使用配置类替代 xml 配置文件。

@Configuration //配置类
@ComponentScan(basePackages = "com.atguigu") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
     //创建数据库连接池
     @Bean
     public DruidDataSource getDruidDataSource() {
          DruidDataSource dataSource = new DruidDataSource();
          dataSource.setDriverClassName("com.mysql.jdbc.Driver");
         dataSource.setUrl("jdbc:mysql:///user_db");
         dataSource.setUsername("root");
         dataSource.setPassword("root");
         return dataSource;
     }
    
     //创建 JdbcTemplate 对象
     @Bean
     public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
         //到 ioc 容器中根据类型找到 dataSource
         JdbcTemplate jdbcTemplate = new JdbcTemplate();
         //注入 dataSource
         jdbcTemplate.setDataSource(dataSource);
         return jdbcTemplate;
     }
    
     //创建事务管理器
     @Bean
     public DataSourceTransactionManager 
         getDataSourceTransactionManager(DataSource dataSource) {
         DataSourceTransactionManager transactionManager = 
             			new DataSourceTransactionManager();
         transactionManager.setDataSource(dataSource);
         return transactionManager;
     }
}

原文地址:https://www.cnblogs.com/lisong0626/p/17986175

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


开发过程中是不可避免地会出现各种异常情况的,例如网络连接异常、数据格式异常、空指针异常等等。异常的出现可能导致程序的运行出现问题,甚至直接导致程序崩溃。因此,在开发过程中,合理处理异常、避免异常产生、以及对异常进行有效的调试是非常重要的。 对于异常的处理,一般分为两种方式: 编程式异常处理:是指在代
说明:使用注解方式实现AOP切面。 什么是AOP? 面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能。 AOP底层使用动态代理。 AOP术语 连接点
Spring MVC中的拦截器是一种可以在请求处理过程中对请求进行拦截和处理的机制。 拦截器可以用于执行一些公共的操作,例如日志记录、权限验证、数据转换等。在Spring MVC中,可以通过实现HandlerInterceptor接口来创建自定义的拦截器,并通过配置来指定拦截器的应用范围和顺序。 S
在 JavaWeb 中,共享域指的是在 Servlet 中存储数据,以便在同一 Web 应用程序的多个组件中进行共享和访问。常见的共享域有四种:ServletContext、HttpSession、HttpServletRequest、PageContext。 ServletContext 共享域:
文件上传 说明: 使用maven构建web工程。 使用Thymeleaf技术进行服务器页面渲染。 使用ResponseEntity实现下载文件的功能。 @Controller public class FileDownloadAndUpload { @GetMapping(&quot;/file/d
创建初始化类,替换web.xml 在Servlet3.0环境中,Web容器(Tomcat)会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器。 Spring提供了这个接口的实现,名为SpringS
在 Web 应用的三层架构中,确保在表述层(Presentation Layer)对数据进行检查和校验是非常重要的。正确的数据校验可以确保业务逻辑层(Business Logic Layer)基于有效和合法的数据进行处理,同时将错误的数据隔离在业务逻辑层之外。这有助于提高系统的健壮性、安全性和可维护
什么是事务? 事务(Transaction)是数据库操作最基本单元,逻辑上一组操作,要么都成功,要么都失败,如果操作之间有一个失败所有操作都失败 。 事务四个特性(ACID) 原子性 一组操作要么都成功,要么都失败。 一致性 一组数据从事务1合法状态转为事务2的另一种合法状态,就是一致。 隔离性 事
什么是JdbcTemplate? Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作。 准备工作 引入jdbcTemplate的相关依赖: 案例实操 创建jdbc.properties文件,配置数据库信息 jdbc.driver=com.mysql.cj.
SpringMVC1.MVC架构MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范是将业务逻辑、数据、显示分离的方法来写代码MVC主要作用是:降低了视图和业务逻辑之间的双向耦合MVC是一个架构模型,不是一种设计模式。1.model(模型)数据模型,提供要展示的数据,因此包
SpringMVC学习笔记1.SpringMVC应用1.1SpringMVC简介​SpringMVC全名叫SpringWebMVC,是⼀种基于Java的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于SpringFrameWork的后续产品。​MVC全名是ModelViewController,是模型(model)-视图(view)-控制器(co
11.1数据回显基本用法数据回显就是当用户数据提交失败时,自动填充好已经输入的数据。一般来说,如果使用Ajax来做数据提交,基本上是没有数据回显这个需求的,但是如果是通过表单做数据提交,那么数据回显就非常有必要了。11.1.1简单数据类型简单数据类型,实际上框架在这里没有
一、SpringMVC简介1、SpringMVC中重要组件DispatcherServlet:前端控制器,接收所有请求(如果配置/不包含jsp)HandlerMapping:解析请求格式的.判断希望要执行哪个具体的方法.HandlerAdapter:负责调用具体的方法.ViewResovler:视图解析器.解析结果,准备跳转到具体的物
1.它们主要负责的模块Spring主要应用于业务逻辑层。SpringMVC主要应用于表现层。MyBatis主要应用于持久层。2.它们的核心Spring有三大核心,分别是IOC(控制反转),DI(依赖注入)和AOP(面向切面编程)。SpringMVC的核心是DispatcherServlet(前端控制器)。MyBatis的核心是ORM(对
3.注解开发Springmvc1.使用注解开发要注意开启注解支持,2.注解简化了,处理映射器和处理适配器,只用去管视图解析器即可案例代码:1.web.xml,基本不变可以直接拿去用<!--调用DispatcherServlet--><servlet><servlet-name>springmvc</servlet-name>
拦截器概述SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。**过滤器与拦截器的区别:**拦截器是AOP思想的具体应用。过滤器servlet规范中的一部分,任何javaweb工程都可以使用
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:xsi="
学习内容:1、SSH&SSM2、Spring3、Struts2&SpringMVC4、Hibernate&MyBatis学习产出:1.SSH和SSM都是有Spring框架的,他们两个差不多。2.Spring分为四个模块,持久层,表示层,检测层,还有核心层,核心层分为2个关键核心功能。分别为,控制反转(IOC),依赖注入(DI),和面向切面编程
一、SpringMVC项目无法引入js,css的问题具体原因是css和js等被SpringMVC拦截了:解决方案:在spring-mvc.xml中配置<mvc:default-servlet-handler/><?xmlversion="1.0"encoding="UTF-8"?><beansxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
开发环境:Eclipse/MyEclipse、Tomcat8、Jdk1.8数据库:MySQL前端:JavaScript、jQuery、bootstrap4、particles.js后端:maven、SpringMVC、MyBatis、ajax、mysql读写分离、mybatis分页适用于:课程设计,毕业设计,学习等等系统介绍