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

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

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

Spring框架学习(14):Spring的事务管理_第1张图片

数据库的内容如下:


工程的目录如下:


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

类的代码如下:

package tx;

public interface BookShopDao {
	
	//根据书号获取书的单价
	public int findBookPriceByIsbn(String isbn);
	
	//更新书的库存,使对应库存减一
	public void updateBookStock(String isbn);
	
	//更新用户的账户余额,使username 的balance减去price
	public 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 {

	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	@Override
	public int findBookPriceByIsbn(String isbn) {
		// TODO Auto-generated method stub
		String sql = "SELECT price FROM book WHERE isbn = ?";
		return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
	}

	@Override
	public 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);
	}

	@Override
	public 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 {

	@Autowired
	private 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)
	@Override
	public void purchase(String username, String isbn) {
		// TODO Auto-generated method stub
		/*模拟事务超时
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.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的配置文件如下:




	
	
	
	

	
	
		
		
		
		
		
		
		
	
	
	
	
		
	
	
	
	
		
	

	
	
		
		
	
	
	
	

property文件:

jdbc.user=root
jdbc.password=1234
jdbc.driverclass=com.mysql.cj.jdbc.Driver
jdbc.jdbcurl=jdbc:mysql:///spring-5?useSSL=true&serverTimezone=UTC

jdbc.initPoolSize=5
jdbc.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);
	}
	
	
	
	@Test
	public void testBookShoService() {
		bookShopService.purchase("AA", "1001");
	}
	
	@Test
	public void testBookShopDaoUpdate() {
		bookShopDao.updataUserAccount("AA", 100);
	}
	
	@Test
	public void testBookShopDaoUpdateBookStock() {
		bookShopDao.updateBookStock("1001");
	}
	
	@Test
	public void testBookShopDapFindPriceByIsbn() {
		System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
	}

}
运行一下可以发现运行结果正常,再次查看数据库也会发现一切正常。读者可以自行修改数据库,产生购买失败的情况,可以看到失败的事件会被回滚。

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

写一个cashier类来测试一下:

package tx;

import java.util.List;

public interface Cashier {
	public void checkout(String username, List 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 {

	
	@Autowired
	private BookShopService bookShopService;
	
	//添加事务注解
	@Transactional
	@Override
	public void checkout(String username, List isbns) {
		// TODO Auto-generated method stub
		for (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的配置文件中进行事务管理,配置文件如下:






	
	
	
	

	
	
		
		
		
		
		
		
		
	
	
	
	
		
	

	
	
		
	
	
	
		
	
	
	
		
	
	
	
	
		
	
	
	
	
		
			
			
			
			
			
		
	
	
	
	
	
		
		
	


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




你可能感兴趣的:(Spring框架学习,Spring,事务,mysql)