目录
(3)配置Spring的配置文件 tx-annotation.xml
(10) 找到实现类BookServiceImpl对买书的方法进行重写
(11)创建getPriceByBookId updateStock updateBalance方法
编程式事务
- Connection conn = ...;
- try {
- // 开启事务:关闭事务的自动提交
- conn.setAutoCommit(false);
- // 核心操作
- // 提交事务
- conn.commit();
- }catch(Exception e){
- // 回滚事务
- conn.rollBack();
- }finally{
- // 释放数据库连接
- conn.close();
- }
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC jdbc.username=root jdbc.password=123456
- "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.atguigu.spring">context:component-scan>
-
-
- <context:property-placeholder location="classpath:jdbc.properties">context:property-placeholder>
-
-
-
- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
- <property name="driverClassName" value="${jdbc.driver}">property>
- <property name="url" value="${jdbc.url}">property>
- <property name="username" value="${jdbc.username}">property>
- <property name="password" value="${jdbc.password}">property>
- bean>
-
- <bean class="org.springframework.jdbc.core.JdbcTemplate">
-
- <property name="dataSource" ref="dataSource">property>
- bean>
-
- beans>
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 ' 价格 ' ,`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 );
控制层:controller.BookController 持久层:dao.BookDao dao.impl.BookDaoImpl 业务层:service.BookService service.impl.BookServiceImpl
控制层:controller.BookController
- package com.atguigu.spring.controller;
-
- import org.springframework.stereotype.Controller;
-
- @Controller
- public class BookController {
-
- }
持久层:dao.BookDao dao .impl.BookDaoImpl
dao.BookDao
- package com.atguigu.spring.dao;
-
- public interface BookDao {
-
- }
dao .impl.BookDaoImpl
- package com.atguigu.spring.dao.impl;
- import org.springframework.stereotype.Repository;
- @Repository
- public class BookDaoImpl {
-
- }
业务层:service.BookService service.impl.BookServiceImpl
service.BookService
- package com.atguigu.spring.service;
-
- public interface BookService {
-
-
- }
service.impl.BookServiceImpl
- package com.atguigu.spring.service.impl;
-
- import org.springframework.stereotype.Service;
-
- @Service
- public class BookServiceImpl {
-
-
- }
-
- <context:component-scan base-package="com.atguigu.spring">context:component-scan>
- package com.atguigu.spring.controller;
-
- import com.atguigu.spring.service.BookService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
-
- @Controller
- public class BookController {
-
- @Autowired
- private BookService bookService;
-
- }
- package com.atguigu.spring.service.impl;
-
- import com.atguigu.spring.dao.BookDao;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
-
- @Service
- public class BookServiceImpl {
-
- @Autowired
- private BookDao bookDao;
-
-
- }
- package com.atguigu.spring.controller;
-
- import com.atguigu.spring.service.BookService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
-
- @Controller
- public class BookController {
-
- @Autowired
- private BookService bookService;
-
-
- public void buyBook(Integer userId, Integer bookId){
- bookService.buyBook(userId, bookId);
- }
-
- }
- package com.atguigu.spring.service;
-
- public interface BookService {
-
- /**
- * 买书
- * @param userId
- * @param bookId
- */
- void buyBook(Integer userId, Integer bookId);
- }
-
- @Service
- public class BookServiceImpl implements BookService {
-
- @Autowired
- private BookDao bookDao;
-
-
- @Override
- public void buyBook(Integer userId, Integer bookId) {
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- }
- }
- package com.atguigu.spring.dao;
-
- public interface BookDao {
-
- /**
- * 根据图书的id查询图书的价格
- * @param bookId
- * @return
- */
- Integer getPriceByBookId(Integer bookId);
-
- /**
- * 更新图书的库存
- * @param bookId
- */
- void updateStock(Integer bookId);
-
- /**
- * 更新用户的余额
- * @param userId
- * @param price
- */
- void updateBalance(Integer userId, Integer price);
- }
在配置文件中配置了JdbcTemplate 测试事务功能的时候就是 使用JdbcTemplate执行sql语句 的过程中来测试的
- package com.atguigu.spring.dao.impl;
-
- @Repository
- public class BookDaoImpl implements BookDao {
-
- @Autowired
- private JdbcTemplate jdbcTemplate;
-
- @Override
- public Integer getPriceByBookId(Integer bookId) {
- return null;
- }
-
- @Override
- public void updateStock(Integer bookId) {
-
- }
-
- @Override
- public void updateBalance(Integer userId, Integer price) {
-
- }
- }
JdbcTemplate在iOC容器里面有 因为 当前配置文件已经配置了一个bean 所以我们这里可以自动装配@Autowired
- package com.atguigu.spring.dao.impl;
-
-
- import com.atguigu.spring.dao.BookDao;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.stereotype.Repository;
-
- @Repository
- public class BookDaoImpl implements BookDao {
- @Autowired
- private JdbcTemplate jdbcTemplate;
-
- //根据图书的id查询图书的价格
- @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);
- }
- }
让用户id为1的用户 去购买图书id为1的图书
可以看到库存够 余额不够 书的价格是80 余额只有50
创建测试类TxByAnnotationTest
- package com.atguigu.spring.test;
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:tx-annotation.xml")
- public class TxByAnnotationTest {
-
- @Autowired
- private BookController bookController;
-
- @Test
- public void testBuyBook(){
-
- bookController.buyBook(1, 1);
-
- }
-
- }
报错
Caused by: com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: BIGINT UNSIGNED value is out of range in '(`ssm`.`t_user`.`balance` - 80)'
UNSIGNED:无符号
结果超出范围
我们当前是没有事物的情况
在mysql默认的情况下 我们当前的一个sql语句 独占一个事物 并且自动提交
所以在当前没有设置事物有情况下 我们的三个sql 都是独占一个事物 并且自动提交
所以我们在实现事物的过程中 一定要把事物的自动提交给关闭掉 因为如果不关闭 每一个sql语句是独占一个事物的
总结:在mysql 默认的情况下 一个sql语句 独占一个事物 并且自动提交
观察数据库 发现更新图书的库存可以成功 更新用户的余额不成功
原来
现在
在Spring的配置文件中添加配置:
- "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.atguigu.spring">context:component-scan>
-
-
- <context:property-placeholder location="classpath:jdbc.properties">context:property-placeholder>
-
- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
- <property name="driverClassName" value="${jdbc.driver}">property>
- <property name="url" value="${jdbc.url}">property>
- <property name="username" value="${jdbc.username}">property>
- <property name="password" value="${jdbc.password}">property>
- bean>
-
- <bean class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dataSource">property>
- bean>
-
-
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource">property>
- bean>
-
-
-
-
- <tx:annotation-driven transaction-manager="transactionManager" />
-
- beans>
拓展:注意:导入的名称空间需要 tx 结尾的那个。
- package com.atguigu.spring.service.impl;
-
- import com.atguigu.spring.dao.BookDao;
- import com.atguigu.spring.service.BookService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
-
- @Service
- public class BookServiceImpl implements BookService {
-
- @Autowired
- private BookDao bookDao;
-
-
- @Override
- @Transactional //使用事务进行管理
- public void buyBook(Integer userId, Integer bookId) {
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- }
- }
现在我们在t_book表里面的库存由98改成100
原来
改成100
由于使用了Spring的声明式事务,更新库存和更新余额都没有执行
从新执行
- package com.atguigu.spring.test;
- /**
- * 声明式事务的配置步骤:
- * 1、在Spring的配置文件中配置事务管理器
- * 2、开启事务的注解驱动
- * 在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理
- * @Transactional注解标识的位置:
- * 1、标识在方法上
- * 2、标识在类上,则类中所有的方法都会被事务管理
- */
-
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:tx-annotation.xml")
- public class TxByAnnotationTest {
-
- @Autowired
- private BookController bookController;
-
- @Test
- public void testBuyBook(){
-
- bookController.buyBook(1, 1);
-
- }
-
- }
总结:
/** * 声明式事务的配置步骤: * 1、在Spring的配置文件中配置事务管理器 * 2、开启事务的注解驱动 * 在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理 * @Transactional注解标识的位置: * 1、标识在方法上 * 2、标识在类上,则类中所有的方法都会被事务管理 */
- @Service
- public class BookServiceImpl implements BookService {
-
- @Autowired
- private BookDao bookDao;
-
-
- @Override
- @Transactional(readOnly = true) //使用事务进行管理
- public void buyBook(Integer userId, Integer bookId) {
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- }
- }
测试
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:tx-annotation.xml")
- public class TxByAnnotationTest {
-
- @Autowired
- private BookController bookController;
-
- @Test
- public void testBuyBook(){
-
- bookController.buyBook(1, 1);
-
- }
-
- }
报错:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
我们当前的连接是只读的 我们的数据修改是不被允许的
只有当前的事物全部是查询操作的时候才可以用只读
- @Service
- public class BookServiceImpl implements BookService {
-
- @Autowired
- private BookDao bookDao;
-
-
- @Override
- @Transactional(
- //readOnly = true
- timeout = 3
- ) //使用事务进行管理
- public void buyBook(Integer userId, Integer bookId) {
-
- try {
- TimeUnit.SECONDS.sleep(5);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- }
- }
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Tue Nov 08 22:19:55 CST 2022事物真是异常
-
- @Service
- public class BookServiceImpl implements BookService {
-
- @Autowired
- private BookDao bookDao;
-
-
- @Override
- @Transactional(
- //readOnly = true
- //timeout = 3
- ) //使用事务进行管理
- public void buyBook(Integer userId, Integer bookId) {
-
- /* try {
- TimeUnit.SECONDS.sleep(5);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }*/
-
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- System.out.println(1 / 0); //数学运算异常 运行时异常
-
-
- }
- }
原来
现在
结果:
-
-
- @Service
- public class BookServiceImpl implements BookService {
-
- @Autowired
- private BookDao bookDao;
-
-
- @Override
- @Transactional(
- //readOnly = true
- //timeout = 3
- noRollbackFor = ArithmeticException.class //不造成回滚
- ) //使用事务进行管理
- public void buyBook(Integer userId, Integer bookId) {
-
- /* try {
- TimeUnit.SECONDS.sleep(5);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }*/
-
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- System.out.println(1/0); //数学运算异常 运行时异常
-
-
- }
- }
各个隔离级别解决并发问题的能力见下表:
各种数据库产品对事务隔离级别的支持程度:
②使用方式
-
-
- @Service
- public class BookServiceImpl implements BookService {
-
- @Autowired
- private BookDao bookDao;
-
-
- @Override
- @Transactional(
- //readOnly = true
- //timeout = 3
- // noRollbackFor = ArithmeticException.class 不造成回滚
- // noRollbackForClassName = "java.lang.ArithmeticException" //不造成回滚
- isolation = Isolation.DEFAULT
- ) //使用事务进行管理
- public void buyBook(Integer userId, Integer bookId) {
-
- /* try {
- TimeUnit.SECONDS.sleep(5);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }*/
-
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- System.out.println(1/0); //数学运算异常 运行时异常
-
-
- }
- }
- @Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
- @Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
- @Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
- @Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
- @Transactional(isolation = Isolation.SERIALIZABLE)//串行化
- public interface CheckoutService {
-
- /**
- * 结账
- * @param userId
- * @param bookIds
- */
- void checkout(Integer userId, Integer[] bookIds);
- }
-
- @Service
- public class CheckoutServiceImpl implements CheckoutService {
-
- @Autowired
- private BookService bookService;
-
- @Override
- //@Transactional
- public void checkout(Integer userId, Integer[] bookIds) {
- for (Integer bookId : bookIds) {
- bookService.buyBook(userId, bookId);
- }
- }
- }
- @Controller
- public class BookController {
-
- @Autowired
- private BookService bookService;
- @Autowired
- private CheckoutService checkoutService;
-
- public void buyBook(Integer userId, Integer bookId){
- bookService.buyBook(userId, bookId);
- }
-
- public void checkout(Integer userId, Integer[] bookIds){
- checkoutService.checkout(userId, bookIds);
- }
-
- }
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:tx-annotation.xml")
- public class TxByAnnotationTest {
-
- @Autowired
- private BookController bookController;
-
- @Test
- public void testBuyBook(){
-
- // bookController.buyBook(1, 1);
- bookController.checkout(1, new Integer[]{1,2});
-
- }
-
- }