【Spring4.0】Spring基于注解方式进行事务管理

一、什么是事务管理


  • 事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性.。
  • 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用。

事务的四个关键属性(ACID):

属性解释
原子性(atomicity)事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用
一致性(consistency)一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中
隔离性(isolation)可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏
持久性(durability)一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中
  • Spring 的核心事务管理抽象是PlatformTransactionManager它为事务管理封装了一组独立于技术的方法. 无论使用
    Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的。
  • DataSourceTransactionManager:在应用程序中只需要处理一个数据源, 而且通过 JDBC 存取。
  • JtaTransactionManager: 在 JavaEE 应用服务器上用 JTA(Java Transaction API) 进行事务管理
  • HibernateTransactionManager:用 Hibernate 框架存取数据库
  • 事务管理器以普通的 Bean 形式声明在 Spring IOC 容器中



二、为什么要使用事务管理


举个例子:
    在一个卖书的商城系统中,肯定会有一个数据库表记录书本价格书本的库存用户的余额。当一个用户想买一本价值100元的书,在他结账的时候用户余额会减去100元,而书的库存就会减去1本,这就是一个事务单元。当用户的余额只剩下90元,不足以购买100元的书,这个时候肯定会抛出一个余额不足的异常,虽然账户余额由于异常没有再减钱,但是减去书本库存并没有出现异常,所以依旧会减去1本书的库存,这样做的话就不符合常规,常规应该时在扣钱的时候库存才减去。为了避免这种情况,事务管理就诞生了,它要求一个事务单元要么全都完成,只要当中有一个流程失败,整个事务单元就会回滚到原来没改动前的状态。


三、使用注解方式进行事务管理流程


一般在xml配置文件中启用事务注解即可
这里写图片描述

四、实例的传播属性


事务传播属性可以在 @Transactional 注解的 propagation 属性中定义

参考资料:孟凡柱的专栏

传播属性解释
PROPAGATION_REQUIRED支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

参考资料:尚硅谷
这里写图片描述

REQUIREDREQUIRES_NEW区别

REQUIRED 传播行为:当 bookService 的 purchase() 方法被另一个事务方法 checkout() 调用时, 它默认会在现有的事务内运行. 这个默认的传播行为就是 REQUIRED. 因此在 checkout() 方法的开始和终止边界内只有一个事务. 这个事务只在 checkout() 方法结束的时候被提交, 结果用户一本书都买不了
REQUIRES_NEW传播行为:另一种常见的传播行为是 REQUIRES_NEW. 它表示该方法必须启动一个新事务, 并在自己的事务内运行. 如果有事务在运行, 就应该先挂起它.

五、示例代码


注意!!

在进行本实验之前请做好JdbcTemplate的配置,如果没有了解过JdbcTemplate,请翻阅我的【Spring4.0】系列日志,里面有提到JdbcTemplate相关内容。

(1)了解需求

需要创建的类:
这里写图片描述
数据库的表:
这里写图片描述
数据库名叫spring
各个表的详情:
这里写图片描述
(account表)
这里写图片描述
(book表)
这里写图片描述
(book_stock表)

(2)数据库创建相关的表

CREATE DATABASE  IF NOT EXISTS `spring` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `spring`;
-- MySQL dump 10.13  Distrib 5.7.9, for Win64 (x86_64)
--
-- Host: localhost    Database: spring
-- ------------------------------------------------------
-- Server version	5.7.10-log

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `account`
--

DROP TABLE IF EXISTS `account`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `account` (
  `username` varchar(50) NOT NULL,
  `balance` int(11) DEFAULT NULL,
  PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `account`
--

LOCK TABLES `account` WRITE;
/*!40000 ALTER TABLE `account` DISABLE KEYS */;
INSERT INTO `account` VALUES ('AA',200);
/*!40000 ALTER TABLE `account` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `book`
--

DROP TABLE IF EXISTS `book`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
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=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `book`
--

LOCK TABLES `book` WRITE;
/*!40000 ALTER TABLE `book` DISABLE KEYS */;
INSERT INTO `book` VALUES ('1001','Java',100),('1002','Oracle',70);
/*!40000 ALTER TABLE `book` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `book_stock`
--

DROP TABLE IF EXISTS `book_stock`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `book_stock` (
  `isbn` varchar(50) NOT NULL,
  `stock` int(11) DEFAULT NULL,
  PRIMARY KEY (`isbn`),
  CONSTRAINT `book_stock_isbn` FOREIGN KEY (`isbn`) REFERENCES `book` (`isbn`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `book_stock`
--

LOCK TABLES `book_stock` WRITE;
/*!40000 ALTER TABLE `book_stock` DISABLE KEYS */;
INSERT INTO `book_stock` VALUES ('1001',10),('1002',10);
/*!40000 ALTER TABLE `book_stock` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2018-08-06 21:12:05

(3)导入相关的包

这里写图片描述

(4)创建外部配置文件db.properties

在src根目录下创建db.properties外部配置文件,用于连接MySQL数据库

jdbc.user=root
jdbc.password=123456
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc\:mysql\://localhost\:3306/spring
jdbc.initPoolSize=5
jdbc.maxPoolSize=10

(5)创建applicationContext-tx-xml.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:p="http://www.springframework.org/schema/p"
	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-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.3.xsd">

	<!-- 自动扫描 -->
	<context:component-scan base-package="com.spring"></context:component-scan>

	<!-- 导入外部资源文件 -->
	<context:property-placeholder location="classpath:db.properties" />

	<!-- 配置C3P0数据源 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="user" value="${jdbc.user}"></property>
		<property name="password" value="${jdbc.password}"></property>
		<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
		<property name="driverClass" value="${jdbc.driverClass}"></property>
		<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
		<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
	</bean>

	<!-- 配置Spring 的JdbcTemplate -->
	<bean id="jdbcTemplate" 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>

(6)创建接口BookShopServiceDaoBookShopService

BookShopServiceDao

package com.spring.tx;

public interface BookShopDao {

	//根据书号获取书的单价
	public int findBookPriceByIsbn(String isbn);
	
	//更新书的库存,使书号对应的库存-1
	public void updateBookStock(String isbn);
	
	//更新用户账户余额:使username的balance - price
	public void updateUserAccount(String username,int price);
	
}

BookShopService

package com.spring.tx;

public interface BookShopDao {

	//根据书号获取书的单价
	public int findBookPriceByIsbn(String isbn);
	
	//更新书的库存,使书号对应的库存-1
	public void updateBookStock(String isbn);
	
	//更新用户账户余额:使username的balance - price
	public void updateUserAccount(String username,int price);
	
}

(7)创建异常类UserAccountExceptionBookStockException

两个类继承RuntimeException并且把相对应的构造器重写即可
UserAccountException

package com.spring.tx;

public class UserAccountException extends RuntimeException{

	/**
	 * 
	 */
	private static final long serialVersionUID = 3217299177229543901L;

	public UserAccountException() {
		super();
		// TODO Auto-generated constructor stub
	}

	public UserAccountException(String message, Throwable cause, boolean enableSuppression,
			boolean writableStackTrace) {
		super(message, cause, enableSuppression, writableStackTrace);
		// TODO Auto-generated constructor stub
	}

	public UserAccountException(String message, Throwable cause) {
		super(message, cause);
		// TODO Auto-generated constructor stub
	}

	public UserAccountException(String message) {
		super(message);
		// TODO Auto-generated constructor stub
	}

	public UserAccountException(Throwable cause) {
		super(cause);
		// TODO Auto-generated constructor stub
	}
	

}

BookStockException

package com.spring.tx;

public class BookStockException extends RuntimeException {

	/**
	 * 
	 */
	private static final long serialVersionUID = -8345575141732656052L;

	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
	}
	

}

(8)创建实现类BookShopServiceImplBookShopServiceImpl

BookShopServiceImpl

package com.spring.tx;

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) {
		String sql = "SELECT price FROM book WHERE isbn = ?";
		return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
	}

	@Override
	public void updateBookStock(String isbn) {
		// 检查书的库存是否足够,若不够则抛出异常
		String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
		int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
		if(stock == 0){
			throw new BookStockException("书本库存不足");
		}
		
		String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";
		jdbcTemplate.update(sql, isbn);
	}

	@Override
	public void updateUserAccount(String username, int price) {
		String sql2 = "SELECT balance FROM account WHERE username = ?";
		int balance = jdbcTemplate.queryForObject(sql2, Integer.class, username);
		if(balance < price){
			throw new UserAccountException("余额不足");
		}
		String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
		jdbcTemplate.update(sql, price, username);
	}

}

BookShopServiceImpl

package com.spring.tx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {

	@Autowired
	private BookShopDao bookShopDao;
	/*
	 * 添加事务注解(non-Javadoc)
	 *1.使用propagation 指定事务的传播行为,即当前的事务方法被另一个事务方法调用时
	 *如何使用事务,默认值为REQUIRED,即使用调用方法的事务
	 *REQUIRES_NEW:使用自己的事务,调用事务方法的事务挂起
	 *2.使用isolation指定事务隔离级别,最常用的值为READ_COMMITTED
	 *3.默认情况下Spring的声明式事务对所有异常进行回滚,但是也可以通过属性进行设置,正常情况下默认值即可
	 *noRollbackFor对某些类不进行回滚
	 *RollbackFor
	 *4.使用readOnly属性指定事务是否为只读,表示这个事务数据但不更新数据,这样可以帮数据库引擎优化事务
	 *5.使用timeOut指定强制回滚之前事务可以占用的时间,单位是秒
	 * @see com.spring.tx.BookShopService#purchase(java.lang.String, java.lang.String)
	 */
	
	@Transactional(propagation = Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED)
	@Override
	public void purchase(String username, String isbn) {
		//1.获取书的单价
		int price = bookShopDao.findBookPriceByIsbn(isbn);
		//2.更新数据库库存
		bookShopDao.updateBookStock(isbn);
		//3.更新用户价格
		bookShopDao.updateUserAccount(username, price);
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值