当前位置: 首页 > news >正文

Spring笔记(五)——事务

简介:Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作。

一、准备工作

        1.加入依赖

<dependencies><!--spring jdbc  Spring 持久化层支持jar包--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.0.2</version></dependency><!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><!-- 数据源 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.15</version></dependency>
</dependencies>

        2.创建jdbc.properties

jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&use
SSL=false
jdbc.driver=com.mysql.cj.jdbc.Driver

        3.配置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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!-- 导入外部属性文件 --><context:property-placeholder location="classpath:jdbc.properties" /><!-- 配置数据源 --><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"/><property name="driverClassName" value="${jdbc.driver}"/><property name="username" value="${jdbc.user}"/><property name="password" value="${jdbc.password}"/></bean><!-- 配置 JdbcTemplate --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><!-- 装配数据源 --><property name="dataSource" ref="druidDataSource"/></bean></beans>

        4.准备数据库与测试表

CREATE DATABASE `spring`;use `spring`;CREATE TABLE `t_emp` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(20) DEFAULT NULL COMMENT '姓名',`age` int(11) DEFAULT NULL COMMENT '年龄',`sex` varchar(2) DEFAULT NULL COMMENT '性别',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

二、实现CURD

        1.装配JdbcTemplate

package com.spring;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;@SpringJUnitConfig(locations = "classpath:beans.xml")
public class JDBCTemplateTest {@Autowiredprivate JdbcTemplate jdbcTemplate;}

        2.测试增删改功能

@Test
//测试增删改功能
public void testUpdate(){//添加功能String sql = "insert into t_emp values(null,?,?,?)";int result = jdbcTemplate.update(sql, "张三", 23, "男");//修改功能//String sql = "update t_emp set name=? where id=?";//int result = jdbcTemplate.update(sql, "张三atguigu", 1);//删除功能//String sql = "delete from t_emp where id=?";//int result = jdbcTemplate.update(sql, 1);
}

        3.查询数据返回对象

public class Emp {private Integer id;private String name;private Integer age;private String sex;//生成get和set方法//......@Overridepublic String toString() {return "Emp{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", sex='" + sex + '\'' +'}';}
}
@Test
public void testSelectObject() {//写法一
//        String sql = "select * from t_emp where id=?";
//        Emp empResult = jdbcTemplate.queryForObject(sql,
//                (rs, rowNum) -> {
//                    Emp emp = new Emp();
//                    emp.setId(rs.getInt("id"));
//                    emp.setName(rs.getString("name"));
//                    emp.setAge(rs.getInt("age"));
//                    emp.setSex(rs.getString("sex"));
//                    return emp;
//                }, 1);
//        System.out.println(empResult);//写法二String sql = "select * from t_emp where id=?";Emp emp = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(Emp.class),1);System.out.println(emp);
}

        4.查询数据返回list集合

@Test
//查询多条数据为一个list集合
public void testSelectList(){String sql = "select * from t_emp";List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class));System.out.println(list);
}

        5.查询返回单个的值

@Test
//查询单行单列的值
public void selectCount(){String sql = "select count(id) from t_emp";Integer count = jdbcTemplate.queryForObject(sql, Integer.class);System.out.println(count);
}

三、事务概念

        1.什么是事务

        数据库事务是访问可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。

        2.事务的特性

        - 原子性

        一个事务中的所有操作,要么全部执行,要么全部不执行,不会结束在某个中间环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态。

        - 一致性

        事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功完成,则系统处于有效状态,否则回滚到事务开始之前的原始状态。

        - 隔离性

        指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会看到中间状态的数据。

        - 持久性

        指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。

        3.编程式事务

Connection conn = ...;try {// 开启事务:关闭事务的自动提交conn.setAutoCommit(false);// 核心操作// 提交事务conn.commit();}catch(Exception e){// 回滚事务conn.rollBack();}finally{// 释放数据库连接conn.close();}

        缺陷:代码复用性不高,细节未屏蔽

        4.声明式事务

        进行相关封装,通过配置使框架实现功能。

      四、基于注解的声明式事务

        1.添加配置

<!--扫描组件-->
<context:component-scan base-package="com.spring"></context:component-scan>

        2.创建表

CREATE TABLE `t_book` (`book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',`book_name` varchar(20) DEFAULT NULL COMMENT '图书名称',`price` int(11) DEFAULT NULL COMMENT '价格',`stock` int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)',PRIMARY KEY (`book_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
insert  into `t_book`(`book_id`,`book_name`,`price`,`stock`) values (1,'神雕侠侣',80,100),(2,'仙逆',50,100);
CREATE TABLE `t_user` (`user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',`username` varchar(20) DEFAULT NULL COMMENT '用户名',`balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)',PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert  into `t_user`(`user_id`,`username`,`balance`) values (1,'admin',50);

        3.创建组件

//创建BookController
package com.spring.controller;@Controller
public class BookController {@Autowiredprivate BookService bookService;public void buyBook(Integer bookId, Integer userId){bookService.buyBook(bookId, userId);}
}
//创建接口BookService:package com.spring.service;
public interface BookService {void buyBook(Integer bookId, Integer userId);
}
//创建实现类BookServiceImpl:package com.spring.service.impl;
@Service
public class BookServiceImpl implements BookService {@Autowiredprivate BookDao bookDao;@Overridepublic void buyBook(Integer bookId, Integer userId) {//查询图书的价格Integer price = bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId, price);}
}
//创建BookDao
package com.spring.dao;
public interface BookDao {Integer getPriceByBookId(Integer bookId);void updateStock(Integer bookId);void updateBalance(Integer userId, Integer price);
}
//创建实现类BookDaoImpl:package com.spring.dao.impl;
@Repository
public class BookDaoImpl implements BookDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic Integer getPriceByBookId(Integer bookId) {String sql = "select price from t_book where book_id = ?";return jdbcTemplate.queryForObject(sql, Integer.class, bookId);}@Overridepublic void updateStock(Integer bookId) {String sql = "update t_book set stock = stock - 1 where book_id = ?";jdbcTemplate.update(sql, bookId);}@Overridepublic void updateBalance(Integer userId, Integer price) {String sql = "update t_user set balance = balance - ? where user_id = ?";jdbcTemplate.update(sql, price, userId);}
}

        4.测试无事务情况

//创建测试类
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;@SpringJUnitConfig(locations = "classpath:beans.xml")
public class TxByAnnotationTest {@Autowiredprivate BookController bookController;@Testpublic void testBuyBook(){bookController.buyBook(1, 1);}}

        5.加入事务

        - 添加事务配置

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd">#在Spring的配置文件中添加配置:<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="druidDataSource"></property>
</bean><!--开启事务的注解驱动注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />

        - 添加事务注解

        处理事务一般在service层,因为service层是业务逻辑层

        在该代码的BookServiceImpl的buybook()添加注解@Transactional

        @Transactional标识在方法上只能影响该方法,标识在类上,则只会影响该类。

        6.事务属性

        - 只读

        如果我们将一个事务设置成只读,则明确这个操作不涉及写操作,数据库就会针对查询操作进行优化。对增删改操作会抛出异常。

//使用
@Transactional(readOnly = true)

        - 超时

        事务在执行过程中,有可能出现问题导致程序卡住,这时需要对程序进行回滚撤销事务,把资源释放出来,让其他程序得以运行。

//超时时间单位秒
@Transactional(timeout = 3)

        - 回滚策略

        声明式事务默认只针对运行时异常回滚,编译时异常不回滚。

        

属性要求
rollbackFor属性、noRollbackFor属性需设置一个Class类型的对象
rollbackForClassName属性、rollbackFor属性需要设置一个字符串类型的全类名
//使用
@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")

        - 隔离级别

        数据库系统具有并发事务的能力,但事务与事务之间不会相互影响,一个事务与其他事务的隔离程度称为事务的隔离级别。SQL标准中规定了多种事务隔离级别,隔离级别越高,数据一致性就越好,但并发性越弱。

读未提交允许事务1读取事务2未提交的修改
读已提交要求事务1读只能取事务2已提交的修改
可重复读事务1执行期间禁止其他事务对这个字段进行更新,事务1可以多次从这个字段中读到相同的值
串行化确保事务1可以多次从一个表中读取到相同值,事务1执行期间,禁止其他事务对这个表进行增删改操作。可避免任何并发问题,但性能低下

        隔离级别解决并发可能出现的问题:

隔离级别脏读不可重复读幻读
读未提交
读已提交
可重复读
串行化

        使用:

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

        - 传播行为

        a方法有事务,b方法有事务,当a方法执行过程中调用了b事务,则事务是如何执行的。即事物的传播行为。

传播行为解释
REQUIRED支持当前事务,如果不存在就新建一个

SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行
MANDATORY必须运行在一个事务当中,如果当前没有事务发生则抛出异常
REQUIRES_NEW开启一个事务,如果一个事务已存在,就将这个事务挂起
NOT_SUPPORTED以非事务方式运行,如果有事务存在,挂起当前事务
NEVER以非事务方式运行,如果有事务存在,抛出异常
NESTED如果当前正有一个事务进行,则该方法应该运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚,如果外层事务不存在,行为如REQUIRED

        举例:

//创建接口package com.spring.service;public interface CheckoutService {void checkout(Integer[] bookIds, Integer userId);
}
//创建实现类CheckoutServiceImplpackage com.spring.service.impl;@Service
public class CheckoutServiceImpl implements CheckoutService {@Autowiredprivate BookService bookService;@Override@Transactional//一次购买多本图书public void checkout(Integer[] bookIds, Integer userId) {for (Integer bookId : bookIds) {bookService.buyBook(bookId, userId);}}
}
//在BookController中添加方法:@Autowired
private CheckoutService checkoutService;public void checkout(Integer[] bookIds, Integer userId){checkoutService.checkout(bookIds, userId);
}

        - @Transactional(propagation = Propagation.REQUIRED),购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此方法中执行。当用户余额只够买第一本书时,购买第二本书因余额不足而失败,整个checkout()回滚。只要有一本买不了,所有的都买不了。

        - @Transactional(propagation = Propagation.REQUIRES_NEW),每次购买图书都是在buyBook()的事务中执行,因此在相同的场景下,此时会执行只购买第一本书,而第二本书因为余额不够不会购买,只在第二次的buyBook()中回滚。

五、全注解配置事务

        1.添加配置类

package com.spring.config;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;@Configuration
@ComponentScan("com.spring6")
@EnableTransactionManagement
public class SpringConfig {@Beanpublic DataSource getDataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");dataSource.setUsername("root");dataSource.setPassword("root");return dataSource;}@Bean(name = "jdbcTemplate")public JdbcTemplate getJdbcTemplate(DataSource dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}@Beanpublic DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;}
}

        2.测试

import com.spring.config.SpringConfig;
import com.spring.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;public class TxByAllAnnotationTest {@Testpublic void testTxAllAnnotation(){ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);BookController accountService = applicationContext.getBean("bookController", BookController.class);accountService.buyBook(1, 1);}
}

六、基于xml的声明事务

        1.引入依赖

  <dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.2</version></dependency>

        2.修改Spring配置文件

<aop:config><!-- 配置事务通知和切入点表达式 --><aop:advisor advice-ref="txAdvice" pointcut="execution(* com.spring.tx.xml.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager"><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" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/><tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/><tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/></tx:attributes>
</tx:advice>

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 组件化开发
  • 【C++】C++中的find方法介绍
  • 无标题栏窗口通过消息模拟拖动窗口时,无法拖动的一个原因
  • 鸿蒙应用框架开发【基于原生能力的无障碍模式】
  • ​​​​​​​开发面试“八股文”:助力还是阻力?
  • 基于Deap遗传算法在全量可转债上做因子挖掘(附python代码及全量因子数据)
  • 《计算机网络》(学习笔记)
  • redis面试(三)Hash数据结构
  • Linux--Socket编程TCP
  • LIMS实验室管理系统的三大分类
  • Python自学第五天
  • 计算机毕业设计选题推荐-学院教学工作量统计系统-Java/Python项目实战
  • 【C++】用Lua绑定C/C++对象,实现对脚本调用(依赖LuaBridge实现)
  • Hello 算法:动画图解、一键运行的数据结构与算法教程
  • MySQL的面试题,从简单到困难三道题目
  • [译] React v16.8: 含有Hooks的版本
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • HTML5新特性总结
  • JAVA_NIO系列——Channel和Buffer详解
  • PHP 7 修改了什么呢 -- 2
  • Redux系列x:源码分析
  • STAR法则
  • ucore操作系统实验笔记 - 重新理解中断
  • unity如何实现一个固定宽度的orthagraphic相机
  • Vue全家桶实现一个Web App
  • windows下如何用phpstorm同步测试服务器
  • 闭包--闭包作用之保存(一)
  • 大型网站性能监测、分析与优化常见问题QA
  • 分享几个不错的工具
  • 浮动相关
  • 基于 Ueditor 的现代化编辑器 Neditor 1.5.4 发布
  • 基于组件的设计工作流与界面抽象
  • 听说你叫Java(二)–Servlet请求
  • 我从编程教室毕业
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • 京东物流联手山西图灵打造智能供应链,让阅读更有趣 ...
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • # centos7下FFmpeg环境部署记录
  • #Datawhale X 李宏毅苹果书 AI夏令营#3.13.2局部极小值与鞍点批量和动量
  • #if #elif #endif
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • (2009.11版)《网络管理员考试 考前冲刺预测卷及考点解析》复习重点
  • (23)Linux的软硬连接
  • (c语言版)滑动窗口 给定一个字符串,只包含字母和数字,按要求找出字符串中的最长(连续)子串的长度
  • (php伪随机数生成)[GWCTF 2019]枯燥的抽奖
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (六)c52学习之旅-独立按键
  • (十)T检验-第一部分
  • (算法)大数的进制转换
  • (学习日记)2024.01.19
  • (一)RocketMQ初步认识