Spring 事务、事务管理、事务管理器、事务的传播性、@Transactional
源码获取github
1.实例引入
和尚经理跟某个书店沟通业务,王经理有一个需求需要买书的操作(暂时只能让买一本书,使用会员卡)
需求:买一本书===>需要的步骤
和尚经理思考:
- 查询书的价格
- 判断余额是否充足(获取余额的信息)
- 会员中的余额 — 书价格
- 判断库存是否充足(获取书库存数量)
- 库存书的数量 — 1
2.数据库表
SET FOREIGN_KEY_CHECKS=0;
------
-- Table structure for tx_book
------
DROP TABLE IF EXISTS `tx_book`;
CREATE TABLE `tx_book` (
`isbn` varchar(255) NOT NULL,
`book_name` varchar(255) DEFAULT NULL,
`price` int(11) DEFAULT NULL,
PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
------
-- Records of tx_book
------
INSERT INTO `tx_book` VALUES ('1001', '西游记', '50');
INSERT INTO `tx_book` VALUES ('1002', '水浒', '60');
INSERT INTO `tx_book` VALUES ('1003', '三国', '70');
------
-- Table structure for tx_book_stock
------
DROP TABLE IF EXISTS `tx_book_stock`;
CREATE TABLE `tx_book_stock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`isbn` varchar(255) DEFAULT NULL,
`stock` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
------
-- Records of tx_book_stock
------
INSERT INTO `tx_book_stock` VALUES ('1', '1001', '10');
INSERT INTO `tx_book_stock` VALUES ('2', '1002', '10');
INSERT INTO `tx_book_stock` VALUES ('3', '1003', '10');
------
-- Table structure for tx_user
------
DROP TABLE IF EXISTS `tx_user`;
CREATE TABLE `tx_user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`account` varchar(255) DEFAULT NULL,
`balance` int(255) DEFAULT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
------
-- Records of tx_user
------
INSERT INTO `tx_user` VALUES ('1', 'wukong', '100');
INSERT INTO `tx_user` VALUES ('2', 'bajie', '200');
INSERT INTO `tx_user` VALUES ('3', 'tangseng', '100');
3.项目结构
4.dao层
接口
package com.hs.dao;
public interface BookDao {
/**
* 通过书的isbn查看这本书的价格
* @param isbn
* @return
*/
int getBookPriceByIsbn(String isbn);
/**
* 减去用户里的钱
* @param account
* @param price
*/
void updateUserBalance(String account, int price);
/**
* 减少书的数量
* @param isbn
*/
void updateBookStock(String isbn);
/**
* 查询用户的余额
* @param account
* @return
*/
int getUserBalanceByAccount(String account);
/**
* 查询书的库存
* @param isbn
* @return
*/
int getBookStockByIsbn(String isbn);
}
package com.hs.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository //<bean id =bookDaoImpl >
public class BookDaoImpl implements BookDao {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public int getBookPriceByIsbn(String isbn) {
String sql = "select price from tx_book where isbn=?";
int price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
return price;
}
@Override
public void updateUserBalance(String account, int price) {
String sql = "update tx_user set balance = balance-? where account = ?";
jdbcTemplate.update(sql, price, account);
}
@Override
public void updateBookStock(String isbn) {
String sql = "update tx_book_stock set stock = stock-1 where isbn = ?";
jdbcTemplate.update(sql, isbn);
}
@Override
public int getUserBalanceByAccount(String account) {
String sql = "select balance from tx_user where account=?";
int balance = jdbcTemplate.queryForObject(sql, Integer.class, account);
return balance;
}
@Override
public int getBookStockByIsbn(String isbn) {
String sql = "select stock from tx_book_stock where isbn=?";
int stock = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
return stock;
}
}
5.Service层
接口
package com.hs.service;
public interface OneBookService {
void buyOneBook(String account, String isbn);
}
package com.hs.service;
import com.hs.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service //<bean id = "oneBookServiceImpl" class="xxx">
public class OneBookServiceImpl implements OneBookService {
//建立联系
@Autowired //直接在属性上注解setter方式,也可以在setter方法上注解
//指明具体的id
@Qualifier("bookDaoImpl")
//<property name = "BookDao" ref="bookDaoImpl">
private BookDao bookDao;
@Override
public void buyOneBook(String account, String isbn) {
//1.查询书的价格
int price = bookDao.getBookPriceByIsbn(isbn);
//2.判断会员余额是否充足
int balance = bookDao.getUserBalanceByAccount(account);
if (balance < price) {
throw new RuntimeException("账号余额不足,请充值");
}
//3.会员中的余额 - 书价格
bookDao.updateUserBalance(account, price);
//4.判断库存数量是否充足
int stock = bookDao.getBookStockByIsbn(isbn);
if (stock == 0) {
throw new RuntimeException("书的库存数量不足");
}
//5.库存书的数量 - 1
bookDao.updateBookStock(isbn);
}
}
6.测试
package com.hs.test;
import com.hs.service.OneBookService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class OneBookServiceTest {
private ApplicationContext ac;
private OneBookService oneBookService;
@Before
public void init() {
ac = new ClassPathXmlApplicationContext("beans.xml");
oneBookService = ac.getBean("oneBookServiceImpl", OneBookService.class);
}
@Test
public void testBuyOneBookTest() {
oneBookService.buyOneBook("wukong", "1001");
}
}
此时如果把数据库里1001的库存改为0,然后再执行测试,就会发现wukong的钱扣了,但是控制台报错,书的库存数量不足,这样就引入了事务的概念!!
7.什么是事务?
一个工作单元由多个动作组成,只有动作全部正确的时候才能执行成功,如果有一个动作错了,其他的动作都是无效的(回滚)(事务就是一系列的动作,它们被当做一 个单独的工作单元.这些动作要么全部完成,要么全部不起作用)
事务的四个关键属性(ACID)
- 原子性(atomicity):事务是一一个原子操作,由一系列动作组成,事务的原子性确保动作要么全部完成要么完全不起作用
- 一致性(consistency):一旦所有事务动作完成,事务就被提交,数据和资源就处于一种满足业务规则的一-致性状态中.
- 隔离性(isolation):可能有许多事务会同时处理相同的数据,因此每个事物都应该与其他事务隔离开来,防止数据损坏。
- 持久性(durability): 一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,通常情况下,事务的结果被写到持久化存储器中
8.事务管理
事务管理就是管理事务,用来确保数据的完整性和一致性.
9.事务管理器
就是对事务管理的实现,数据的完整性和一致性(数据库—>数据源),MyBatis使用的是第一种
10.在XML配置事务,启动事务注解
<!--5.配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"/>
</bean>
<!--6.启动事务注解:告知该方法是事务方法(一个错,其他全部错),而不是普通方法
transaction-manager="transactionManager"可以省略
-->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
<!--操作数据库了的xml版本,没注释的是注解版本-->
<!--<bean id="bookDaoImpl" class="com.hs.dao.BookDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>-->
在方法前面加入注解@Transactional,或者类前面加这个注解,告之这个类的方法都是事务方法
//点石成金,标识这个方法是事务方法
@Transactional
再buyOneBook方法前面加入这个注解,再继续测试,就会发现,报书库存不足,但是wukong的钱没有扣。
11.@Transactional的属性
//@Transactional
//@Transactional(readOnly = true) //只读,一般只做查询操作使用
//@Transactional(noRollbackForClassName = "RuntimeException") //遇见这个异常,数据就不会回滚
// @Transactional(propagation = Propagation.REQUIRES_NEW)
// @Transactional(propagation = Propagation.REQUIRED)
12.事务的传播性
在上面的条件下,新增加如果买多本书这个功能
事务的传播性:当你事务方法被另一个事务方法调用的时候,需要检查其事务的传播性
可能延续调用方法的事务
也可能开启新的事务
买多本书的接口:
MoreBookService.java
package com.hs.service;
public interface MoreBookService {
/**
* 买多本书
* String... 是可变长度参数列表,可以存多个
* @param account
* @param isbns
*/
void buyMoreBook(String account, String... isbns);
}
MoreBookServiceImpl.java
package com.hs.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MoreBookServiceImpl implements MoreBookService {
@Autowired
private OneBookService oneBookService;
@Transactional //包含在这里面的都是事务方法,事务的传播性
@Override
public void buyMoreBook(String account, String... isbns) {
if (isbns != null) {
for (String isbn : isbns) {
oneBookService.buyOneBook(account,isbn);
}
}
}
}
buyMoreBook这个事务方法,调用了buyOneBook这个事务方法,然后检查buyOneBook这个事务方法的传播性,在这个方法上面写@Transactional,默认为延续调用方法的事务,下面的注解在被调用的事务方法上写
//@Transactional(propagation = Propagation.REQUIRES_NEW) //本方法延续调用方法的事务
// @Transactional(propagation = Propagation.REQUIRED) //本方法开启一个新的事务
测试
package com.hs.test;
import com.hs.service.MoreBookService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MoreBookServiceTest {
private ApplicationContext ac;
private MoreBookService moreBookService;
@Before
public void init() {
ac = new ClassPathXmlApplicationContext("beans.xml");
moreBookService = ac.getBean("moreBookServiceImpl", MoreBookService.class);
}
@Test
public void testBuyMoreBookMethod() {
moreBookService.buyMoreBook("wukong","1003","1001");
}
}