Spring框架学习(14):Spring的事务管理

来源:互联网 发布:公司安装丁丁软件 编辑:程序博客网 时间:2024/06/05 10:31

事务的意思是原子事务,即要么完整的完成这个事务,要么在报错时使系统的状态回到执行事务之前。这么做的意义是在进行事务处理时会涉及到持久层的访问和读写,举个简单的例子,一个简单的转账操作,A转100块给B,那么对应的数据库操作是A的账户余额减100,B的账户余额加100,这两个步骤构成了简单的转账的事务。这两个步骤对数据库的读写肯定会存在时间的先后,那么万一A的账户余额减少了100后在进行B的账户余额加100时,出现了错误怎么办呢,例如网络通信断开或者数据库访问错误等,这时候这个事务就出现了错误,我们应该对整个事务进行回退将系统恢复到事务执行之前,即让A和B的账户余额都回到转账之前的状态,然后再告知用户转账操作失败。在Spring中,它为我们提供了配置事务的方法,可以实现事务的原子性。

这里用的例子是从书店购书的例子,购书这个事务分两个步骤,一是书本库存减一,二是用户余额扣去书费。当书本库存不足时,需要对这个事务进行回退,当用户余额不足时也要进行回退。类的关系如下:


数据库的内容如下:


工程的目录如下:


一、使用注解的方式进行事务管理

类的代码如下:

package tx;public interface BookShopDao {//根据书号获取书的单价public int findBookPriceByIsbn(String isbn);//更新书的库存,使对应库存减一public void updateBookStock(String isbn);//更新用户的账户余额,使username 的balance减去pricepublic void updataUserAccount(String username, int price);}
package tx;import javax.security.auth.login.AccountException;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 {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic int findBookPriceByIsbn(String isbn) {// TODO Auto-generated method stubString sql = "SELECT price FROM book WHERE isbn = ?";return jdbcTemplate.queryForObject(sql, Integer.class, isbn);}@Overridepublic void updateBookStock(String isbn) {// TODO Auto-generated method stub//检测书的库存是否足够,若不够则抛出异常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 updataUserAccount(String username, int price) {// TODO Auto-generated method stub//检测余额是否足够,若不够则抛出异常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);}}

package tx;public interface BookShopService {public void purchase(String username, String isbn);}

package 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;import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;@Service("bookShopService")public class BookShopServiceImpl implements BookShopService {@Autowiredprivate BookShopDao bookShopDao;//添加事物注解//1.使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时//如何使用事务,默认取值为REQUIRED,即使用调用方法的事务//REQUIRED_NEW创建新的事务执行//2.使用isolation指定事务的隔离级别,最常用的取值为READ_COMMITTED//3.默认情况下Spring的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置//noRollBackFor不对什么异常回滚 :noRollbackFor={UserAccountException.class}//4.使用readOnly指定事务是否为只读,优化数据库引擎可以不加锁,加快速度//readOnly=true//5.timeout单位是秒,在强制回滚之前事务可以占用的时间@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED,timeout=3)@Overridepublic void purchase(String username, String isbn) {// TODO Auto-generated method stub/*模拟事务超时try {Thread.sleep(4000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}*///1.获取书的单价int price = bookShopDao.findBookPriceByIsbn(isbn);//2.更新书的库存bookShopDao.updateBookStock(isbn);//3.更新用户余额bookShopDao.updataUserAccount(username, price);}}
然后是两个异常类分别是库存不足和余额不足
package tx;public class BookStockException extends RuntimeException {private static final long serialVersionUID = 1L;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}}

package tx;public class UserAccountException extends RuntimeException {private static final long serialVersionUID = 1L;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}}
bean的配置文件如下:

<?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-3.2.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd"><context:component-scan base-package="tx"></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><!-- 配置 NameParameterJdbcTemplate,该对象可以使用具名参数,其没有无参数的构造器,所以必须为其构造器指定参数--><bean id="namedParameterJdbcTemplate"class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"><constructor-arg ref="dataSource"></constructor-arg></bean><!-- 配置事务管理器 --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean><!-- 启用事务注解 --><tx:annotation-driven transaction-manager="transactionManager"/></beans>

property文件:

jdbc.user=rootjdbc.password=1234jdbc.driverclass=com.mysql.cj.jdbc.Driverjdbc.jdbcurl=jdbc:mysql:///spring-5?useSSL=true&serverTimezone=UTCjdbc.initPoolSize=5jdbc.maxPoolSize=10

bean的配置和property属性文件和上一篇文章的差不多,这里不多做介绍

使用junit测试一下功能:

package tx;import static org.junit.Assert.*;import java.util.Arrays;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringTransactionTest {private ApplicationContext ctx = null;private BookShopDao bookShopDao = null;private BookShopService bookShopService  = null;private Cashier cashier = null;{ctx = new ClassPathXmlApplicationContext("applicationContext.xml");bookShopDao = ctx.getBean(BookShopDao.class);bookShopService = ctx.getBean(BookShopService.class);}@Testpublic void testBookShoService() {bookShopService.purchase("AA", "1001");}@Testpublic void testBookShopDaoUpdate() {bookShopDao.updataUserAccount("AA", 100);}@Testpublic void testBookShopDaoUpdateBookStock() {bookShopDao.updateBookStock("1001");}@Testpublic void testBookShopDapFindPriceByIsbn() {System.out.println(bookShopDao.findBookPriceByIsbn("1001"));}}
运行一下可以发现运行结果正常,再次查看数据库也会发现一切正常。读者可以自行修改数据库,产生购买失败的情况,可以看到失败的事件会被回滚。

现在我们来考虑一个新的问题,当我们在一个事务中执行了一连串的事件时,当其中的一个事件出错了,需不需要将前面执行成功的事务也回滚呢?

写一个cashier类来测试一下:

package tx;import java.util.List;public interface Cashier {public void checkout(String username, List<String> isbns);}

package 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 Cashier {@Autowiredprivate BookShopService bookShopService;//添加事务注解@Transactional@Overridepublic void checkout(String username, List<String> isbns) {// TODO Auto-generated method stubfor (String isbn : isbns) {bookShopService.purchase(username, isbn);}}}
我们运行这段代码的话可以看到结果是只回滚了发生错误的事务,前面正常执行的事务没有被回滚,这是因为我们在前面的代码中设置了事务的传播属性
propagation=Propagation.REQUIRES_NEW,

即在处理该事件时新开一个事件来处理,因此这个事件与调用它的事件不是处于同一个事件当中的,因此不会将调用其的事件也回滚。

除此之外Spring事务的事务传播行为还有6种:

REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。

NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。

MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。

SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。

NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。

NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。 


二、使用xml文件的方式进行事务管理

同样的,除了使用注解我们还可以通过xml文件配置的方式进行事务管理

还是使用上面的代码,但是需要将它们的注解全部去掉,然后在bean的配置文件中进行事务管理,配置文件如下:



<?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-3.2.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd"><context:component-scan base-package="tx"></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 --><bean id="bookShopDao" class="tx.xml.BookShopDaoImpl"><property name="jdbcTemplate" ref="jdbcTemplate"></property></bean><bean id="bookShopService" class="tx.xml.service.impl.BookShopServiceImpl"><property name="bookShopDao" ref="bookShopDao"></property></bean><bean id="cashier" class="tx.xml.service.impl.CashierImpl"><property name="bookShopService" ref="bookShopService"></property></bean><!-- 1.配置事务管理器 --><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="purchase" propagation="REQUIRES_NEW"/><tx:method name="get*" read-only="true"/><tx:method name="find*" read-only="true"/><tx:method name="*"/></tx:attributes></tx:advice><!-- 这一步一定要记得添加编织器的库AspectJWeaver.jar,不然会报错 --><!-- 配置事务切入点,以及把事务切入点和事务属性关联起来 --><aop:config><aop:pointcut expression="execution(* tx.xml.service.*.*(..))" id="txPointCut"/><aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/></aop:config></beans>

一般来说,只要依赖的jar包加对了,版本也正确,是不会出什么问题的。以上就是Spring的事务管理。




0 0
原创粉丝点击