1、JdbcTemplate
1.1、簡介
Spring 框架對 JDBC 進(jìn)行封裝,使用 JdbcTemplate 方便實(shí)現(xiàn)對數(shù)據(jù)庫操作
1.2、準(zhǔn)備工作
①搭建子模塊
搭建子模塊:spring-jdbc-tx
②加入依賴
<dependencies>
<!--spring jdbc Spring 持久化層支持jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.2</version>
</dependency>
<!-- MySQL驅(qū)動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- 數(shù)據(jù)源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
</dependencies>
③創(chuàng)建jdbc.properties
jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false
jdbc.driver=com.mysql.cj.jdbc.Driver
④配置Spring的配置文件
beans.xml
<?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">
<!-- 導(dǎo)入外部屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置數(shù)據(jù)源 -->
<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">
<!-- 裝配數(shù)據(jù)源 -->
<property name="dataSource" ref="druidDataSource"/>
</bean>
</beans>
⑤準(zhǔn)備數(shù)據(jù)庫與測試表
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;
1.3、實(shí)現(xiàn)CURD
①裝配 JdbcTemplate
創(chuàng)建測試類,整合JUnit,注入JdbcTemplate
package com.atguigu.spring6;
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 {
@Autowired
private JdbcTemplate jdbcTemplate;
}
②測試增刪改功能
@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);
}
③查詢數(shù)據(jù)返回對象
public class Emp {
private Integer id;
private String name;
private Integer age;
private String sex;
//生成get和set方法
//......
@Override
public 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);
}
④查詢數(shù)據(jù)返回list集合
@Test
//查詢多條數(shù)據(jù)為一個list集合
public void testSelectList(){
String sql = "select * from t_emp";
List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class));
System.out.println(list);
}
⑤查詢返回單個的值
@Test
//查詢單行單列的值
public void selectCount(){
String sql = "select count(id) from t_emp";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println(count);
}
2、聲明式事務(wù)概念
2.1、事務(wù)基本概念
①什么是事務(wù)
數(shù)據(jù)庫事務(wù)( transaction)是訪問并可能操作各種數(shù)據(jù)項(xiàng)的一個數(shù)據(jù)庫操作序列,這些操作要么全部執(zhí)行,要么全部不執(zhí)行,是一個不可分割的工作單位。事務(wù)由事務(wù)開始與事務(wù)結(jié)束之間執(zhí)行的全部數(shù)據(jù)庫操作組成。
②事務(wù)的特性
A:原子性(Atomicity)
一個事務(wù)(transaction)中的所有操作,要么全部完成,要么全部不完成,不會結(jié)束在中間某個環(huán)節(jié)。事務(wù)在執(zhí)行過程中發(fā)生錯誤,會被回滾(Rollback)到事務(wù)開始前的狀態(tài),就像這個事務(wù)從來沒有執(zhí)行過一樣。
C:一致性(Consistency)
事務(wù)的一致性指的是在一個事務(wù)執(zhí)行之前和執(zhí)行之后數(shù)據(jù)庫都必須處于一致性狀態(tài)。
如果事務(wù)成功地完成,那么系統(tǒng)中所有變化將正確地應(yīng)用,系統(tǒng)處于有效狀態(tài)。
如果在事務(wù)中出現(xiàn)錯誤,那么系統(tǒng)中的所有變化將自動地回滾,系統(tǒng)返回到原始狀態(tài)。
I:隔離性(Isolation)
指的是在并發(fā)環(huán)境中,當(dāng)不同的事務(wù)同時操縱相同的數(shù)據(jù)時,每個事務(wù)都有各自的完整數(shù)據(jù)空間。由并發(fā)事務(wù)所做的修改必須與任何其他并發(fā)事務(wù)所做的修改隔離。事務(wù)查看數(shù)據(jù)更新時,數(shù)據(jù)所處的狀態(tài)要么是另一事務(wù)修改它之前的狀態(tài),要么是另一事務(wù)修改它之后的狀態(tài),事務(wù)不會查看到中間狀態(tài)的數(shù)據(jù)。
D:持久性(Durability)
指的是只要事務(wù)成功結(jié)束,它對數(shù)據(jù)庫所做的更新就必須保存下來。即使發(fā)生系統(tǒng)崩潰,重新啟動數(shù)據(jù)庫系統(tǒng)后,數(shù)據(jù)庫還能恢復(fù)到事務(wù)成功結(jié)束時的狀態(tài)。
2.2、編程式事務(wù)
事務(wù)功能的相關(guān)操作全部通過自己編寫代碼來實(shí)現(xiàn):
Connection conn = ...;
try {
// 開啟事務(wù):關(guān)閉事務(wù)的自動提交
conn.setAutoCommit(false);
// 核心操作
// 提交事務(wù)
conn.commit();
}catch(Exception e){
// 回滾事務(wù)
conn.rollBack();
}finally{
// 釋放數(shù)據(jù)庫連接
conn.close();
}
編程式的實(shí)現(xiàn)方式存在缺陷:
- 細(xì)節(jié)沒有被屏蔽:具體操作過程中,所有細(xì)節(jié)都需要程序員自己來完成,比較繁瑣。
- 代碼復(fù)用性不高:如果沒有有效抽取出來,每次實(shí)現(xiàn)功能都需要自己編寫代碼,代碼就沒有得到復(fù)用。
2.3、聲明式事務(wù)
既然事務(wù)控制的代碼有規(guī)律可循,代碼的結(jié)構(gòu)基本是確定的,所以框架就可以將固定模式的代碼抽取出來,進(jìn)行相關(guān)的封裝。
封裝起來后,我們只需要在配置文件中進(jìn)行簡單的配置即可完成操作。
- 好處1:提高開發(fā)效率
- 好處2:消除了冗余的代碼
- 好處3:框架會綜合考慮相關(guān)領(lǐng)域中在實(shí)際開發(fā)環(huán)境下有可能遇到的各種問題,進(jìn)行了健壯性、性能等各個方面的優(yōu)化
所以,我們可以總結(jié)下面兩個概念:
- 編程式:自己寫代碼實(shí)現(xiàn)功能
- 聲明式:通過配置讓框架實(shí)現(xiàn)功能
3、基于注解的聲明式事務(wù)
3.1、準(zhǔn)備工作
①添加配置
在beans.xml添加配置
<!--掃描組件-->
<context:component-scan base-package="com.atguigu.spring6"></context:component-scan>
②創(chuàng)建表
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 '價(jià)格',
`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);
③創(chuàng)建組件
創(chuàng)建BookController:
package com.atguigu.spring6.controller;
@Controller
public class BookController {
@Autowired
private BookService bookService;
public void buyBook(Integer bookId, Integer userId){
bookService.buyBook(bookId, userId);
}
}
創(chuàng)建接口BookService:
package com.atguigu.spring6.service;
public interface BookService {
void buyBook(Integer bookId, Integer userId);
}
創(chuàng)建實(shí)現(xiàn)類BookServiceImpl:
package com.atguigu.spring6.service.impl;
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
public void buyBook(Integer bookId, Integer userId) {
//查詢圖書的價(jià)格
Integer price = bookDao.getPriceByBookId(bookId);
//更新圖書的庫存
bookDao.updateStock(bookId);
//更新用戶的余額
bookDao.updateBalance(userId, price);
}
}
創(chuàng)建接口BookDao:
package com.atguigu.spring6.dao;
public interface BookDao {
Integer getPriceByBookId(Integer bookId);
void updateStock(Integer bookId);
void updateBalance(Integer userId, Integer price);
}
創(chuàng)建實(shí)現(xiàn)類BookDaoImpl:
package com.atguigu.spring6.dao.impl;
@Repository
public class BookDaoImpl implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Integer getPriceByBookId(Integer bookId) {
String sql = "select price from t_book where book_id = ?";
return jdbcTemplate.queryForObject(sql, Integer.class, bookId);
}
@Override
public void updateStock(Integer bookId) {
String sql = "update t_book set stock = stock - 1 where book_id = ?";
jdbcTemplate.update(sql, bookId);
}
@Override
public void updateBalance(Integer userId, Integer price) {
String sql = "update t_user set balance = balance - ? where user_id = ?";
jdbcTemplate.update(sql, price, userId);
}
}
3.2、測試無事務(wù)情況
①創(chuàng)建測試類
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 {
@Autowired
private BookController bookController;
@Test
public void testBuyBook(){
bookController.buyBook(1, 1);
}
}
②模擬場景
用戶購買圖書,先查詢圖書的價(jià)格,再更新圖書的庫存和用戶的余額
假設(shè)用戶id為1的用戶,購買id為1的圖書
用戶余額為50,而圖書價(jià)格為80
購買圖書之后,用戶的余額為-30,數(shù)據(jù)庫中余額字段設(shè)置了無符號,因此無法將-30插入到余額字段
此時執(zhí)行sql語句會拋出SQLException
③觀察結(jié)果
因?yàn)闆]有添加事務(wù),圖書的庫存更新了,但是用戶的余額沒有更新
顯然這樣的結(jié)果是錯誤的,購買圖書是一個完整的功能,更新庫存和更新余額要么都成功要么都失敗
3.3、加入事務(wù)
①添加事務(wù)配置
在spring配置文件中引入tx命名空間
<?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">
在Spring的配置文件中添加配置:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!--
開啟事務(wù)的注解驅(qū)動
通過注解@Transactional所標(biāo)識的方法或標(biāo)識的類中所有的方法,都會被事務(wù)管理器管理事務(wù)
-->
<!-- transaction-manager屬性的默認(rèn)值是transactionManager,如果事務(wù)管理器bean的id正好就是這個默認(rèn)值,則可以省略這個屬性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
②添加事務(wù)注解
因?yàn)閟ervice層表示業(yè)務(wù)邏輯層,一個方法表示一個完成的功能,因此處理事務(wù)一般在service層處理
在BookServiceImpl的buybook()添加注解@Transactional
③觀察結(jié)果
由于使用了Spring的聲明式事務(wù),更新庫存和更新余額都沒有執(zhí)行
3.4、@Transactional注解標(biāo)識的位置
@Transactional標(biāo)識在方法上,則只會影響該方法
@Transactional標(biāo)識的類上,則會影響類中所有的方法
3.5、事務(wù)屬性:只讀
①介紹
對一個查詢操作來說,如果我們把它設(shè)置成只讀,就能夠明確告訴數(shù)據(jù)庫,這個操作不涉及寫操作。這樣數(shù)據(jù)庫就能夠針對查詢操作來進(jìn)行優(yōu)化。
②使用方式
@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) {
//查詢圖書的價(jià)格
Integer price = bookDao.getPriceByBookId(bookId);
//更新圖書的庫存
bookDao.updateStock(bookId);
//更新用戶的余額
bookDao.updateBalance(userId, price);
//System.out.println(1/0);
}
③注意
對增刪改操作設(shè)置只讀會拋出下面異常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
3.6、事務(wù)屬性:超時
①介紹
事務(wù)在執(zhí)行過程中,有可能因?yàn)橛龅侥承﹩栴},導(dǎo)致程序卡住,從而長時間占用數(shù)據(jù)庫資源。而長時間占用資源,大概率是因?yàn)槌绦蜻\(yùn)行出現(xiàn)了問題(可能是Java程序或MySQL數(shù)據(jù)庫或網(wǎng)絡(luò)連接等等)。此時這個很可能出問題的程序應(yīng)該被回滾,撤銷它已做的操作,事務(wù)結(jié)束,把資源讓出來,讓其他正常程序可以執(zhí)行。
概括來說就是一句話:超時回滾,釋放資源。
②使用方式
//超時時間單位秒
@Transactional(timeout = 3)
public void buyBook(Integer bookId, Integer userId) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//查詢圖書的價(jià)格
Integer price = bookDao.getPriceByBookId(bookId);
//更新圖書的庫存
bookDao.updateStock(bookId);
//更新用戶的余額
bookDao.updateBalance(userId, price);
//System.out.println(1/0);
}
③觀察結(jié)果
執(zhí)行過程中拋出異常:
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022
3.7、事務(wù)屬性:回滾策略
①介紹
聲明式事務(wù)默認(rèn)只針對運(yùn)行時異?;貪L,編譯時異常不回滾。
可以通過@Transactional中相關(guān)屬性設(shè)置回滾策略
-
rollbackFor屬性:需要設(shè)置一個Class類型的對象
-
rollbackForClassName屬性:需要設(shè)置一個字符串類型的全類名
-
noRollbackFor屬性:需要設(shè)置一個Class類型的對象
-
rollbackFor屬性:需要設(shè)置一個字符串類型的全類名
②使用方式
@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void buyBook(Integer bookId, Integer userId) {
//查詢圖書的價(jià)格
Integer price = bookDao.getPriceByBookId(bookId);
//更新圖書的庫存
bookDao.updateStock(bookId);
//更新用戶的余額
bookDao.updateBalance(userId, price);
System.out.println(1/0);
}
③觀察結(jié)果
雖然購買圖書功能中出現(xiàn)了數(shù)學(xué)運(yùn)算異常(ArithmeticException),但是我們設(shè)置的回滾策略是,當(dāng)出現(xiàn)ArithmeticException不發(fā)生回滾,因此購買圖書的操作正常執(zhí)行
3.8、事務(wù)屬性:隔離級別
①介紹
數(shù)據(jù)庫系統(tǒng)必須具有隔離并發(fā)運(yùn)行各個事務(wù)的能力,使它們不會相互影響,避免各種并發(fā)問題。一個事務(wù)與其他事務(wù)隔離的程度稱為隔離級別。SQL標(biāo)準(zhǔn)中規(guī)定了多種事務(wù)隔離級別,不同隔離級別對應(yīng)不同的干擾程度,隔離級別越高,數(shù)據(jù)一致性就越好,但并發(fā)性越弱。
隔離級別一共有四種:
-
讀未提交:READ UNCOMMITTED
允許Transaction01讀取Transaction02未提交的修改。
-
讀已提交:READ COMMITTED、
要求Transaction01只能讀取Transaction02已提交的修改。
-
可重復(fù)讀:REPEATABLE READ
確保Transaction01可以多次從一個字段中讀取到相同的值,即Transaction01執(zhí)行期間禁止其它事務(wù)對這個字段進(jìn)行更新。
-
串行化:SERIALIZABLE
確保Transaction01可以多次從一個表中讀取到相同的行,在Transaction01執(zhí)行期間,禁止其它事務(wù)對這個表進(jìn)行添加、更新、刪除操作??梢员苊馊魏尾l(fā)問題,但性能十分低下。
各個隔離級別解決并發(fā)問題的能力見下表:
隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
---|---|---|---|
READ UNCOMMITTED | 有 | 有 | 有 |
READ COMMITTED | 無 | 有 | 有 |
REPEATABLE READ | 無 | 無 | 有 |
SERIALIZABLE | 無 | 無 | 無 |
各種數(shù)據(jù)庫產(chǎn)品對事務(wù)隔離級別的支持程度:
隔離級別 | Oracle | MySQL |
---|---|---|
READ UNCOMMITTED | × | √ |
READ COMMITTED | √(默認(rèn)) | √ |
REPEATABLE READ | × | √(默認(rèn)) |
SERIALIZABLE | √ | √ |
②使用方式
@Transactional(isolation = Isolation.DEFAULT)//使用數(shù)據(jù)庫默認(rèn)的隔離級別
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//讀未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//讀已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重復(fù)讀
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
3.9、事務(wù)屬性:傳播行為
①介紹
什么是事務(wù)的傳播行為?
在service類中有a()方法和b()方法,a()方法上有事務(wù),b()方法上也有事務(wù),當(dāng)a()方法執(zhí)行過程中調(diào)用了b()方法,事務(wù)是如何傳遞的?合并到一個事務(wù)里?還是開啟一個新的事務(wù)?這就是事務(wù)傳播行為。
一共有七種傳播行為:
- REQUIRED:支持當(dāng)前事務(wù),如果不存在就新建一個(默認(rèn))【沒有就新建,有就加入】
- SUPPORTS:支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就以非事務(wù)方式執(zhí)行**【有就加入,沒有就不管了】**
- MANDATORY:必須運(yùn)行在一個事務(wù)中,如果當(dāng)前沒有事務(wù)正在發(fā)生,將拋出一個異常**【有就加入,沒有就拋異?!?*
- REQUIRES_NEW:開啟一個新的事務(wù),如果一個事務(wù)已經(jīng)存在,則將這個存在的事務(wù)掛起**【不管有沒有,直接開啟一個新事務(wù),開啟的新事務(wù)和之前的事務(wù)不存在嵌套關(guān)系,之前事務(wù)被掛起】**
- NOT_SUPPORTED:以非事務(wù)方式運(yùn)行,如果有事務(wù)存在,掛起當(dāng)前事務(wù)**【不支持事務(wù),存在就掛起】**
- NEVER:以非事務(wù)方式運(yùn)行,如果有事務(wù)存在,拋出異常**【不支持事務(wù),存在就拋異?!?*
- NESTED:如果當(dāng)前正有一個事務(wù)在進(jìn)行中,則該方法應(yīng)當(dāng)運(yùn)行在一個嵌套式事務(wù)中。被嵌套的事務(wù)可以獨(dú)立于外層事務(wù)進(jìn)行提交或回滾。如果外層事務(wù)不存在,行為就像REQUIRED一樣。【有事務(wù)的話,就在這個事務(wù)里再嵌套一個完全獨(dú)立的事務(wù),嵌套的事務(wù)可以獨(dú)立的提交和回滾。沒有事務(wù)就和REQUIRED一樣。】
②測試
創(chuàng)建接口CheckoutService:
package com.atguigu.spring6.service;
public interface CheckoutService {
void checkout(Integer[] bookIds, Integer userId);
}
創(chuàng)建實(shí)現(xiàn)類CheckoutServiceImpl:
package com.atguigu.spring6.service.impl;
@Service
public class CheckoutServiceImpl implements CheckoutService {
@Autowired
private 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);
}
在數(shù)據(jù)庫中將用戶的余額修改為100元
③觀察結(jié)果
可以通過@Transactional中的propagation屬性設(shè)置事務(wù)傳播行為
修改BookServiceImpl中buyBook()上,注解@Transactional的propagation屬性
@Transactional(propagation = Propagation.REQUIRED),默認(rèn)情況,表示如果當(dāng)前線程上有已經(jīng)開啟的事務(wù)可用,那么就在這個事務(wù)中運(yùn)行。經(jīng)過觀察,購買圖書的方法buyBook()在checkout()中被調(diào)用,checkout()上有事務(wù)注解,因此在此事務(wù)中執(zhí)行。所購買的兩本圖書的價(jià)格為80和50,而用戶的余額為100,因此在購買第二本圖書時余額不足失敗,導(dǎo)致整個checkout()回滾,即只要有一本書買不了,就都買不了
@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管當(dāng)前線程上是否有已經(jīng)開啟的事務(wù),都要開啟新事務(wù)。同樣的場景,每次購買圖書都是在buyBook()的事務(wù)中執(zhí)行,因此第一本圖書購買成功,事務(wù)結(jié)束,第二本圖書購買失敗,只在第二次的buyBook()中回滾,購買第一本圖書不受影響,即能買幾本就買幾本。
3.10、全注解配置事務(wù)
①添加配置類
package com.atguigu.spring6.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.atguigu.spring6")
@EnableTransactionManagement
public class SpringConfig {
@Bean
public 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;
}
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
②測試
import com.atguigu.spring6.config.SpringConfig;
import com.atguigu.spring6.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 {
@Test
public void testTxAllAnnotation(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
BookController accountService = applicationContext.getBean("bookController", BookController.class);
accountService.buyBook(1, 1);
}
}
4、基于XML的聲明式事務(wù)
4.1、場景模擬
參考基于注解的聲明式事務(wù)
4.2、修改Spring配置文件
將Spring配置文件中去掉tx:annotation-driven 標(biāo)簽,并添加配置:文章來源:http://www.zghlxwxcb.cn/news/detail-713740.html
<aop:config>
<!-- 配置事務(wù)通知和切入點(diǎn)表達(dá)式 -->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.atguigu.spring.tx.xml.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice標(biāo)簽:配置事務(wù)通知 -->
<!-- id屬性:給事務(wù)通知標(biāo)簽設(shè)置唯一標(biāo)識,便于引用 -->
<!-- transaction-manager屬性:關(guān)聯(lián)事務(wù)管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- tx:method標(biāo)簽:配置具體的事務(wù)方法 -->
<!-- name屬性:指定方法名,可以使用星號代表多個字符 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<!-- read-only屬性:設(shè)置只讀屬性 -->
<!-- rollback-for屬性:設(shè)置回滾的異常 -->
<!-- no-rollback-for屬性:設(shè)置不回滾的異常 -->
<!-- isolation屬性:設(shè)置事務(wù)的隔離級別 -->
<!-- timeout屬性:設(shè)置事務(wù)的超時屬性 -->
<!-- propagation屬性:設(shè)置事務(wù)的傳播行為 -->
<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>
注意:基于xml實(shí)現(xiàn)的聲明式事務(wù),必須引入aspectJ的依賴文章來源地址http://www.zghlxwxcb.cn/news/detail-713740.html
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.2</version> </dependency>
到了這里,關(guān)于spring6-事務(wù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!