Spring事务的用法及案例(注解方式)入门
一、什么是Spring事务
事务Transaction,它是一些列严密操作动作,要么都操作完成,要么都回滚撤销。Spring事务管理基于底层数据库本身的事务处理机制。数据库事务的基础,是掌握Spring事务管理的基础。
二、事务的四个特性ACID
2.1 原子性
事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
2.2 一致性
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
2.3 隔离性
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
2.4 持久性
指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
三、Spring中的事务管理
Spring既支持编程式事务管理,也支持声明式的事务管理
编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式事务中,必须在每个业务操作中包含额外的事务管理代码
声明式事务管理:大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点,可以通过AOP方法模块化。Spring通过Spring
AOP框架支持声明式事务管理。
三、Spring声明式事务管理:基于注解方式使用步骤(举个小栗子)
3.1编写数据库
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for account
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`balance` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`username`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES ('whd', 70);
-- ----------------------------
-- Table structure for book
-- ----------------------------
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
`isbn` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`book_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`price` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`isbn`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of book
-- ----------------------------
INSERT INTO `book` VALUES ('1001', 'JAVA', 100);
INSERT INTO `book` VALUES ('1002', 'Oracle', 70);
-- ----------------------------
-- Table structure for book_stock
-- ----------------------------
DROP TABLE IF EXISTS `book_stock`;
CREATE TABLE `book_stock` (
`isbn` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '图书编号',
`stock` int(11) NULL DEFAULT NULL COMMENT '库存',
PRIMARY KEY (`isbn`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of book_stock
-- ----------------------------
INSERT INTO `book_stock` VALUES ('1001', 10);
INSERT INTO `book_stock` VALUES ('1002', 10);
SET FOREIGN_KEY_CHECKS = 1;
数据库名字:book_manager
表:account
表:book
表:book_stock
3.2pom.xml导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
</dependencies>
3.3spring配置
3.3.1 配置事务管理器DataSourceTransactionManager(事务第一步)
<!--配置事务管理器 .事务类、理解为切面类-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
3.3.2开启注解事务(事务第二步)
<!--2.开启事务注解-->
<tx:annotation-driven/>
3.3.3整体的spring配置applicationContext.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"
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 https://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.da.service"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/book_manager"/>
<property name="username" value="root"/>
<property name="password" value="1998"/>
</bean>
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.da.dao"/>
</bean>
<!--配置事务管理器 .事务类、理解为切面类-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2.开启事务注解-->
<tx:annotation-driven/>
</beans>
3.4实体类
3.4.1 Account实体类
public class Account {
private String username;
private Integer balance;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username == null ? null : username.trim();
}
public Integer getBalance() {
return balance;
}
public void setBalance(Integer balance) {
this.balance = balance;
}
}
3.4.2 Book实体类
public class Book {
private String isbn;
private String book_name;
private Integer price;
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn == null ? null : isbn.trim();
}
public String getBook_name() {
return book_name;
}
public void setBook_name(String book_name) {
this.book_name = book_name == null ? null : book_name.trim();
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
}
3.4.3 BookStock实体类
public class BookStock {
private String isbn;
private Integer stock;
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn == null ? null : isbn.trim();
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
}
3.5 dao层代码(BookShopMapper)
public interface BookShopMapper {
/**
* 根据图书编号查询图书价格
* @param isbn
* @return
*/
public int findBookPriceByIsbn(String isbn);
/**
* 根据图书编号修改图书的库存
* @param isbn
*/
public void updateBookStock(String isbn);
/**
* 根据用户名修改用户余额
*
* @param username
* @param money
*/
public void updateAccount(@Param("username") String username, @Param("money") int money);
/**
* 根据图书编号查询图书的库存
* @param isbn
* @return
*/
public int findStockByIsbn(String isbn);
/**
* 根据用户名查询用户余额
* @param username
* @return
*/
public int findBalanceByUsername(String username);
}
3.6 BookShopMapper映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.da.dao.BookShopMapper">
<!--根据图书编号修改图书库存-->
<update id="updateBookStock">
update book_stock set stock = stock-1 where isbn=#{isbn}
</update>
<!--根据用户名字修改余额-->
<update id="updateAccount">
update account set balance=balance-#{money} where username=#{username}
</update>
<!--根据图书编号查找图书价格-->
<select id="findBookPriceByIsbn" resultType="java.lang.Integer">
select price from book where isbn=#{isbn}
</select>
<!--根据图书编号查找图书库存-->
<select id="findStockByIsbn" resultType="java.lang.Integer">
select stock from book_stock where isbn=#{isbn}
</select>
<!--根据用户名字查询用户余额-->
<select id="findBalanceByUsername" resultType="java.lang.Integer">
select balance from account where username=#{username}
</select>
</mapper>
3.7 业务层代码
3.7.1 BookShopService
public interface BookShopService {
/**
* 根据用户名和 书籍编号下单
* @param username
* @param isbn
*/
public void purchase(String username,String isbn);
}
3.7.2 BookShopServiceImpl(事务第三步,对应的业务层添加@Transaction注解)
@Service
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopMapper bookShopMapper;
/**
* 该方法中的所有动作,要吗都成功、妖魔都失败
* 事务:ACID 原子性、一致性、隔离性、持久性
* @param username
* @param isbn
*/
@Override
//3.添加注解
@Transactional
public void purchase(String username, String isbn) {
//1.根据isbn查询图书的价格
int price = bookShopMapper.findBookPriceByIsbn(isbn);
//2.根据isbn查询图书的库存量
int stock = bookShopMapper.findStockByIsbn(isbn);
if (stock>0){
//3.根据isbn修改库存
bookShopMapper.updateBookStock(isbn);
}else {
throw new RuntimeException("库存不足");
}
//查询用户余额
int balance = bookShopMapper.findBalanceByUsername(username);
if (balance>=price){
//4.根据用户名修改余额
bookShopMapper.updateAccount(username,price);
}else {
throw new RuntimeException("余额不足");
}
}
}
3.8 测试代码
public static void main(String[] args) {
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
//BookShopService bookShopService= (BookShopService) app.getBean("bookShopServiceImpl");
//bookShopService.purchase("ykq","1002");
BookShopService bookShopService = (BookShopService) app.getBean("bookShopServiceImpl");
bookShopService.purchase("whd","1002");
}
3.9 分析
3.9.1 不开启事务的情况下,购买1本售价大于余额的图书(JAVA 100)
3.9.1.1购买前 库存10本,余额70.
3.9.1.2 开始购买
余额不足
3.9.1.3 购买后(库存减少1本,余额不变,出现问题)
3.9.2 开启事务的情况下,购买1本售价大于余额的图书(JAVA 100)
3.9.2.1购买前 库存9本,余额70.
3.9.2.2 开始购买
余额不足
3.9.2.3 购买后(库存不变,余额不变,事务正常)