【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`
**各个表的详情:** ![这里写图片描述](https://img-blog.csdn.net/20180806210353909?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTk2OTc4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
**(account表)**
![这里写图片描述](https://img-blog.csdn.net/20180806210529698?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTk2OTc4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
**(book表)** ![这里写图片描述](https://img-blog.csdn.net/20180806211047903?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTk2OTc4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
**(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;

扫描二维码关注公众号,回复: 3633445 查看本文章


– 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)导入相关的包
![这里写图片描述](https://img-blog.csdn.net/20180806211916500?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTk2OTc4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

###(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"?>

<!-- 自动扫描 -->
<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"/>
```

###(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);
	}

}

猜你喜欢

转载自blog.csdn.net/qq_33596978/article/details/81451212
今日推荐