百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

spring事物中的传播及隔离(简述spring的事物传播行为和隔离级别)

bigegpt 2024-08-01 12:02 15 浏览

添加事务注解

1、使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时。如何使用事务, 默认取值为 REQUIRED, 即使用调用方法的事务REQUIRES_NEW: 事务自己的事务, 调用的事务方法的事务被挂起。

2、使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED。

3、默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的属性进行设置. 通常情况下去默认值即可。

4、使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务. 若真的是一个只读取数据库值的方法, 应设置 readOnly=true

5、使用 timeout 指定强制回滚之前事务可以占用的时间。

个人疑问有一些,结尾来说,用教程例子来说吧。

我的代码如下:

BookShopDao接口

package
 com.demo.spring.bean;
public
 
interface
 
BookShopDao
 {
 
//根据书的编号返回书的单价
 
public
 
int
 findBookPriceByIsbn(
String
 isbn);
 
//根据书的编号返回输的库存
 
public
 
void
 updateBookStock(
String
 isbn);
 
//更新用户账户余额
 
public
 
void
 updateUserAccount(
String
 username, 
int
 price);
}

实现类

package
 com.demo.spring.bean;
import
 org.springframework.beans.factory.annotation.
Autowired
;
import
 org.springframework.jdbc.core.
JdbcTemplate
;
import
 org.springframework.stereotype.
Repository
;
@Repository
(
"bookShopDao"
)
public
 
class
 
BookShopDaoImpl
 
implements
 
BookShopDao
 {
 
@Autowired
 
private
 
JdbcTemplate
 jdbcTemplate;
 
@Override
 
public
 
int
 findBookPriceByIsbn(
String
 isbn) {
 
// TODO Auto-generated method stub
 
String
 sql = 
"SELECT price FROM book WHERE isbn=?"
;
 
Integer
 price = jdbcTemplate.queryForObject(sql, 
Integer
.
class
, isbn);
 
return
 price;
 }
 
@Override
 
public
 
void
 updateBookStock(
String
 isbn) {
 
// TODO Auto-generated method stub
 
String
 sql = 
"SELECT stock FROM book_stock WHERE isbn= ? "
;
 
Integer
 stock = jdbcTemplate.queryForObject(sql, 
Integer
.
class
, isbn);
 
if
 (stock == 
0
) {
 
throw
 
new
 
BookStockException
(
"库存不足!!"
);
 }
 
String
 sql1 = 
"UPDATE book_stock SET stock=stock-1 WHERE isbn=?"
;
 jdbcTemplate.update(sql1, isbn);
 }
 
@Override
 
public
 
void
 updateUserAccount(
String
 username, 
int
 price) {
 
// TODO Auto-generated method stub
 
String
 sql1 = 
"SELECT balance FROM account WHERE username= ? "
;
 
Integer
 account = jdbcTemplate.queryForObject(sql1, 
Integer
.
class
,
 username);
 
if
 (account < price) {
 
throw
 
new
 
AccountException
(
"余额不足!!"
);
 }
 
String
 sql = 
"UPDATE account SET balance=balance-? WHERE username=?"
;
 jdbcTemplate.update(sql, price, username);
 }
}

BookShopService接口

package
 com.demo.spring.bean;
public
 
interface
 
BookShopService
 {
//购书
public
 
void
 purchase(
String
 username, 
String
 isbn);
}

实现类

package
 com.demo.spring.bean;
import
 org.springframework.beans.factory.annotation.
Autowired
;
import
 org.springframework.stereotype.
Service
;
import
 org.springframework.transaction.annotation.
Transactional
;
@Service
(
"bookShopService"
)
public
 
class
 
BookShopServiceImpl
 
implements
 
BookShopService
 {
 
@Autowired
 
private
 
BookShopDao
 bookShopDao;
// @Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED,readOnly=true,timeout=5,noRollbackFor=AccountException.class)
 
@Transactional
 
@Override
 
public
 
void
 purchase(
String
 username, 
String
 isbn) {
 
try
 {
 
Thread
.sleep(
5000
);
 } 
catch
 (
InterruptedException
 e) {
 
// TODO Auto-generated catch block
 e.printStackTrace();
 }
 
// TODO Auto-generated method stub
 
int
 price = bookShopDao.findBookPriceByIsbn(isbn);
 bookShopDao.updateBookStock(isbn);
 bookShopDao.updateUserAccount(username, price);
 }
}

Cashier批量购书接口

package
 com.demo.spring.bean;
import
 java.util.
List
;
public
 
interface
 
Cashier
 {
 
//批量购书
 
public
 
void
 checkout(
String
 username, 
List
<
String
> isbns);
}

实现类

package
 com.demo.spring.bean;
import
 java.util.
List
;
import
 org.springframework.beans.factory.annotation.
Autowired
;
import
 org.springframework.stereotype.
Service
;
@Service
(
"cashier"
)
public
 
class
 
CashierImpl
 
implements
 
Cashier
 {
 
@Autowired
 
private
 
BookShopService
 bookShopService;
// @Transactional
 
@Override
 
public
 
void
 checkout(
String
 username, 
List
<
String
> isbns) {
 
// TODO Auto-generated method stub
 
for
 (
String
 isbn : isbns) {
 bookShopService.purchase(username, isbn);
 }
 }
}

账户余额不足异常(自定义异常)

package
 com.demo.spring.bean;
public
 
class
 
AccountException
 
extends
 
RuntimeException
 {
 
/**
 *
 */
 
private
 
static
 
final
 
long
 serialVersionUID = 
1L
;
 
public
 
AccountException
() {
 
super
();
 
// TODO Auto-generated constructor stub
 }
 
public
 
AccountException
(
String
 message, 
Throwable
 cause,
 
boolean
 enableSuppression, 
boolean
 writableStackTrace) {
 
super
(message, cause, enableSuppression, writableStackTrace);
 
// TODO Auto-generated constructor stub
 }
 
public
 
AccountException
(
String
 message, 
Throwable
 cause) {
 
super
(message, cause);
 
// TODO Auto-generated constructor stub
 }
 
public
 
AccountException
(
String
 message) {
 
super
(message);
 
// TODO Auto-generated constructor stub
 }
 
public
 
AccountException
(
Throwable
 cause) {
 
super
(cause);
 
// TODO Auto-generated constructor stub
 }
}

库存不足异常(自定义异常)

package
 com.demo.spring.bean;
public
 
class
 
BookStockException
 
extends
 
RuntimeException
 {
 
private
 
static
 
final
 
long
 serialVersionUID = 
1L
;
 
public
 
BookStockException
() {
 
super
();
 
// TODO Auto-generated constructor stub
 }
 
public
 
BookStockException
(
String
 message, 
Throwable
 cause,
 
boolean
 enableSuppression, 
boolean
 writableStackTrace) {
 
super
(message, cause, enableSuppression, writableStackTrace);
 
// TODO Auto-generated constructor stub
 }
 
public
 
BookStockException
(
String
 message, 
Throwable
 cause) {
 
super
(message, cause);
 
// TODO Auto-generated constructor stub
 }
 
public
 
BookStockException
(
String
 message) {
 
super
(message);
 
// TODO Auto-generated constructor stub
 }
 
public
 
BookStockException
(
Throwable
 cause) {
 
super
(cause);
 
// TODO Auto-generated constructor stub
 }
}

测试类

package
 com.demo.spring.bean;
import
 java.util.
Arrays
;
import
 org.junit.
Test
;
import
 org.springframework.beans.factory.annotation.
Autowired
;
import
 org.springframework.context.
ApplicationContext
;
import
 org.springframework.context.support.
ClassPathXmlApplicationContext
;
public
 
class
 
MainTest
 {
 
private
 
ApplicationContext
 ctx;
 
@Autowired
 
private
 
Cashier
 cashier;
 {
 ctx=
new
 
ClassPathXmlApplicationContext
(
"bean.xml"
);
 cashier=(
Cashier
) ctx.getBean(
"cashier"
);
 }
 
@Test
 
public
 
void
 test(){
// System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
 cashier.checkout(
"rongrong"
, 
Arrays
.asList(
"1001"
,
"1002"
));
 }
}

bean文件

<?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:aop
=
"http://www.springframework.org/schema/aop"
 
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-3.2.xsd
 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd"
>
 
<!-- 装载自导导入的包 -->
 
<context:component-scan
base-package
=
"com.demo.spring.bean"
></context:component-scan>
 
<!-- 引入外部数据源 -->
 
<context:property-placeholder
 
location
=
"classpath:db.properties"
/>
 
<!-- 配置mysql数据源 -->
 
<bean
 
id
=
"datasource"
 
class
=
"org.springframework.jdbc.datasource.DriverManagerDataSource"
>
 
<property
 
name
=
"driverClassName"
 
value
=
"${db.driverClassName}"
></property>
 
<property
 
name
=
"url"
 
value
=
"${db.url}"
></property>
 
<property
 
name
=
"username"
 
value
=
"${db.username}"
></property>
 
<property
 
name
=
"password"
 
value
=
"${db.password}"
></property>
 
</bean>
 
<!-- 配置jdbc模板 -->
 
<bean
 
id
=
"jdbcTemplate"
 
class
=
"org.springframework.jdbc.core.JdbcTemplate"
>
 
<property
 
name
=
"dataSource"
 
ref
=
"datasource"
></property>
 
</bean>
 
<!-- 配置事务管理器 -->
 
<bean
 
id
=
"transactionManagertest"
 
class
=
"org.springframework.jdbc.datasource.DataSourceTransactionManager"
>
 
<property
 
name
=
"dataSource"
 
ref
=
"datasource"
></property>
 
</bean>
 
 
<!-- 启用事务注解 -->
 
<tx:annotation-driven
 
transaction-manager
=
"transactionManagertest"
/>
</beans>

数据源

db.driverClassName=com.mysql.jdbc.
Driver
db.url=jdbc:mysql:
//localhost:3306/spring
db.username=root
db.password=root



疑问如下:

1、前提条件:用户按照1001,1002这种顺序去购书,调用checkout接口批量购书 @Transactional注解确实满足原子性操作,要么都做,要么不做 但是,我试验了下,如果在public void checkout(String username, Listisbns) 上方不加Transactional注解与在public void purchase(String username, String isbn)上方加@Transactional(propagation=Propagation.REQUIRES_NEW效果一样

2、当账户余额为120,可以满足账户减去1001的单价,1001的库存减1 但是,将账户余额改为80,从单价上看可以买1002那本书,按照上面的顺序去买书,按照上面加上注解@Transactional(propagation=Propagation.REQUIRES_NEW效果一样,根本不好使,我个人觉得因为在更新账户余额,那有个判断先查询1001的书的单价确实大于当前账户余额80,先判断了,所以抛异常后面代码就不走了

3、另外当前账户余额可以买1002这本书,想在不改变购书的顺序情况下,用@Transactional注解实现,可以买1002这本书?,减去当前账户余额80,更新1002书的库存,哪位大神看到,帮我看下,怎么用这个注解实现?

以上为我的个人疑惑的点,有兴趣的同学可以研究下,然后在留言私信给我即可,小编不胜感谢!

附上:sql

/*
Navicat MySQL Data Transfer
Source Server : myTestdata
Source Server Version : 50627
Source Host : localhost:3306
Source Database : spring
Target Server Type : MYSQL
Target Server Version : 50627
File Encoding : 65001
Date: 2017-01-18 11:28:50
*/
SET FOREIGN_KEY_CHECKS=
0
;
-- ----------------------------
-- 
Table
 structure 
for
 account
-- ----------------------------
DROP TABLE IF EXISTS 
`account`
;
CREATE TABLE 
`account`
 (
 
`username`
 varchar(
50
) NOT NULL,
 
`balance`
 
int
(
11
) DEFAULT NULL,
 PRIMARY KEY (
`username`
)
) ENGINE=
InnoDB
 DEFAULT CHARSET=gbk;
-- ----------------------------
-- 
Records
 of account
-- ----------------------------
INSERT INTO 
`account`
 VALUES (
'rongrong'
, 
'300'
);
INSERT INTO 
`account`
 VALUES (
'zhangsan'
, 
'200'
);
-- ----------------------------
-- 
Table
 structure 
for
 book
-- ----------------------------
DROP TABLE IF EXISTS 
`book`
;
CREATE TABLE 
`book`
 (
 
`isbn`
 varchar(
50
) NOT NULL,
 
`book_name`
 varchar(
100
) DEFAULT NULL,
 
`price`
 
int
(
11
) DEFAULT NULL,
 PRIMARY KEY (
`isbn`
)
) ENGINE=
InnoDB
 DEFAULT CHARSET=gbk;
-- ----------------------------
-- 
Records
 of book
-- ----------------------------
INSERT INTO 
`book`
 VALUES (
'1001'
, 
'java'
, 
'100'
);
INSERT INTO 
`book`
 VALUES (
'1002'
, 
'python'
, 
'60'
);
-- ----------------------------
-- 
Table
 structure 
for
 book_stock
-- ----------------------------
DROP TABLE IF EXISTS 
`book_stock`
;
CREATE TABLE 
`book_stock`
 (
 
`isbn`
 varchar(
50
) NOT NULL,
 
`bookname`
 varchar(
100
) DEFAULT NULL,
 
`stock`
 
int
(
11
) DEFAULT NULL,
 PRIMARY KEY (
`isbn`
)
) ENGINE=
InnoDB
 DEFAULT CHARSET=gbk;
-- ----------------------------
-- 
Records
 of book_stock
-- ----------------------------
INSERT INTO 
`book_stock`
 VALUES (
'1001'
, 
'java'
, 
'10'
);
INSERT INTO 
`book_stock`
 VALUES (
'1002'
, 
'python'
, 
'10'
);

以上就是我的对此问题的整理和思考。如果你对此话题有自己的思考和理解,也欢迎留言私信一起探讨!

私信我:“资料”,可免费领取更多学习资料

相关推荐

Go语言泛型-泛型约束与实践(go1.7泛型)

来源:械说在Go语言中,Go泛型-泛型约束与实践部分主要探讨如何定义和使用泛型约束(Constraints),以及如何在实际开发中利用泛型进行更灵活的编程。以下是详细内容:一、什么是泛型约束?**泛型...

golang总结(golang实战教程)

基础部分Go语言有哪些优势?1简单易学:语法简洁,减少了代码的冗余。高效并发:内置强大的goroutine和channel,使并发编程更加高效且易于管理。内存管理:拥有自动垃圾回收机制,减少内...

Go 官宣:新版 Protobuf API(go pro版本)

原文作者:JoeTsai,DamienNeil和HerbieOng原文链接:https://blog.golang.org/a-new-go-api-for-protocol-buffer...

Golang开发的一些注意事项(一)(golang入门项目)

1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...

golang 托盘菜单应用及打开系统默认浏览器

之前看到一个应用,用go语言编写,说是某某程序的windows图形化客户端,体验一下发现只是一个托盘,然后托盘菜单的控制面板功能直接打开本地浏览器访问程序启动的webserver网页完成gui相关功...

golang标准库每日一库之 io/ioutil

一、核心函数概览函数作用描述替代方案(Go1.16+)ioutil.ReadFile(filename)一次性读取整个文件内容(返回[]byte)os.ReadFileioutil.WriteFi...

文件类型更改器——GoLang 中的 CLI 工具

我是如何为一项琐碎的工作任务创建一个简单的工具的,你也可以上周我开始玩GoLang,它是一种由Google制作的类C编译语言,非常轻量和快速,事实上它经常在Techempower的基准测...

Go (Golang) 中的 Channels 简介(golang channel长度和容量)

这篇文章重点介绍Channels(通道)在Go中的工作方式,以及如何在代码中使用它们。在Go中,Channels是一种编程结构,它允许我们在代码的不同部分之间移动数据,通常来自不同的goro...

Golang引入泛型:Go将Interface「」替换为“Any”

现在Go将拥有泛型:Go将Interface{}替换为“Any”,这是一个类型别名:typeany=interface{}这会引入了泛型作好准备,实际上,带有泛型的Go1.18Beta...

一文带你看懂Golang最新特性(golang2.0特性)

作者:腾讯PCG代码委员会经过十余年的迭代,Go语言逐渐成为云计算时代主流的编程语言。下到云计算基础设施,上到微服务,越来越多的流行产品使用Go语言编写。可见其影响力已经非常强大。一、Go语言发展历史...

Go 每日一库之 java 转 go 遇到 Apollo?让 agollo 来平滑迁移

以下文章来源于GoOfficialBlog,作者GoOfficialBlogIntroductionagollo是Apollo的Golang客户端Apollo(阿波罗)是携程框架部门研...

Golang使用grpc详解(golang gcc)

gRPC是Google开源的一种高性能、跨语言的远程过程调用(RPC)框架,它使用ProtocolBuffers作为序列化工具,支持多种编程语言,如C++,Java,Python,Go等。gR...

Etcd服务注册与发现封装实现--golang

服务注册register.gopackageregisterimport("fmt""time"etcd3"github.com/cor...

Golang:将日志以Json格式输出到Kafka

在上一篇文章中我实现了一个支持Debug、Info、Error等多个级别的日志库,并将日志写到了磁盘文件中,代码比较简单,适合练手。有兴趣的可以通过这个链接前往:https://github.com/...

如何从 PHP 过渡到 Golang?(php转golang)

我是PHP开发者,转Go两个月了吧,记录一下使用Golang怎么一步步开发新项目。本着有坑填坑,有错改错的宗旨,从零开始,开始学习。因为我司没有专门的Golang大牛,所以我也只能一步步自己去...