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.什么是事务?

  1. 一个工作单元由多个动作组成,只有动作全部正确的时候才能执行成功,如果有一个动作错了,其他的动作都是无效的(回滚)(事务就是一系列的动作,它们被当做一 个单独的工作单元.这些动作要么全部完成,要么全部不起作用)

  2. 事务的四个关键属性(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");
   }
}