后端:事务

文章目录

    • 1. 事务
    • 2. Spring 单独配置DataSource
    • 3. 利用JdbcTemplate操作数据库
    • 4. 利用JdbcTemplate查询数据
    • 5. Spring 声明式事务
    • 6. 事务的隔离级别
      • 6.1 脏读
      • 6.2 不可重复读
      • 6.3 幻读
      • 6.4 不可重复读和幻读的区别
      • 6.5 三种方案的比较
    • 7. 事务的传播特性
    • 8. 设置事务 只读(readOnly)
    • 9. 超时属性(timeout)
    • 10. 异常属性
    • 11. 事务失效原因

1. 事务

一组关联的数据操作;要么全部成功,要么全部失败。
事务的四大特性:原子性、一致性、隔离性、持久性,即ACID。

  • 原子性:指一组业务操作下,要么全部成功,要么全部失败;
  • 一致性:在业务操作下,前后的数据保持一致;
  • 隔离性:并发操作下,事务之间要相互隔离;
  • 持久性:数据一旦保存,那么就是持久存在的。

2. Spring 单独配置DataSource

在Spring Boot项目中不需要单独配置DataSource,只需要在配置文件添加对应的数据库连接配置即可,在Spring项目中需要单独配置。
已经在application.properties文件做了相关数据库连接配置,为了在单元测试类中避免Spring Boot自动配置DataSource的影响,在单元测试类下新建spring文件夹,在这个文件夹下面新建相关类,用以演示Spring 单独配置DataSource,如下:
在这里插入图片描述
只要不要和启动类同在一个目录下,Spring Boot项目中DataSource就不会自动配置的,配置文件中的配置如下:

# 数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/mytest1
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

定义DataSource这个Bean的配置类代码如下:

package com.example.spring_database_1.spring;import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;import javax.sql.DataSource;@Configuration
public class MyConfiguration {@Value("${spring.datasource.url}")private String url;@Value("${spring.datasource.username}")private String usr;@Value("${spring.datasource.password}")private String pwd;@Value("${spring.datasource.driver-class-name}")private String driver;@Beanpublic DataSource dataSource(){DriverManagerDataSource dataSource = new DriverManagerDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(usr);dataSource.setPassword(pwd);return dataSource;}
}

单元测试类:

package com.example.spring_database_1.spring;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;import javax.sql.DataSource;
import java.sql.SQLException;@SpringBootTest(classes =SpringDataSourceTest.class)
@ComponentScan
public class SpringDataSourceTest {@Testpublic void test1(@Autowired DataSource dataSource) throws SQLException {System.out.println(dataSource.getConnection());}
}

运行结果:
@SpringBootTest(classes =SpringDataSourceTest.class)表示当前这个类为单元测试类,并且还是一个启动类,不会和Spring Boot那个启动类产生关联,@ComponentScan表示自动扫描,没有指定的话默认是扫描当前包下的所有。
在这里插入图片描述

3. 利用JdbcTemplate操作数据库

在Spring项目中需要配置JdbcTemplate这个Bean,参考代码如下:

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){return new JdbcTemplate(dataSource);
}

这个和上面那个DataSource Bean放在同一个配置文件下,单元测试类如下,在mysql数据库中插入一条数据。

@Test
public void test2(@Autowired JdbcTemplate jdbcTemplate){int rows = jdbcTemplate.update("insert into user values(null,?)", "张三");System.out.println(rows);
}

在Spring Boot项目中,上述不需要单独配置,Spring Boot会自动进行配置的。

4. 利用JdbcTemplate查询数据

查询单个数据:

@Test
public void test3(@Autowired JdbcTemplate jdbcTemplate){String sql = "select * from user where u_id = ?";User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 1);System.out.println(user);
}

运行结果:
在这里插入图片描述
实体用户类为:

package com.example.spring_database_1.entity;public class User {private Integer u_id;private String u_name;public Integer getU_id() {return u_id;}public void setU_id(Integer u_id) {this.u_id = u_id;}public String getU_name() {return u_name;}public void setU_name(String u_name) {this.u_name = u_name;}@Overridepublic String toString() {return "User{" +"u_id=" + u_id +", u_name='" + u_name + '\'' +'}';}
}

查询多个数据,可以改写上述代码为:

@Test
public void test3(@Autowired JdbcTemplate jdbcTemplate){String sql = "select * from user";List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));for (User user : users) {System.out.println(user);}
}

在这里插入图片描述

5. Spring 声明式事务

需要在一个配置类下定义事务管理的Bean,并且需要开启事务(使用注解 @EnableTransactionManagement),另外就是需要添加注解==@Transactional==(可以放在方法、类上)
配置类如下:

package com.example.spring_database_1.spring;import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;@Configuration
@EnableTransactionManagement
public class MyConfiguration {@Value("${spring.datasource.url}")private String url;@Value("${spring.datasource.username}")private String usr;@Value("${spring.datasource.password}")private String pwd;@Value("${spring.datasource.driver-class-name}")private String driver;@Beanpublic DataSource dataSource(){DriverManagerDataSource dataSource = new DriverManagerDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(usr);dataSource.setPassword(pwd);return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource){return new JdbcTemplate(dataSource);}@Beanpublic TransactionManager transactionManager(DataSource dataSource){return new DataSourceTransactionManager(dataSource);}
}

这是上述所有的Bean的配置。
示例方法为(添加了注解@Transactional):

package com.example.spring_database_1.spring.dao;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;@Component
public class UserDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactionalpublic Integer insert(){int row = jdbcTemplate.update("insert into user values(null,?)", "张三");int a = 1 / 0;return row;}}

单元测试类为:

package com.example.spring_database_1.spring;import com.example.spring_database_1.spring.dao.UserDao;
import com.example.spring_database_1.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.List;@SpringBootTest(classes =SpringDataSourceTest.class )
@ComponentScan
public class SpringDataSourceTest {@Autowiredprivate UserDao ud;@Testpublic void test2(){int rows = ud.insert();}
}

运行结果当然也是会报错的,但是数据库中不会插入数据。
在这里插入图片描述
如果是Spring Boot项目,要达到上述效果,只需要在对应的类或者方法上添加注解 @Transactional 即可。
关于@Transactional注解的使用:

  • 放在类上面,表示当前这个类下的所有方法都会开启事务(这种通常不会使用,因为开启事务也是会占用一定资源的);
  • 放在方法上面,表示当前这个方法开启事务(放在业务逻辑类下的方法上面);

6. 事务的隔离级别

通过设置事务的隔离级别来解决并发过程中产生的一些问题。在并发情况下,对同一个数据(变量、对象)进行读写操作才产生的问题(脏读、不可重复读、幻读)。

6.1 脏读

现在有两个事务,事务2以微弱的优势首先进行修改操作,把一个值修改为100,此时事务1执行查询操作,得到值为100,但是事务2在执行过程中出现了异常,进行了事务回滚,此时的值没有得到修改,依旧是200,也就是说此时事务1得到值100是一个脏数据。
一个事务,读取了一个事务中没有提交的数据,会在本事务中产生数据不一致的问题。
解决方案

@Transactional(isolation = Isolation.READ_COMMITTED)

这个不用设置,数据库默认都会保证都已提交

6.2 不可重复读

现在有两个事务,事务1以微弱的优势首先进行查询操作,得到结果为100,此时事务2执行修改操作,把对应的值修改为200,之后事务1又进行了一次查询操作,此时得到的值为200。
一个事务中,多次读取相同的数据,但是读取的结果不一样,从而造成在本事务中所读取的数据不一致的问题。
解决方案:

@Transactional(isolation = Isolation.REPEATABLE_READ)

相当于加上一个行锁

6.3 幻读

现在有两个事务,事务1首先执行求和操作,对正常表里的一些数据进行求和操作,得到的值为100,而事务2执行了插入数据操作,插入了100,此时事务1又进行了一次求和操作,得到的值为200。
一个事务中,多次对数据进行整表数据读取,但是结果不一样,从而导致在本事务中产生数据不一致的问题。
解决方案:

@Transactional(isolation = Isolation.SERIALIZABLE)

相当于对整表上锁

6.4 不可重复读和幻读的区别

前者,只需要锁行;后者,需要锁表

6.5 三种方案的比较

方案脏读不可重复都幻读
READ_COMMITTED不会可能出现可能出现
REPEATABLE_READ不会不会可能出现
SERIALIZABLE不会不会不会

并发安全:SERIALIZABLE>REPEATABLE_READ>READ_COMMITTED
运行效率:SERIALIZABLE<REPEATABLE_READ<READ_COMMITTED

数据库默认情况下设置了隔离级别:

在这里插入图片描述
上述是Mysql数据的隔离级别,如果是Oracle,它的隔离级别为READ_COMMITTED。

对于脏读,通过设置都已提交(行锁,读不会加锁);对于不可重复读,需要设置重复读(行锁,读和写都会上锁);对于幻读,通过设置串行化(表锁)。

7. 事务的传播特性

常用的 spring 的事务传播行为:

事务传播行为类型外部不存在事务(外层事务)外层存在事务(嵌套事务)使用方式
REQUIRED(默认)开启新的事务融合到外部事务中@Transactional(propagation = Propagation.REQUIRED),适合增删改查,常用
SUPPORTS不开启新的事务融合到外部事务中@Transactional(propagation = Propagation.SUPPORTS),适合查询
REQUIRES_NEW开启新的事务不用外部事务,创建新的事务中@Transactional(propagation = Propagation.REQUIRES_NEW),适合内部事务和外部事务不存在业务关联,如日志,常用
NEVER不开启新的事务抛出异常@Transactional(propagation = Propagation.NEVER),不常用

8. 设置事务 只读(readOnly)

readOnly:只会设置在查询的业务方法中
connection.setReadOnly(true),通知数据库,当前数据库操作是只读,数据库就会当作只读做相应优化。

  1. readOnly并不是所有数据库都支持,不同的数据库下会有不同的结果;
  2. 设置readOnly后,connection都会被赋予readOnly,效果取决于数据库的实现。
package com.example.spring_database_1.spring.dao;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;@Component
public class UserDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactional(readOnly = true)public Integer insert(){return jdbcTemplate.update("insert into user values(null,?)", "张三");}
}

运行结果(设置为readOnly=true之后,在下面执行除查询之外的操作,所以报错。):
在这里插入图片描述

9. 超时属性(timeout)

指定事务等待的最长时间(秒)
当前事务访问数据时,有可能访问的数据库被别的数据进行加锁处理,那么此时事务就必须等待,如果等待时间过长给用户造成的体验感差。

package com.example.spring_database_1.spring.dao;import com.example.spring_database_1.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;@Component
public class UserDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactional(timeout = 3)public Integer insert(){return jdbcTemplate.update("insert into user values(null,?)", "张三");}@Transactional(isolation = Isolation.SERIALIZABLE)public List<User> query(){List<User> users = jdbcTemplate.query("select * from user", new BeanPropertyRowMapper<>(User.class));try {Thread.sleep(100000);} catch (InterruptedException e) {e.printStackTrace();}return users;}}
package com.example.spring_database_1.spring;import com.example.spring_database_1.spring.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;@SpringBootTest(classes =SpringDataSourceTest.class )
@ComponentScan
public class SpringDataSourceTest {@Testpublic void test1(@Autowired UserDao ud) throws InterruptedException {Thread thread = new Thread(()->{System.out.println("查询。。。");ud.query();});thread.start();Thread.sleep(1000);Thread thread2 = new Thread(()->{System.out.println("1。。。");ud.insert();System.out.println("2。。。");});thread2.start();thread.join();thread2.join();}}

运行结果:
在这里插入图片描述
查询操作那里使用了事务的隔离级别串行化,此时需要等待查询操作执行完毕,才会执行插入操作,但插入操作那里设置超时时间为3秒,超过3秒之后,此时报错。

10. 异常属性

设置当前事务出现的那些异常就进行回滚或者提交。
默认对于RuntimeException及其子类,采用的是回滚的策略。
默认对于非RuntimeException就不会回滚。

  1. 设置哪些异常不回滚(notRollbackFor)
  2. 设置那些异常回滚(rollbackFor)
@Transactional
public void rollbackFor() throws AlreadyBoundException {insert();throw new AlreadyBoundException("xxx异常");}
@Test
public void test2(@Autowired UserDao ud) throws AlreadyBoundException {ud.rollbackFor();
}

此时并没有回滚,数据库里边插入了一条数据,如下:
在这里插入图片描述
如果想所有异常都会回滚,可以设置如下:

@Transactional(rollbackFor = Exception.class)
public void rollbackFor() throws AlreadyBoundException {insert();throw new AlreadyBoundException("xxx异常");}

11. 事务失效原因

事务实现原理:动态代理
在这里插入图片描述

失效原因

  • 保证事务类配置为一个Bean,@Component、@Service。。。;
  • 事务的方法不能是private;
  • 自己把异常捕捉了,并且没抛出去;
  • 动态代理层面失效原因:
    • 要让aop、事务生效,一定要通过动态代理的对象调用目标方法,不能通过普通对象去调用;
    • 直接调用本类的方法,没有通过动态代理的对象调用目方法,解决方案;
      • 将本类Bean自动装配进来(会产生循环依赖,Spring Boot中需要单独开启循环依赖支持,直接在配置文件中配置即可);
      • ((xxx类)AopContext.currentProxy()).xxx方法(获取当前类)
        1. 前提需要 @EnableAspectJAutoProxy(exposeProxy = true);
        2. 添加aop的依赖;
        3. 把本类的方法移动到其他类的Bean中,然后再把其他类自动装配进来;

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Fakelocation Server服务器/专业版 Windows11

前言:需要Windows11系统 Fakelocation开源文件系统需求 Windows11 | Fakelocation | 任务一 打开 PowerShell&#xff08;以管理员身份&#xff09;命令安装 Chocolatey Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProto…

MySQL系列之身份鉴别(安全)

导览 前言Q&#xff1a;如何保障MySQL数据库身份鉴别的有效性一、有效性检查1. 用户唯一2. 启用密码验证3. 是否存在空口令用户4. 是否启用口令复杂度校验5. 是否设置口令的有效期6. 是否限制登录失败尝试次数7. 是否设置&#xff08;超过尝试次数&#xff09;锁定的最小时长8.…

MySQL面试题补

内连接和外连接的区别&#xff1a; ○1.功能和用法不同&#xff1a;内连接是连接两表都满足情况的数据&#xff1b;而外连接是以一边的表为主表&#xff0c;另一个表只显示匹配的行&#xff1b; ○2.用途&#xff1a;内连接一般是用于检索不同表需要根据共同的列值进行匹配的&a…

线程(三)【线程互斥(下)】

目录 4. 互斥锁4.1 解决数据不一致问题 5. 锁的原理5.1 加锁5.2 解锁 6. 可重入 vs 线程安全 4. 互斥锁 NAMEpthread_mutex_destroy, pthread_mutex_init - destroy and initialize a mutex // 创建、释放锁SYNOPSIS#include <pthread.h>// pthread_mutex_t: 线程库提供…

如何使用AWS Lambda构建一个云端工具(超详细)

首发地址&#xff08;欢迎大家访问&#xff09;&#xff1a;如何使用AWS Lambda构建一个云端工具&#xff08;超详细&#xff09; 1 前言 1.1 无服务器架构 无服务器架构&#xff08;Serverless Computing&#xff09;是一种云计算服务模型&#xff0c;它允许开发者构建和运行…

网络爬虫总结与未来方向

通过深入学习和实际操作&#xff0c;网络爬虫技术从基础到进阶得以系统掌握。本节将全面总结关键内容&#xff0c;并结合前沿技术趋势与最新资料&#xff0c;为开发者提供实用性强的深度思考和方案建议。 1. 网络爬虫技术发展趋势 1.1 趋势一&#xff1a;高性能分布式爬虫 随…

实验十三 生态安全评价

1 背景及目的 生态安全是生态系统完整性和健康性的整体反映&#xff0c;完整健康的生态系统具有调节气候净化污染、涵养水源、保持水土、防风固沙、减轻灾害、保护生物多样性等功能。维护生态安全对于人类生产、生活、健康及可持续发展至关重要。随着城市化进程的不断推进&…

archlinux安装waydroid

目录 参考资料 注意 第一步切换wayland 第二步安装binder核心模组 注意 开始安装 AUR安裝Waydroid 启动waydroid 设置网络&#xff08;正常的可以不看&#xff09; 注册谷歌设备 安装Arm转译器 重启即可 其他 参考资料 https://ivonblog.com/posts/archlinux-way…

鸿蒙NEXT开发案例:随机数生成

【引言】 本项目是一个简单的随机数生成器应用&#xff0c;用户可以通过设置随机数的范围和个数&#xff0c;并选择是否允许生成重复的随机数&#xff0c;来生成所需的随机数列表。生成的结果可以通过点击“复制”按钮复制到剪贴板。 【环境准备】 • 操作系统&#xff1a;W…

【Android】Service使用方法:本地服务 / 可通信服务 / 前台服务 / 远程服务(AIDL)

1 本地Service 这是最普通、最常用的后台服务Service。 1.1 使用步骤 步骤1&#xff1a;新建子类继承Service类&#xff1a;需重写父类的onCreate()、onStartCommand()、onDestroy()和onBind()方法步骤2&#xff1a;构建用于启动Service的Intent对象步骤3&#xff1a;调用st…

【LeetCode热题100】队列+宽搜

这篇博客是关于队列宽搜的几道题&#xff0c;主要包括N叉树的层序遍历、二叉树的锯齿形层序遍历、二叉树最大宽度、在每个数行中找最大值。 class Solution { public:vector<vector<int>> levelOrder(Node* root) {vector<vector<int>> ret;if(!root) …

双因子认证:统一运维平台安全管理策略

01双因子认证概述 双因子认证&#xff08;Two-Factor Authentication&#xff0c;简称2FA&#xff09;是一种身份验证机制&#xff0c;它要求用户提供两种不同类型的证据来证明自己的身份。这通常包括用户所知道的&#xff08;如密码&#xff09;、用户所拥有的&#xff08;如…

Apple Vision Pro开发002-新建项目配置

一、新建项目 可以选择默认的&#xff0c;也可以选择Universal 3D 二、切换打包平台 注意选择Target SDK为Devices SDk&#xff0c;这种适配打包到真机调试 三、升级新的Input系统 打开ProjectSettings&#xff0c;替换完毕之后引擎会重启 四、导入PolySpatial 修改上图红…

瑞佑液晶控制芯片RA6807系列介绍 (三)软件代码详解 Part.10(让PNG图片动起来)完结篇

RA6807是RA8876M的缩小版&#xff0c;具备RA8876M的所有功能&#xff0c;只将MCU控制接口进行缩减&#xff0c;仅保留SPI-3和I2C接口&#xff0c;其它功能基本相同。 该芯片最大可控制854x600的分辨率&#xff0c;内建64Mbits显存&#xff0c;多个图层&#xff0c;使用起来相当…

机器人SLAM建图与自主导航:从基础到实践

前言 这篇文章我开始和大家一起探讨机器人SLAM建图与自主导航 &#xff0c;在前面的内容中&#xff0c;我们介绍了差速轮式机器人的概念及应用&#xff0c;谈到了使用Gazebo平台搭建仿真环境的教程&#xff0c;主要是利用gmapping slam算法&#xff0c;生成一张二维的仿真环境…

一篇保姆式centos/ubuntu安装docker

前言&#xff1a; 本章节分别演示centos虚拟机&#xff0c;ubuntu虚拟机进行安装docker。 上一篇介绍&#xff1a;docker一键部署springboot项目 一&#xff1a;centos 1.卸载旧版本 yum remove docker docker-client docker-client-latest docker-common docker-latest doc…

Robot | 用 RDK 做一个小型机器人(更新中)

目录 前言架构图开发过程摄像头模型转换准备校准数据使用 hb_mapper makertbin 工具转换模型 底版开发 结语 前言 最近想开发一个小型机器人&#xff0c;碰巧看到了 RDK x5 发布了&#xff0c;参数对于我来说非常合适&#xff0c;就买了一块回来玩。 外设也是非常丰富&#xf…

如何在 UniApp 中实现 iOS 版本更新检测

随着移动应用的不断发展&#xff0c;保持应用程序的更新是必不可少的&#xff0c;这样用户才能获得更好的体验。本文将帮助你在 UniApp 中实现 iOS 版的版本更新检测和提示&#xff0c;适合刚入行的小白。我们将分步骤进行说明&#xff0c;每一步所需的代码及其解释都会一一列出…

软件工程导论 选填题知识点总结

一 原型化方法是一种动态定义需求的方法&#xff0c;提供完整定义的需求不是原型化方法的特征&#xff0c;其特征包括尽快建立初步需求、简化项目管理以及加强用户参与和决策。 软件危机的表现包括用户对已完成的软件系统不满意的现象经常发生、软件产品的质量往往靠不住、软件…

软件测试面试之常规问题

1.描述一下测试过程 类似题目:测试的生命周期 思路:这是一个“范围”很大的题目&#xff0c;而且回答时间一般在3分钟之内&#xff0c;不可能非常详细的描述整个过程&#xff0c;因此答题的思路要从整体结构入手&#xff0c;不要过细。为了保证答案的准确性&#xff0c;可以引…