事务简介:

事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性。

事务就是一系列的动作,它们被当做一个单独的工作单元。这些动作要么全部完成,要么全部不起作用。

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

  1. 原子性(atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  2. 一致性(consistency):一旦所有事务动作完成,事务就被提交。数据和资源就处于一种满足业务的一致性状态中。
  3. 隔离性(isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  4. 持久性(durablility):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响。通常情况下,事务的结果都被写到持久化存储中。

传统事务与Spring事务管理器:

传统事务:

package com.dx.jdbc;import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;import javax.sql.DataSource;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class JdbcAPITx {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");DataSource dataSource = (DataSource) ctx.getBean("dataSource");Connection connection = null;try {connection = dataSource.getConnection();connection.setAutoCommit(false);Statement statement = connection.createStatement();ResultSet resultSet = statement.executeQuery("select @@version;");while (resultSet.next()) {String version = resultSet.getString(1);System.out.println(version);}connection.commit();} catch (SQLException ex) {ex.printStackTrace();if (connection != null) {try {connection.rollback();} catch (SQLException e1) {e1.printStackTrace();}}throw new RuntimeException(ex);} finally {if (connection != null) {try {connection.close();} catch (SQLException e1) {e1.printStackTrace();}}}System.out.println("Complete...");}
}

View Code

传统事务不利于一般情况需要把一个完整业务写在一个Transaction控制范围内,不利于代码按照功能划分数据操作类。还有必须要为不同的方法重写类似的样板代码。这段代码是特定于JDBC的,一旦选择其他数据库存取技术,代码需要作出相应的调整。

Spring事务管理器:

  • 作为企业级引用程序框架,Spring在不同的事务管理API之上定义了一个抽象类,而应用层开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制。
  • Spring既支持编程式的事务管理,也支持声明式的事务管理:
  1. 编程式事务管理:将事务管理代码嵌入到业务方法中来控制事物的提交和回滚。在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码。
  2. 声明式事务管理:大多数情况下比编程式的事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理,事务管理作为一种横切关注点,可以通过AOP方法模块化,Spring通过Spring AOP框架支持声明式事务管理。
  • Spring从不同的事务管理API中抽象了一整套的事务机制。开发人员不必了解底层的事务API,就可以利用这些事务机制。有了这些事务机制,事务管理代码就能独立于特定的事务技术了。
  • Spring的核心事务管理抽象是org.springframework.transaction.PlatformTransactionManager.java接口类,它封装了一组独立于技术的方法,无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。org.springframework.transaction.PlatformTransactionManager.java接口类:
    1 package org.springframework.transaction;
    2
    3 public interface PlatformTransactionManager {
    4     TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    5     void commit(TransactionStatus status) throws TransactionException;
    6     void rollback(TransactionStatus status) throws TransactionException;
    7 }

需求简介:

已知有图书网络销售店,包含有:账户、图书、图书库存。

账户:

/*Table structure for table `account` */
DROP TABLE IF EXISTS `account`;CREATE TABLE `account` (`username` varchar(64) DEFAULT NULL,`balance` decimal(16,0) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*Data for the table `account` */
insert  into `account`(`username`,`balance`) values ('zhangsan','60');

图书:

/*Table structure for table `book` */
DROP TABLE IF EXISTS `book`;CREATE TABLE `book` (`isbn` varchar(32) DEFAULT NULL,`book_name` varchar(256) DEFAULT NULL,`price` decimal(16,0) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*Data for the table `book` */
insert  into `book`(`isbn`,`book_name`,`price`) values ('10001','Java','100'),('10002','MySql','120');

库存:

/*Table structure for table `book_stock` */
DROP TABLE IF EXISTS `book_stock`;CREATE TABLE `book_stock` (`isbn` varchar(32) DEFAULT NULL,`stock` int(8) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*Data for the table `book_stock` */
insert  into `book_stock`(`isbn`,`stock`) values ('10001',10),('10002',10);

需求实现:

图书销售功能

  • 第一步:获取书的单价;
  • 第二步:减少库存;
  • 第三步:更新用户余额

新建一个java project,在src下添加一个spring bean配置文件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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"><!-- 导入资源文件 --><context:property-placeholder location="classpath:db.properties" /><!-- 自动扫描的包 --><context:component-scan base-package="com.dx.spring.tx"></context:component-scan><!-- 配置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.dirverClass}"></property><property name="initialPoolSize" value="${jdbc.initialPoolSize}"></property><property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property><property name="minPoolSize" value="${jdbc.minPoolSize}"></property></bean><!-- 配置Spring 的 JdbcTemplate --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean></beans>

View Code

db.properties数据库配置文件

jdbc.user=root
jdbc.password=123456
jdbc.jdbcUrl=jdbc:mysql://localhost/spring
jdbc.dirverClass=com.mysql.jdbc.Driverjdbc.initialPoolSize=5
jdbc.maxPoolSize=50
jdbc.minPoolSize=3

View Code

数据层:

IBookShopDao.java

package com.dx.spring.tx;public interface IBookShopDao {double findBookPriceByIsbn(String isbn);void updateBookStock(String isbn);void updateUserAccount(String username, double price);
}

BookShopDaoImpl.java

package com.dx.spring.tx;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;import com.dx.spring.exceptions.AccountBalanceInsufficientException;
import com.dx.spring.exceptions.BookStockException;@Repository("bookShopDao")
public class BookShopDaoImpl implements IBookShopDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic double findBookPriceByIsbn(String isbn) {String sql = "select price from book where isbn=?";return jdbcTemplate.queryForObject(sql, Double.class, isbn);}@Overridepublic 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);}@Overridepublic void updateUserAccount(String username, double price) {String sql2 = "select balance from account where username=?";double balance = jdbcTemplate.queryForObject(sql2, Double.class, username);if (balance < price) {throw new AccountBalanceInsufficientException("余额不足!");}String sql = "update account set balance=balance-? where username=?";jdbcTemplate.update(sql, price, username);}}

View Code

服务层:

IBookShopService.java

package com.dx.spring.tx;public interface IBookShopService {public void purchase(String username, String isbn);
}

BookShopServiceImpl.java

package com.dx.spring.tx;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service("bookShopService")
public class BookShopServiceImpl implements IBookShopService {@Autowiredprivate IBookShopDao bookShopDao;@Overridepublic void purchase(String username, String isbn) {// 第一步:获取书的单价;double price = bookShopDao.findBookPriceByIsbn(isbn);// 第二步:减少库存;
        bookShopDao.updateBookStock(isbn);// 第三步:更新用户余额
        bookShopDao.updateUserAccount(username, price);}}

异常类:AccountBalanceInsufficientException.java

package com.dx.spring.exceptions;public class AccountBalanceInsufficientException extends RuntimeException {private static final long serialVersionUID = -162287400165698929L;public AccountBalanceInsufficientException() {super();}public AccountBalanceInsufficientException(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}public AccountBalanceInsufficientException(String message, Throwable cause) {super(message, cause);}public AccountBalanceInsufficientException(String message) {super(message);}public AccountBalanceInsufficientException(Throwable cause) {super(cause);}}

View Code

BookStockException.java

package com.dx.spring.exceptions;public class BookStockException extends RuntimeException {private static final long serialVersionUID = 6192610386513480985L;public BookStockException() {super();}public BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}public BookStockException(String message, Throwable cause) {super(message, cause);}public BookStockException(String message) {super(message);}public BookStockException(Throwable cause) {super(cause);}}

View Code

添加JUnit测试类BookShopTest.java测试功能

package com.dx.spring.tx;import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;import com.dx.spring.exceptions.AccountBalanceInsufficientException;
import com.dx.spring.exceptions.BookStockException;public class BookShopTest {private ApplicationContext ctx = null;private IBookShopDao bookShopDao = null;private IBookShopService bookShopService = null;{ctx = new ClassPathXmlApplicationContext("applicationContext.xml");bookShopDao = (IBookShopDao) ctx.getBean("bookShopDao");bookShopService = (IBookShopService) ctx.getBean("bookShopService");}@Testpublic void testPurchase() {bookShopService.purchase("zhangsan", "10001");}@Testpublic void testFindBookPriceByIsbn() {System.out.println(bookShopDao.findBookPriceByIsbn("10001"));}@Testpublic void testUpdateBookStock() throws BookStockException {bookShopDao.updateBookStock("10001");}@Testpublic void testUpdateUserAccount() throws AccountBalanceInsufficientException {bookShopDao.updateUserAccount("zhangsan", 100d);}}

View Code

执行测试方法:

    @Testpublic void testPurchase() {bookShopService.purchase("zhangsan", "10001");}

发现抛出了异常:

但是,查看数据库中记录发现库存已经被减1。这显然不满足事务的原子性。

那么如何使用Spring来管理图书网络销售事务?以下有两种方式来管理:基于Spring注解方式的事务管理、基于Spring XML配置文件的事务管理。

基于Spring注解方式的事务管理

第一步:修改spring bean配置文件,添加以下配置:

    <!-- 配置Spring事务管理器 --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean><!-- 启用事务注解 如果上边配置的‘事务管理器’已经是‘transactonManager’时,这里的transaction-manager属性可以省略 --><tx:annotation-driven transaction-manager="transactionManager" />

第二步:在服务类purchase方法上添加@Transactional注解。

package com.dx.spring.tx;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service("bookShopService")
public class BookShopServiceImpl implements IBookShopService {@Autowiredprivate IBookShopDao bookShopDao;@Transactional@Overridepublic void purchase(String username, String isbn) {// 第一步:获取书的单价;double price = bookShopDao.findBookPriceByIsbn(isbn);// 第二步:减少库存;
        bookShopDao.updateBookStock(isbn);// 第三步:更新用户余额
        bookShopDao.updateUserAccount(username, price);}
}

第三步:测试。

测试结果发现,当发现用户余额不足时,并没有使得库存减1。

Spring事务的传播行为

需求:

  • 新定义Cashier接口:表示客户的结账操作。
  • 修改数据表信息如下,目的是用户Tom在结账时,余额只能支付第一本数,不够支付第二本书。账户:,图书:,库存:
ICashier.java
package com.dx.spring.tx;import java.util.List;public interface ICashier {public void checkout(String username, List<String> isbns);
}

CashierImpl.java

package com.dx.spring.tx;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service("cashier")
public class CashierImpl implements ICashier {@Autowiredprivate IBookShopService bookShopService;@Transactional@Overridepublic void checkout(String username, List<String> isbns) {for (String isbn : isbns) {bookShopService.purchase(username, isbn);}}
}

此时,添加Junit测试。

package com.dx.spring.tx;import java.util.Arrays;import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class BookShopTest {private ApplicationContext ctx = null;private ICashier cashier = null;{ctx = new ClassPathXmlApplicationContext("applicationContext.xml");cashier = (ICashier) ctx.getBean("cashier");}@Testpublic void testCheckout() {cashier.checkout("Tom", Arrays.asList("0001", "0002"));}
}

测试抛出异常,但是此时数据库中的数据并未发生任何改变。

事务的传播行为 

PROPAGION_XXX :事务的传播行为

* 保证同一个事务中

PROPAGATION_REQUIRED 支持当前事务,如果不存在 就新建一个(默认)

PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务

PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常

* 保证没有在同一个事务中

PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务

PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务

PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常

PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行

REQUIRED传播行为:

  • 当bookService的purchase()方法被另外一个事务方法checkout()调用是,它默认会在现有的事务内运行,这个默认的传播行为就是REQUIRED,因此在checkout()方法的开始和终止边界内只有一个事务,这个事务只能自checkout()方法结束的时候被提交,结果用户一本书也买不了。
  • 事务传播属性可以在@Transactional注解的propagation属性中定义。

REQUIRES_NEW传播行为:

  • 另外一种常见的传播行为是REQUIRES_NEW,它表示该方法必须启动一个新事务,并能在自己的事务内运行。如果有事务在运行,就应该先挂起它。

修改BookShopServiceImpl.java中的@Transactional(propagation=REQUIRES_NEW)

package com.dx.spring.tx;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service("bookShopService")
public class BookShopServiceImpl implements IBookShopService {@Autowiredprivate IBookShopDao bookShopDao;@Transactional(propagation=Propagation.REQUIRES_NEW)@Overridepublic void purchase(String username, String isbn) {// 第一步:获取书的单价;double price = bookShopDao.findBookPriceByIsbn(isbn);// 第二步:减少库存;
        bookShopDao.updateBookStock(isbn);// 第三步:更新用户余额
        bookShopDao.updateUserAccount(username, price);}}

此时,重新执行JUnit测试,发现数据发生变化:

账户余额发生变化:

库存减少:

Spring事务的隔离级别:

  • DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别. (Mysql 默认:可重复读,SqlServer,Oracle 默认:读已提交)
  • 未提交读(read uncommited) :脏读,不可重复读,虚读都有可能发生
  • 已提交读 (read commited):避免脏读。但是不可重复读和虚读有可能发生
  • 可重复读 (repeatable read) :避免脏读和不可重复读.但是虚读有可能发生.
  • 串行化的 (serializable) :避免以上所有读问题.

  • read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。
  • read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。
  • repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。
  • serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读(避免三种)。

在Spring事务中用法是在@Transactional的属性中设置:

package com.dx.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 IBookShopService {@Autowiredprivate IBookShopDao bookShopDao;@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED)@Overridepublic void purchase(String username, String isbn) {// 第一步:获取书的单价;double price = bookShopDao.findBookPriceByIsbn(isbn);// 第二步:减少库存;
        bookShopDao.updateBookStock(isbn);// 第三步:更新用户余额
        bookShopDao.updateUserAccount(username, price);}
}

Spring事务的回滚:

正常情况下,按照正确的编码是不会出现事务回滚失败的。下面说几点保证事务能回滚的方法

  • (1)如果采用编程式事务,一定要确保切入点表达式书写正确
  • (2)如果Service层会抛出不属于运行时异常也要能回滚,那么可以将Spring默认的回滚时的异常修改为Exception,这样就可以保证碰到什么异常都可以回滚。具体的设置方式也说下:
    • ① 声明式事务,在配置里面添加一个rollback-for,代码如下

      •  <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>

    • ② 注解事务,直接在注解上面指定,代码如下
      @Transactional(rollbackFor=Exception.class)

  • (3)只有非只读事务才能回滚的,只读事务是不会回滚的
  • (4)如果在Service层用了try catch,在catch里面再抛出一个 RuntimeException异常,这样出了异常才会回滚
  • (5)如果你不喜欢(4)的方式,你还可以直接在catch后面写一句回滚代码(TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();)来实现回滚,这样的话,就可以在抛异常后也能return 返回值;比较适合需要拿到Service层的返回值的场景。具体的用法可以参见考下面的伪代码
           /** TransactionAspectSupport手动回滚事务:*/@Transactional(rollbackFor = { Exception.class })  public boolean test() {  try {  doDbSomeThing();    } catch (Exception e) {  e.printStackTrace();     //就是这一句了, 加上之后抛了异常就能回滚(有这句代码就不需要再手动抛出运行时异常了)
                     TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();  return false;}  return true;}  

Spring事务的只读:

Spring的事务经常会有这样的配置:

<tx:method name="search*" read-only="true" /> 

或者这样的注记:

@Transactional(readOnly = true)

  “只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。
  但是你非要在“只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像“读写事务”那样保险而已。
  因此,“只读事务”仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可。

Spring事务的超时:

@Transactional(timeout = 60)

如果用这个注解描述一个方法的话,线程已经跑到方法里面,如果已经过去60秒了还没跑完这个方法并且线程在这个方法中的后面还有涉及到对数据库的增删改查操作时会报事务超时错误(会回滚)。如果已经过去60秒了还没跑完但是后面已经没有涉及到对数据库的增删改查操作,那么这时不会报事务超时错误(不会回滚)。

基于Spring XML配置文件的事务管理

将项目转化为Spring配置(不包含事务):

IBookShopDao.java

package com.dx.spring.xml.tx;public interface IBookShopDao {double findBookPriceByIsbn(String isbn);void updateBookStock(String isbn);void updateUserAccount(String username, double price);
}

View Code

IBookShopService.java

package com.dx.spring.xml.tx;public interface IBookShopService {public void purchase(String username, String isbn);
}

View Code

ICashier.java

package com.dx.spring.xml.tx;import java.util.List;public interface ICashier {public void checkout(String username, List<String> isbns);
}

View Code

BookShopDaoImpl.java

package com.dx.spring.xml.tx;import org.springframework.jdbc.core.JdbcTemplate;import com.dx.spring.exceptions.AccountBalanceInsufficientException;
import com.dx.spring.exceptions.BookStockException;public class BookShopDaoImpl implements IBookShopDao {private JdbcTemplate jdbcTemplate;public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}@Overridepublic double findBookPriceByIsbn(String isbn) {String sql = "select price from book where isbn=?";return jdbcTemplate.queryForObject(sql, Double.class, isbn);}@Overridepublic 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);}@Overridepublic void updateUserAccount(String username, double price) {String sql2 = "select balance from account where username=?";double balance = jdbcTemplate.queryForObject(sql2, Double.class, username);if (balance < price) {throw new AccountBalanceInsufficientException("余额不足!");}String sql = "update account set balance=balance-? where username=?";jdbcTemplate.update(sql, price, username);}}

View Code

BookShopServiceImpl.java

package com.dx.spring.xml.tx;public class BookShopServiceImpl implements IBookShopService {private IBookShopDao bookShopDao;public void setBookShopDao(IBookShopDao bookShopDao) {this.bookShopDao = bookShopDao;}@Overridepublic void purchase(String username, String isbn) {// 第一步:获取书的单价;double price = bookShopDao.findBookPriceByIsbn(isbn);// 第二步:减少库存;
        bookShopDao.updateBookStock(isbn);// 第三步:更新用户余额
        bookShopDao.updateUserAccount(username, price);}
}

View Code

CashierImpl.java

package com.dx.spring.xml.tx;import java.util.List;public class CashierImpl implements ICashier {private IBookShopService bookShopService;public void setBookShopService(IBookShopService bookShopService) {this.bookShopService = bookShopService;}@Overridepublic void checkout(String username, List<String> isbns) {for (String isbn : isbns) {bookShopService.purchase(username, isbn);}}}

View Code

新建Spring Bean Xml配置文件applicationContext-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: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.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"><!-- 导入资源文件 --><context:property-placeholder location="classpath:db.properties" /><!-- 自动扫描的包 --><context:component-scan base-package="com.dx.spring.xml.tx"></context:component-scan><!-- 配置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.dirverClass}"></property><property name="initialPoolSize" value="${jdbc.initialPoolSize}"></property><property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property><property name="minPoolSize" value="${jdbc.minPoolSize}"></property></bean><!-- 配置Spring 的 JdbcTemplate --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean><!-- 配置 Bean --><bean id="bookShopDao" class="com.dx.spring.xml.tx.BookShopDaoImpl"><property name="jdbcTemplate" ref="jdbcTemplate"></property></bean><bean id="bookShopService" class="com.dx.spring.xml.tx.BookShopServiceImpl"><property name="bookShopDao" ref="bookShopDao"></property></bean><bean id="cashier" class="com.dx.spring.xml.tx.CashierImpl"><property name="bookShopService" ref="bookShopService"></property></bean>
</beans>

添加Junit测试类BookShopTest.java

package com.dx.spring.xml.tx;import java.util.Arrays;import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class BookShopTest {private ApplicationContext ctx = null;private IBookShopService bookShopService = null;private ICashier cashier = null;{ctx = new ClassPathXmlApplicationContext("applicationContext-xml.xml");bookShopService = (IBookShopService) ctx.getBean("bookShopService");cashier = (ICashier) ctx.getBean("cashier");}@Testpublic void testCheckout() {cashier.checkout("Tom", Arrays.asList("0001", "0002"));}@Testpublic void testPurchase() {bookShopService.purchase("Tom", "0001");}
}

初始化数据库中:

库存:,书:,账户:

此时执行testPurchase()Junit测试方法:

执行抛出异常“余额不足”,但是库存减一 

库存减一:

将项目转化为Spring配置(包含事务):

1)初始化数据库中:

库存:,书:,账户:

2)导入aop依赖扩展包

3)修改Spring Bean XML配置文件applicationContext-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:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"><!-- 导入资源文件 --><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.dirverClass}"></property><property name="initialPoolSize" value="${jdbc.initialPoolSize}"></property><property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property><property name="minPoolSize" value="${jdbc.minPoolSize}"></property></bean><!-- 配置Spring 的 JdbcTemplate --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean><!-- 配置 Bean --><bean id="bookShopDao" class="com.dx.spring.xml.tx.BookShopDaoImpl"><property name="jdbcTemplate" ref="jdbcTemplate"></property></bean><bean id="bookShopService" class="com.dx.spring.xml.tx.BookShopServiceImpl"><property name="bookShopDao" ref="bookShopDao"></property></bean><bean id="cashier" class="com.dx.spring.xml.tx.CashierImpl"><property name="bookShopService" ref="bookShopService"></property></bean><!-- 1.配置Spring事务管理器 --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean><!-- 2.配置事务属性 --><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="*" /></tx:attributes></tx:advice><!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来 --><aop:config><aop:pointcutexpression="execution(* com.dx.spring.xml.tx.IBookShopService.*(..))"id="txPointCut" /><aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" /></aop:config>
</beans>

4)此时执行Junit测试方法:

    @Testpublic void testPurchase() {bookShopService.purchase("Tom", "0001");}

执行抛出异常,但是库存没有发生改变,说明事务已经开启。

调整项目结构使其设置事务的传播性:

1)修改账户余额:

库存不变:

图书价格也不变:

2)调整项目结构:

3)修改applicationContext-xml.xml配置文件

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 4     xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
 5     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
 6         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
 7         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
 8         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
 9     <!-- 导入资源文件 -->
10     <context:property-placeholder location="classpath:db.properties" />
11
12     <!-- 配置C3P0数据源 -->
13     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
14         <property name="user" value="${jdbc.user}"></property>
15         <property name="password" value="${jdbc.password}"></property>
16         <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
17         <property name="driverClass" value="${jdbc.dirverClass}"></property>
18
19         <property name="initialPoolSize" value="${jdbc.initialPoolSize}"></property>
20         <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
21         <property name="minPoolSize" value="${jdbc.minPoolSize}"></property>
22     </bean>
23
24     <!-- 配置Spring 的 JdbcTemplate -->
25     <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
26         <property name="dataSource" ref="dataSource"></property>
27     </bean>
28
29     <!-- 配置 Bean -->
30     <bean id="bookShopDao" class="com.dx.spring.xml.tx.BookShopDaoImpl">
31         <property name="jdbcTemplate" ref="jdbcTemplate"></property>
32     </bean>
33     <bean id="bookShopService" class="com.dx.spring.xml.tx.service.impl.BookShopServiceImpl">
34         <property name="bookShopDao" ref="bookShopDao"></property>
35     </bean>
36     <bean id="cashier" class="com.dx.spring.xml.tx.service.impl.CashierImpl">
37         <property name="bookShopService" ref="bookShopService"></property>
38     </bean>
39
40     <!-- 1.配置Spring事务管理器 -->
41     <bean id="transactionManager"
42         class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
43         <property name="dataSource" ref="dataSource"></property>
44     </bean>
45
46     <!-- 2.配置事务属性 -->
47     <tx:advice id="txAdvice" transaction-manager="transactionManager">
48         <tx:attributes>
49             <tx:method name="purchase" propagation="REQUIRES_NEW" />
50             <tx:method name="get*" read-only="true" />
51             <tx:method name="find*" read-only="true" />
52             <tx:method name="*" />
53         </tx:attributes>
54     </tx:advice>
55
56     <!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来 -->
57     <aop:config>
58         <aop:pointcut expression="execution(* com.dx.spring.xml.tx.service.impl.*.*(..))"
59             id="txPointCut" />
60         <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
61     </aop:config>
62 </beans>

4)此时

    @Testpublic void testCheckout() {cashier.checkout("Tom", Arrays.asList("0001", "0002"));}

,执行测试发现库存少了一本。

参考:

①《Spring事务超时、回滚的相关说明》

②《Spring 事务 readOnly 到底是怎么回事?》

③《spring 中事务的Readonly的解释》

转载于:https://www.cnblogs.com/yy3b2007com/p/9156923.html

Spring(二十二):Spring 事务相关推荐

  1. Spring Boot教程(二十):Spring Boot使用String Task定时任务

    一.JAVA常见的几种定时任务比较 Timer:jdk自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务.使用这种方式可以让程序按照某一个频度执行, ...

  2. FreeSql (二十八)事务

    FreeSql实现了四种数据库事务的使用方法,脏读等事务相关方法暂时未提供.主要原因系这些方法各大数据库.甚至引擎的事务级别五花八门较难统一. 事务用于处理数据的一致性,处于同一个事务中的操作是一个U ...

  3. 【白话设计模式二十二】解释器模式(Interpreter)

    为什么80%的码农都做不了架构师?>>>    #0 系列目录# 白话设计模式 工厂模式 单例模式 [白话设计模式一]简单工厂模式(Simple Factory) [白话设计模式二] ...

  4. (二十二)admin-boot项目之集成just-auth实现第三方授权登录

    (二十二)集成just-auth实现第三方授权登录 项目地址:https://gitee.com/springzb/admin-boot 如果觉得不错,给个 star 简介: 这是一个基础的企业级基础 ...

  5. FreeSql (二十二)Dto 映射查询

    适合喜欢使用 dto 的朋友,很多时候 entity 与 dto 属性名相同,属性数据又不完全一致. 有的人先查回所有字段数据,再使用 AutoMapper 映射. 我们的功能是先映射,再只查询映射好 ...

  6. 2021年大数据Hadoop(二十二):MapReduce的自定义分组

    全网最详细的Hadoop文章系列,强烈建议收藏加关注! 后面更新文章都会列出历史文章目录,帮助大家回顾知识重点. 目录 本系列历史文章 前言 MapReduce的自定义分组 需求 分析 实现 第一步: ...

  7. 一位中科院自动化所博士毕业论文的致谢:二十二载风雨求学路,他把自己活成了光.........

    4月18日,中国科学院官方微博发布消息,披露了这篇论文为<人机交互式机器翻译方法研究与实现>,作者是2017年毕业于中国科学院大学的工学博士黄国平. 这篇论文中情感真挚的<致谢> ...

  8. iOS 11开发教程(二十二)iOS11应用视图实现按钮的响应(2)

    iOS 11开发教程(二十二)iOS11应用视图实现按钮的响应(2) 此时,当用户轻拍按钮后,一个叫tapButton()的方法就会被触发. 注意:以上这一种方式是动作声明和关联一起进行的,还有一种先 ...

  9. 实验二十二 SCVMM中的SQL Server配置文件

    实验二十二 SCVMM中的SQL Server配置文件 在VMM 2012中管理员可以使用 SQL Server 配置文件,在部署完成虚拟机之后,实现 SQL Server 数据库服务自动化部署并交付 ...

  10. 插入DLL和挂接API——Windows核心编程学习手札之二十二

    插入DLL和挂接API --Windows核心编程学习手札之二十二 如下情况,可能要打破进程的界限,访问另一个进程的地址空间: 1)为另一个进程创建的窗口建立子类时: 2)需要调试帮助时,如需要确定另 ...

最新文章

  1. usaco Camelot
  2. 0525 项目回顾7.0
  3. form表单嵌套,用标签的form属性来解决表单嵌套的问题
  4. CnPack 使用的组件命名约定
  5. [转]远程唤醒技术在运维中的应用
  6. 开源播放器 ijkplayer (一) :使用Ijkplayer播放直播视频
  7. linux ubuntu安装教程6,64位Ubuntu下安装IE6步骤
  8. stl源码剖析_《STL源码剖析》学习笔记——空间配置器
  9. 向量外积_解析几何 -向量
  10. 数据分析和大数据哪个更吃香_处理数据,大数据甚至更大数据的17种策略
  11. python归并排序算法实现_python算法实现系列-归并排序
  12. 面向非易失内存的MPI-IO接口优化
  13. 故障解决-CPU超频问题解决
  14. obs源码分析【二】:录制功能剖析
  15. web前端学习135-144(盒子模型---网页布局,盒子模型组成,边框,表格细线边框,盒子实际大小,内边距)
  16. 2021好用的CI/CD工具推荐清单
  17. Android debug.keystore的密码
  18. libpng warning: iCCP: cHRM chunk does not match sRGB
  19. redis 查看的版本
  20. 高中数学40分怎么办_高一数学40分有救吗?

热门文章

  1. Windows7 x64在Wampserver上安装memcache
  2. 蓝牙天线的一点小资料
  3. java实现井字棋 人工智能,Storm之——实现井字棋游戏(人工智能)
  4. 用html css设计网站,HTMLCSS构建和设计网站
  5. Registry Size 提示注册表容量不够!
  6. java异常机制throwable
  7. 用C# Regex类实现的一些常规输入判断
  8. rust货轮什么时候出现_婴儿什么时候用枕头合适?并非三个月,出现以下征兆再用不迟...
  9. avl树 php,PHP实现平衡二叉树(AVL树)
  10. JavaWeb——springMVC入门程序