第七章、事务
1、JdbcTemplate
1、介绍
1、spring框架对JDBC进行了封装,所以使用JdbcTemplate
2、准备工作(准备依赖)
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=123456
jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false
jdbc.driver=com.mysql.cj.jdbc.Driver
3、配置spring的配置文件
1、创建外部属性文件,创建数据库对象
2、创建jdbcTemplate对象,注入数据库
<?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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 创建外务部属性文件,创建数据库对象-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 创建jdbcTemplate对象,注入数据库-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
</beans>
4、创建数据库代码
1、数据库sql文件
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;
INSERT INTO t_emp VALUES(NULL,'好好好',11,'男')
UPDATE t_emp SET NAME="嗨嗨嗨" WHERE id=1;
SELECT * FROM t_emp WHERE id=2;
5、创建实体类Emp
1、Emp.java
package com.atqt.spring6.jdbc;
public class Emp {
private int id;
private String name;
private int age;
private String sex;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
3、实例演示
1、添加
@Test
public void testAdd(){
String sql = "INSERT INTO t_emp VALUES(NULL,?,?,?)";
int update = jdbcTemplate.update(sql, "潘金莲", 18, "男");
System.out.println(update);
}
2、修改
@Test
public void testUpdate(){
String sql = "update t_emp set name=? where id=?";
int update = jdbcTemplate.update(sql,"大傻瓜",1);
System.out.println(update);
}
3、删除
@Test
public void testDelete(){
String sql = "delete from t_emp where id=?";
int update = jdbcTemplate.update(sql,1);
System.out.println(update);
}
4、查询
1、使用
1、方式一
new BeanPropertyRowMapper<>(Emp.class)
方式二
1、查询返回集合
@Test
public void testSelectList(){
String sql = "select * from t_emp ";
// 返回集合对象
List<Emp> emp = jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(Emp.class) );
System.out.println(emp);
}
2、查询返回单个值
@Test
public void testSelectVaulue(){
String sql = "select count(*) from t_emp";
Integer count = jdbcTemplate.queryForObject(sql,Integer.class);
System.out.println(count);
}
3、代码实现
package com.atqt.spring6.jdbc;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.util.List;
@SpringJUnitConfig(locations="classpath:beans.xml")
public class JdbcTemplateTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testAdd(){
String sql = "INSERT INTO t_emp VALUES(NULL,?,?,?)";
int update = jdbcTemplate.update(sql, "潘金莲", 18, "男");
System.out.println(update);
}
@Test
public void testUpdate(){
String sql = "update t_emp set name=? where id=?";
int update = jdbcTemplate.update(sql,"大傻瓜",1);
System.out.println(update);
}
@Test
public void testDelete(){
String sql = "delete from t_emp where id=?";
int update = jdbcTemplate.update(sql,1);
System.out.println(update);
}
// 查询返回的集合
@Test
public void testGetObject(){
String sql = "select * from t_emp where id=?";
// 返回集合对象
Emp emp = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(Emp.class),2);
System.out.println(emp);
}
// 查询返回的集合
@Test
public void testSelectList(){
String sql = "select * from t_emp ";
// 返回集合对象
List<Emp> emp = jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(Emp.class) );
System.out.println(emp);
}
// 查询返回单个值
@Test
public void testSelectVaulue(){
String sql = "select count(*) from t_emp";
Integer count = jdbcTemplate.queryForObject(sql,Integer.class);
System.out.println(count);
}
}
2、声明事务的概念
1、事务的基本概念
1、什么是事务
1、是数据库事务是访问并可能操作各种数据项的一个数据库操作序列,这些操作,要么全部执行,要么全部不执行,是不可分割的一部分。事务的开始和事务的结束之间执行全部数据库操作组成
2、事务的特性
1、原子性(Atomicity)
1、一个事务的素有操作,要么全部完成,要么都不清楚,不会结束在某个环节,事务在执行中出现错误就会回滚到事务开始的状态。
2、一致性(Consistency)
1、事务的一致性是指在一个事务执行前和执行后数据库都必须保持一个状态。
2、如果事务成功完成,数据库修改,系统承认有效
3、如果事务失败,数据库内容进行回滚,系统返回最初值
3、隔离性(solation)
1、并发环境中,每个事务执行操作同样的数据,每个事务都有各自完整的数据空间。
2、由于并发的单个事务修改需要和其他事务做隔离,事务查看更新的时候要么是其他事务操作数据前的,或其他事务操作数据更新后的状态。不会看到事务中间状态的数据。
4、持久性(Durability)
1、事务结束成功,数据库更新必须保存
2、即时系统卡死也会保存下来
2、编程式事务
1、代码格式
1、就是自己用代码手动实现
2、缺点
1、细节没有被屏蔽,程序员需要自己完成
2、代码复用性不高,没有进行封装
3、声明式事务
1、内容介绍
1、就是将编程式的代码进行封装
2、封装后,我们只需要在配置文件进行简单的配置即可完成操作
3、通过配置让框架实现功能
2、优点
1、提高开发效率
2、消除冗余代码
3、框架会考虑相关领域在实际开发中可能遇到的各种问题,进行了健壮性、性能等各方面的优化
4、基于注解的声明式编程
1、搭建环境
1、编写BookService及接口
1、BookService.java
package com.atqt.spring6.tx.service;
public interface BookService {
void buyBook(Integer bookId, Integer userId);
}
2、BookServiceImpl.java
package com.atqt.spring6.tx.service;
import com.atqt.spring6.tx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl implements BookService{
@Autowired
private BookDao bookDao;
@Override
public void buyBook(Integer bookId, Integer userId) {
// 根据图书id查询图书价格
Integer price = bookDao.getBookPriceById(bookId);
// 更新图书表库存量-1
bookDao.getBookStock(bookId);
// 更新用户表里的余额-图书价格
bookDao.getBookBalancePrice(userId,price);
}
}
2、BookController.java
package com.atqt.spring6.tx.controller;
import com.atqt.spring6.tx.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class BookController {
@Autowired
private BookService bookService;
public void buyBook(Integer bookId, Integer userId) {
// 调用service方法
bookService.buyBook(bookId,userId);
}
}
3、BookDao及接口
1、BookDao.java
package com.atqt.spring6.tx.dao;
public interface BookDao {
Integer getBookPriceById(Integer bookId);
void getBookStock(Integer bookId);
void getBookBalancePrice(Integer userId, Integer price);
}
2、BookDaoImpl.java
package com.atqt.spring6.tx.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Integer getBookPriceById(Integer bookId) {
String sql = "select price from t_book where book_id=?";
Integer price = jdbcTemplate.queryForObject(sql,Integer.class,bookId);
return price;
}
@Override
public void getBookStock(Integer bookId) {
String sql = "update t_book set stock=stock-1 where book_id= ?";
jdbcTemplate.update(sql,bookId);
}
@Override
public void getBookBalancePrice( Integer userId,Integer price) {
String sql = "update t_user set balance=balance-? where user_id=?";
jdbcTemplate.update(sql,price,userId);
}
}
4、测试
1、TestUser.java
package com.atqt.spring6.tx;
import com.atqt.spring6.tx.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(locations = "classpath:beans.xml")
public class TestUser {
@Autowired
private BookController bookController ;
@Test
public void testBuyBook(){
bookController.buyBook(1,1);
}
}
2、发现问题
1、钱和图书库存没有同步更新
2、要么都成功,要么都失败
3、加入事务
1、配置bean.xml文件
1、
<?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
http://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.atqt.spring6.tx"></context:component-scan>
<!-- 创建外务部属性文件,创建数据库对象-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 创建jdbcTemplate对象,注入数据库-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!-- 开启事务的注解驱动-->
<tx:annotation-driven></tx:annotation-driven>
</beans>
2、给Service添加事务的注解
1、代码
package com.atqt.spring6.tx.service;
import com.atqt.spring6.tx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class BookServiceImpl implements BookService{
@Autowired
private BookDao bookDao;
@Override
public void buyBook(Integer bookId, Integer userId) {
// 根据图书id查询图书价格
Integer price = bookDao.getBookPriceById(bookId);
// 更新图书表库存量-1
bookDao.getBookStock(bookId);
// 更新用户表里的余额-图书价格
bookDao.getBookBalancePrice(userId,price);
}
}
3、进行测试
1、
package com.atqt.spring6.tx;
import com.atqt.spring6.tx.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(locations = "classpath:beans.xml")
public class TestUser {
@Autowired
private BookController bookController ;
@Test
public void testBuyBook(){
bookController.buyBook(1,1);
}
}
1、执行成功,没有问题
4、@Transactional注解标识的位置
1、@Transactional标识在方法上,只会影响方法
2、如果在类上,会影响类中所有方法
5、事务属性:只读
1、介绍
1、对一个Service来设置一个Transactional来进行标注
2、格式
@Transactional(readOnly=true)
2、注意
1、如果设置了只读,还弄增删改,就会报异常
2、异常信息:Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
6、事务属性:超时
1、介绍
1、事务在运行中,如果遇到问题导致死锁卡住,此时程序应该进行回滚。撤销已经做了的操作,把资源让出来
2、就是说:超时就回滚,别拦着其他资源
3、格式
@Transactional(timeout = xxx) //秒
2、注意
1、异常信息:
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022
7、事务属性:回滚策略
1、介绍
1、运行时异常回滚,编译不回滚
2、格式
@Transactional(xxxxx属性 = xxxx对象)
3、相关属性
| 属性 | 设置对象 |
|---|---|
| rollbackFor | 设置一个Class类型的对象 |
| rollbackForClassName | 需要设置一个字符串类型的全类名 |
| noRollbackFor | 需要设置一个Class类型的对象 |
| rollbackFor | 需要设置一个字符串类型的全类名 |
4、例子
@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);
}
5、结果:这个方法里有异常但是没有进行回滚,因为设置了noRollbackFor
8、事务属性:隔离级别
1、介绍
1、必须要有事务隔离,进行并发各个事务的能力,让它们不会互相打扰
2、隔离级别越高,数据一致性就越好,但并发性越弱
3、四种隔离级别
| 级别 | 作用 |
|---|---|
| 读未提交:READ UNCOMMITTED | 允许Transaction01读取Transaction02未提交的修改 |
| 读已提交:READ COMMITTED | 要求Transaction01只能读取Transaction02已提交的修改 |
| 可重复读:REPEATABLE READ | 即Transaction01执行期间禁止其它事务对这个字段进行更新。 |
| 串行化:SERIALIZABLE | 在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。 |
4、各个隔离级别解决并发问题的能力
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 有 | 有 | 有 |
| READ COMMITTED | 无 | 有 | 有 |
| REPEATABLE READ | 无 | 无 | 有 |
| SERIALIZABLE | 无 | 无 | 无 |
5、各种数据库产品对事务隔离级别的支持程度
| 隔离级别 | Oracle | MySQL |
|---|---|---|
| READ UNCOMMITTED | × | √ |
| READ COMMITTED | √(默认) | √ |
| REPEATABLE READ | × | √(默认) |
| SERIALIZABLE | √ | √ |
9、事务属性:传播行为
1、什么是事务传播的行为?
1、方法a有事务,方法b也有事务,如果方法a调用了方法b,那事务该怎么办?
2、是整合两个事务吗?这就是事务传播的行为
2、一共有七中传播的行为:
1、如下表
| 传播行为 | 作用 |
|---|---|
| ?REQUIRED | 支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】 |
| SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】 |
| MANDATORY | 必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】 |
| ?REQUIRES_NEW | 开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】 |
| NOT_SUPPORTED | 以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】 |
| NEVER | 以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】 |
| NESTED | 如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】 |
3、传播事务例子

1、checkoutBookService.java
package com.atqt.spring6.tx.service;
public interface CheckoutService {
void checkoutBugBook(Integer[] bookIds,Integer userId);
}
2、checkoutBookServiceImpl.java
package com.atqt.spring6.tx.service;
import com.atqt.spring6.tx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
public class CheckoutServiceImpl implements CheckoutService{
@Autowired
private BookService bookService;
@Transactional
@Override
public void checkoutBugBook(Integer[] bookIds,Integer userId) {
for(Integer bookId:bookIds){
bookService.buyBook(bookId,userId);
}
}
}
3、TestUser
package com.atqt.spring6.tx;
import com.atqt.spring6.tx.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(locations = "classpath:beans.xml")
public class TestUser {
@Autowired
private BookController bookController ;
@Test
public void testBuyBook(){
Integer[] books = new Integer[]{1,2};
bookController.chekcout(books,1);
}
}
4、BookController.java
package com.atqt.spring6.tx.controller;
import com.atqt.spring6.tx.service.BookService;
import com.atqt.spring6.tx.service.CheckoutService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class BookController {
// @Autowired
// private BookService bookService;
//
// public void buyBook(Integer bookId, Integer userId) {
//// 调用service方法
// bookService.buyBook(bookId,userId);
// }
@Autowired
CheckoutService checkoutService;
public void chekcout(Integer[] bookId,Integer userId){
checkoutService.checkoutBugBook(bookId,userId);
}
}
5、Dao是一样的
10、全注解配置事务
1、编写SpringConfig配置类文件
1、代码实现
package com.atqt.spring6.tx.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.atqt.spring6.tx") //开启扫描的注解
@EnableTransactionManagement //开启事务的注解
public class SpringConfig {
// 创建数据库对象
@Bean
public DataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
return dataSource;
}
//<!--<!– 创建jdbcTemplate对象,注入数据库–>-->
@Bean(name = "jdbcTemplate")
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 开启事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
2、编写jdbc.properties配置文件
1、代码实现
jdbc.user=root
jdbc.password=1234565
jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false
jdbc.driver=com.mysql.cj.jdbc.Driver
3、TestAnno测试
1、代码实现
package com.atqt.spring6.tx;
import com.atqt.spring6.tx.config.SpringConfig;
import com.atqt.spring6.tx.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAnno {
@Test
public void testTxAllAnnotation(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
BookController bookController = context.getBean("bookController", BookController.class);
Integer[] bookIds = {1,2};
bookController.checkOut(bookIds,1);
}
}
5、基于XML的声明式事务
1、场景模拟
1、基于注解的声明事务