[Spring] Spring5——事务简介

news2024/10/6 10:28:41

目录

一、事务概述

1、什么是事务

2、事务的四个特性(ACID)

二、搭建事务操作环境

1、dao、service 两层结构

2、示例

3、模拟异常(事务场景引入)

三、Spring 事务管理

1、事务管理介绍

2、声明式事务管理——注解方式

3、声明式事务管理——注解的参数配置

4、声明式事务管理——XML方式

5、声明式事务管理——完全注解开发


一、事务概述

1、什么是事务

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

2、事务的四个特性(ACID)

(1)原子性

  • 要么都成功,要么全失败

(2)一致性

  • 事务只能把数据库从一个有效(正确)的状态“转移”到另一个有效(正确)的状态

(3)隔离性

  • 多事务操作时,事务之间不会相互影响

(4)持久性

  • 即使发生了异常情况,数据库中的数据也应该能够被恢复,并且不会丢失或损坏

二、搭建事务操作环境

通过模拟银行转账这一例子,来演示在 Spring5 中如何进行事务操作

1、dao、service 两层结构

JavaEE 中有三层结构,分别是:web、service、dao。其中数据库操作由 dao 实现,业务逻辑由 service 实现,因此我们主要关注这两层结构。

收入和支出的方法都执行后,在构成转账的方法,才是一个完整操作。

2、示例

(1)创建数据库、表,添加记录

(2)创建 UserDao 和 UserService,并完成注入

(3)在 dao 中创建两个方法:收入和支出

package com.demo.dao.impl;

import com.demo.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;

@Repository
public class UserDaoImpl implements UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int income(String username, BigDecimal dif) {
        String sql = "update \"BankUser\" set balance=balance+? where username=?";
        return jdbcTemplate.update(sql, dif, username);
    }

    @Override
    public int expense(String username, BigDecimal dif) {
        String sql = "update \"BankUser\" set balance=balance-? where username=?";
        return jdbcTemplate.update(sql, dif, username);
    }
}

(4)在 service 中创建方法:转账

package com.demo.service.impl;

import com.demo.dao.UserDao;
import com.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public void remittance(String name1, String name2, BigDecimal dif) {
        // name1 减少 dif
        userDao.expense(name1, dif);
        // name2 增加 dif
        userDao.income(name2, dif);
    }
}

(5)测试代码

import com.demo.pojo.User;
import com.demo.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.math.BigDecimal;

public class remittanceTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("JdbcTemplate.xml");
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        userService.remittance("wyt", "gyt", new BigDecimal(25));
    }
}

(6)运行结果

3、模拟异常(事务场景引入)

假设在转出一方转出后,出现了异常,那么按照事务的特性,就应当将操作回滚

而在上面的代码中,就会使得转出一方出了钱,而接收一方没有收到钱。

三、Spring 事务管理

1、事务管理介绍

(1)由于异常一般出现在业务逻辑处理,所以一般将事务管理放在 service 层

(2)Spring 事务管理有两种方式:

  • 编程式事务管理(一般不用),前面的模拟异常就是使用编程式;(编程式事务管理会造成代码冗余,一定要用编程式的话,可以使用 filter 来全局捕获异常)
  • 声明式事务管理(一般使用);

(3)声明式事务管理有两种实现方式:

  • 基于 xml 配置文件方式;(一般不用)
  • 基于注解方式;(一般使用)

(4)在 Spring 进行声明式事务管理时,需要使用 AOP 原理

(5)Spring 事务管理 API

  • 提供接口:PlatformTransactionManager,代表事务管理器,这个接口针对不同的框架提供不同的实现类

2、声明式事务管理——注解方式

(1)在 spring 配置文件中配置事务管理器

  • 事务管理器同样使用 set 方法赋值 dataSource,因此也需要使用 <property>。
<!-- 创建事务管理器的对象 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

(2)在 spring 配置文件中开启事务注解

  • 在配置文件中,引入名称空间 tx;
xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
  • 开启事务注解,指定我们配置的事务管理器;
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

(3)为 service 类或 service 类方法添加事务注解 @Transactional

  • 如果把 @Transactional 添加类上面,这个类里面所有的方法都添加事务;
  • 如果把 @Transactional 添加类方法上面,仅为这个方法添加事务;

(4)输出结果

  • 测试代码和前面示例的一样,而此次没有出现转出人少钱、接收人没有多钱的情况了,说明事务回滚成功。

3、声明式事务管理——注解的参数配置

在声明式的事务处理中,要配置一个切面,其中就用到了 propagation 等多个参数。需要明确的是,他们本质上都是对类方法被调用时的一种描述

(1)propagation 事务传播行为

propagation 表示这些方法怎么使用事务,是用还是不用。其常用属性有 3 种:

  • REQUIRED:支持(加入)当前事务,如果当前没有事务,就新建一个事务。(默认、常用)
  • REQUIRED_NEW:必须新建事务,如果当前存在事务,把当前事务挂起
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
@Transactional(propagation = Propagation.REQUIRED)

(2)isolation 事务隔离级别

多事务的并发操作容易引发读问题,事务的隔离级别会产生不同的读问题:脏读、不可重复读、幻读。

(2-1)名词解释 

  • 脏读(脏数据):事务 A 修改了数据 D,但还未提交,就被事务 B 读取了,如果此时 A 进行了回滚,那么 B 读到的数据就是脏数据。依据脏数据所做的操作很可能不正确。
  • 不可重复读:事务 A 需要多次读取数据 D,如果在 A 下一次读取 D 之前,事务 B 修改了 D 并提交,那么 A 前后读取到的数据就不一致了。此情况称为不可重复读。
  • 幻觉读:一个未提交的事务 A,读取到了另一个已经提交的事务 B 所添加的数据 D。

(2-2)设置事务隔离级别解决读问题

  • 默认一般是:REPEATABLE READ,可重复读。

(2-3)示例 

@Transactional(isolation =  Isolation.SERIALIZABLE)

(3)timeout 超时时间

  • 事务需要在一个规定时间内完成提交,如果超时则会回滚。默认值是 -1 ,设置时间以秒单位进行计算。

(4)readOnly 是否只读

  • 读:查询操作,写:添加修改删除操作;
  • readOnly 默认值 false,表示可以查询,也可以进行添加修改删除操作;
  • 设置 readOnly 值是 true 之后,只能查询;

(5)rollbackFor 回滚

  • 设置出现哪些异常,则进行回滚

(6)noRollbackFor 不回滚

  • 设置出现哪些异常,不进行回滚

4、声明式事务管理——XML方式

(1)配置事务管理器

与注解方式配置事务管理器一致。

<!-- 创建事务管理器的对象 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

(2)配置通知

在 AOP 中讲过,通知就是增强的逻辑部分,加上源代码部分,构成动态代理。

<!-- 配置通知,事务就是增强部分,就是通知 -->
<tx:advice id="txAdvice">
    <!-- 配置事务参数 -->
    <tx:attributes>
        <!-- 将事务添加在符合规则的方法上 -->
        <tx:method name="remittance" isolation="REPEATABLE_READ"/> <!-- 还可以设置隔离级别等属性 -->
        <tx:method name="remit*"/> <!-- 以 remit 开头的方法,都符合规则 -->
    </tx:attributes>
</tx:advice>

(3)配置切入点和切面

把事务加到哪些类、哪些方法。作用与注解方式的 @Transactional 一致

<!-- 配置切入点和切面 -->
<aop:config>
    <!-- 配置切入点 -->
    <aop:pointcut id="pc" expression="execution(* com.demo.service.impl.UserServiceImpl.*(..))"/>
    <!-- 配置切面 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>

5、声明式事务管理——完全注解开发(详细)

前面的注解方式中,还是有一部分需要编写配置文件。

(1)创建数据库配置文件

prop.driverClassName = org.postgresql.Driver
prop.url = jdbc:postgresql://localhost:5432/MyDatabase
prop.username = postgres
prop.password = 123456

(2)创建 jdbc 配置类,替代 xml 配置文件中的数据库连接池

  • @Bean:https://blog.csdn.net/lazy_zzzzzz/article/details/94464337
  • @PropertySource:用于加载 Properties 属性配置文件,它需要指定配置文件所在的路径和文件名称。(classpath 详见:https://blog.csdn.net/y_chengbo/article/details/110118195)
  • @Value:用于将基本数据类型的值注入到成员变量,它等价于 <property> 中的 value 属性。 在指定值时如果值是变量,则需要使用 ${变量名称} 的方式注入,如果不使用 ${} 的话则把字符赋值给变量
package com.demo.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.sql.Connection;

@PropertySource("classpath:dataSource.properties")
public class JdbcConfig {
    @Value("${prop.driverClassName}")
    private String driver;

    @Value("${prop.url}")
    private String url;

    @Value("${prop.username}")
    private String username;

    @Value("${prop.password}")
    private String password;

    // 创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    // 创建 jdbcTemplate 对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        // 到 IOC 容器中,根据类型找到 dataSource
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        // 注入 dataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    // 创建 connection 对象
    @Bean // 写了 @Bean,那么就会到 IOC 容器中寻找对应对象
    public Connection getConnection(@Autowired DataSource dataSource) {
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return conn;
    }

}

(3)创建 transaction 配置类,替代 xml 中的事务管理

  • @EnableTransactionManagement:开启事务
package com.demo.config;

import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@EnableTransactionManagement // 开启事务
public class TransactionConfig {
    // 创建事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

(4)创建 Spring 配置类

  • @Configuration: 表示当前的类是一个配置类,它就相当于 Spring 的 xml 配置文件
  • @ComponentScan:注解扫描,用于扫描指定包下的哪些类添加了 @Component、@Service、@Repository、@Controller、@Autowired 等注解  
  • @Import: 导入别的配置类
package com.demo.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan({"com.demo"})
@Import({JdbcConfig.class})
public class SpringConfig {
    
}

(5)测试代码

import com.demo.config.transactionConfig;
import com.demo.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.math.BigDecimal;

public class remittanceTest {

    @Test
    public void completeAnnotation() {
        ApplicationContext context = new AnnotationConfigApplicationContext(transactionConfig.class);
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        userService.remittance("wyt", "gyt", new BigDecimal(25));
    }
}

(6)运行结果

原本两个 user 的 balance 都是 50,现在转账了 25:

若给转账功能手动引入一个异常,观察事务管理是否起作用:

 

显然遇到异常成功回滚,没有破坏一致性。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1063154.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++ 学习 之 继承的基本语法

继承可以减少重复的代码 语法&#xff1a; class 子类 : 继承方式 父类子类 也称为 派生类 父类 也称为 基类 class BasePage { public:void header(){cout << "首页&#xff0c;公开课&#xff0c;登录&#xff0c;注册。。。&#xff08;公共头部&#xff09…

C/C++——内存管理

1.为什么存在动态内存分配 灵活性 静态内存分配是在编译时确定的&#xff0c;程序执行过程中无法改变所分配的内存大小&#xff1b;动态内存分配可以根本程序的运行环境来动态分配和释放空间&#xff0c;提供了更大的灵活性 动态数据结构 有些数据结构的大小和结构在编译时…

input输入多行文本:删除“首先 其次 此外 总的来说”

input允许多行输入 233.3表示停止输入input输入多行文本文本 &#xff08;空行&#xff09; &#xff08;空行&#xff09; &#xff08;空行&#xff09; 正文 &#xff08;空行&#xff09; &#xff08;空行&#xff09; &#xff08;空行&#xff09; 正文 &#xff08;空行…

国庆节:不仅仅是庆祝,更是成长与体验

目录 国庆节&#xff1a;不仅仅是庆祝&#xff0c;更是成长与体验引言第一部分&#xff1a;旅途风景目的地选择旅行亮点与国庆的联系 技术主题完成的博文国庆与技术 第三部分&#xff1a;回家的路为什么回家艰难险阻家与国庆 结论 国庆节&#xff1a;不仅仅是庆祝&#xff0c;更…

【Spring笔记02】Spring中的IOC容器和DI依赖注入介绍

这篇文章&#xff0c;主要介绍一下Spring中的IOC容器和DI依赖注入两个概念。 目录 一、IOC控制反转 1.1、什么是IOC 1.2、两种IOC容器 &#xff08;1&#xff09;基于BeanFactory的IOC容器 &#xff08;2&#xff09;基于ApplicationContext的IOC容器 二、DI依赖注入 2.…

Vue MVVM 模型

一、什么事MVVM 模型 MVVM 是 Model-View-ViewModel 的缩写&#xff0c;它是一种软件架构风格 Model&#xff1a;模型&#xff0c; 数据对象&#xff08;data 函数&#xff09;&#xff0c;如下图 View&#xff1a;视图&#xff0c;模板页面&#xff08;用于渲染数据&#xf…

掌握Mac菜单栏,尽在Bartender 5!菜单栏图标管理软件的终极推荐!

作为Mac用户&#xff0c;菜单栏是我们每天使用电脑时最常接触的区域之一。然而&#xff0c;随着我们安装越来越多的应用程序&#xff0c;菜单栏上的图标往往变得拥挤不堪&#xff0c;给我们的工作和生活带来了不便。 幸运的是&#xff0c;有了Bartender 5这款强大的菜单栏图标…

数据结构与算法(Python)

数据结构与算法 算法基础时间复杂度空间复杂度 递归实例&#xff1a;汉诺塔问题 查找顺序查找&#xff08;线性查找&#xff09;二分查找&#xff08;折半查找&#xff09;比较 排序冒泡排序选择排序插入排序快速排序快排和冒泡的时间比较 堆排序树堆堆的向下调整 堆排序过程时…

除静电设备的工作原理及应用

除静电设备主要包括静电消除器、静电接地装置、静电消除风机等&#xff0c;它们的工作原理和应用如下&#xff1a; 静电消除器&#xff1a;静电消除器的工作原理是利用电离和电击的原理来中和电荷。它包括一个金属板和一个高压电源。当静电消除器接通电源后&#xff0c;金属板…

Redis最常见应用场景

缓存&#xff08;Cache&#xff09; Redis的第一个应用场景是Redis作为缓存对象来加速Web应用的访问。 在该场景下&#xff0c;有一些存储于数据库中的数据会被频繁访问&#xff0c;如果频繁的访问数据库&#xff0c;数据库负载会升高&#xff0c;同时由于数据库IO比较慢&…

阿里云服务器更换公网IP地址的方法流程

阿里云服务器可以更换IP地址吗&#xff1f;可以的&#xff0c;创建6小时以内的云服务器ECS可以免费更换三次公网IP地址&#xff0c;超过6小时的云服务器&#xff0c;可以将公网固定IP地址转成弹性EIP&#xff0c;然后通过换绑EIP的方式来更换IP地址。阿里云服务器网分享阿里云服…

阿里云服务器地域节点怎么选择合适?啥是可用区?

阿里云服务器地域和可用区怎么选择&#xff1f;地域是指云服务器所在物理数据中心的位置&#xff0c;地域选择就近选择&#xff0c;访客距离地域所在城市越近网络延迟越低&#xff0c;速度就越快&#xff1b;可用区是指同一个地域下&#xff0c;网络和电力相互独立的区域&#…

基于遗传算法的新能源电动汽车充电桩与路径选择(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

JavaScript系列从入门到精通系列第十六篇:JavaScript使用函数作为属性以及枚举对象中的属性

文章目录 前言 1&#xff1a;对象属性可以是函数 2&#xff1a;对象属性函数被称为方法 一&#xff1a;枚举对象中的属性 1&#xff1a;for...in 枚举对象中的属性 前言 1&#xff1a;对象属性可以是函数 对象的属性值可以是任何的数据类型&#xff0c;也可以是函数。 v…

RHEL - 订阅、注册系统和 Yum Repository

《OpenShift / RHEL / DevSecOps 汇总目录》 演示环境说明 本文需要有 redhat.com 账号以及包含 RHEL 的有效订阅。 演示环境使用了通过 minimal 方式安装的 RHEL 7.6 环境&#xff0c;RHEL 可以访问互联网。 注册和注销 RHEL 系统 在 RHEL 中执行以下命令查看当前 RHEL 版…

第P8周—YOLOv5-C3模块实现

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/Nb93582M_5usednAKp_Jtw) 中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制](https://mtyjkh.blog.csdn.net/)** >- **&#x1f680;…

transformer不同的包加载模型的结构不一样

AutoModel AutoModelForTokenClassification 结论&#xff1a; AutoModel加载的模型与AutoModelForTokenClassification最后一层是不一样的&#xff0c;从这个模型来看&#xff0c;AutoModelForTokenClassification加载的结果是对的 问题&#xff1a; 为什么AutoModel和Aut…

网络探索之浏览器解析URL

目录 解析URL 特殊情况&#xff1a; 请求响应格式说明 多次请求 解析URL 浏览器是一个具备很多功能的计算机&#xff0c;不仅仅是访问网络浏览信息&#xff0c;使用具体哪个功能是通过url的开头进行区分的&#xff0c;这部分也叫做协议。 协议类型举例&#xff1a; 1.使用…

Windowsold文件夹作用以及删除方法

引言 2021年6月24日&#xff0c;微软正式发布全新操作系统Windows 11。Windows 11系统于2021年10月5日开始全面推送。2021年10月以后生产的电脑已经预装Windows 11系统。刚开始会有一部分人不适应win 11系统&#xff0c;会选择退回win10。现在win11已经推出了稳定版&#xff0…

Gmail 将停止支持基本 HTML 视图

根据 Google 支持文档的更新内容&#xff0c;Gmail 将从明年 1 月起停止支持基本 HTML 视图。 ▲ Gmai 基本 HTML 视图界面 目前网页版 Gmail 提供两个界面&#xff1a;基本 HTML 视图和标准视图。停止支持基本 HTML 视图后&#xff0c;当前打开经典模式的基本 HTML 视图模式 …