Spring声明事务
了解spring使用事务之前,首先让我们看看如何手动写一个事务
需求:一个book shop的管理实现
dataSource.properties (数据源信息)
jdbc.user = root
jdbc.password =123456
jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.jdbcUrl = jdbc:mysql:///spring2
jdbc.initPoolSize=5
jdbc.maxPoolSize=10
applicationContext.xml (Spring的配置文件)
<?xml version="1.0" encoding="UTF-8"?>BookShopDao.java (书店数据库操作接口)
<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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--自动扫描-->
<context:component-scan base-package="cn.limbo.spring"></context:component-scan>
<!--导入资源文件-->
<context:property-placeholder location="dataSource.properties"></context:property-placeholder>
<!--配置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>
<!--配置NamedParameterJdbcTemplate,该对象可以使用具名参数,其没有无参的构造器,所以要为构造器指定参数-->
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
</beans>
package cn.limbo.spring.tx;
/**
* Created by Limbo on 16/7/15.
*/
public interface BookShopDao {
//根据书号获取书的单价
public int findBookPriceByIsbn(int isbn);
//更新书的库存,是书号对应的库存 - 1
public void updateBookStock(int isbn);
//更新用户的账户余额:是userName的balance - price
public void updateUserAccount(String userName,int price);
}
BookShopDaoImpl.java (书店数据库操作实现)
package cn.limbo.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
/**
* Created by Limbo on 16/7/15.
*/
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int findBookPriceByIsbn(int isbn) {
String sql = "SELECT price FROM book WHERE isbn = ?";
return jdbcTemplate.queryForObject(sql,Integer.class,isbn);
}
@Override
public void updateBookStock(int 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);
}
}
BookShopService.java (书店服务接口)
package cn.limbo.spring.tx;BookShopServiceImpl.java (书店服务实现)
/**
* Created by Limbo on 16/7/15.
*/
public interface BookShopService {
public void purchase(String userName,int price);
}
package cn.limbo.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Created by Limbo on 16/7/15.
*/
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService{
@Autowired
private BookShopDao bookShopDao;
@Override
public void purchase(String userName, int isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(userName,price);
}
}
BookStockException.java (书库存异常)
package cn.limbo.spring.tx;UserAccountException.java (用户余额异常)
/**
* Created by Limbo on 16/7/15.
*/
public class BookStockException extends RuntimeException {
public BookStockException() {
super();
}
public BookStockException(String message) {
super(message);
}
public BookStockException(String message, Throwable cause) {
super(message, cause);
}
public BookStockException(Throwable cause) {
super(cause);
}
protected BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
package cn.limbo.spring.tx;SpringTransactionTest.java (测试方法)
/**
* Created by Limbo on 16/7/15.
*/
public class UserAccountException extends RuntimeException {
public UserAccountException() {
super();
}
public UserAccountException(String message) {
super(message);
}
public UserAccountException(String message, Throwable cause) {
super(message, cause);
}
public UserAccountException(Throwable cause) {
super(cause);
}
protected UserAccountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
package cn.limbo.spring.tx;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by Limbo on 16/7/15.
*/
public class SpringTransactionTest {
private static ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
private static BookShopDao bookShopDao = (BookShopDao) ctx.getBean("bookShopDao");
private static BookShopService bookShopService = (BookShopService) ctx.getBean("bookShopService");
public static void testBookShopDaoFindPriceByIsbn(int isbn)
{
System.out.println(bookShopDao.findBookPriceByIsbn(isbn));
}
public static void testBookShopDaoUpdateStock(int isbn)
{
bookShopDao.updateBookStock(isbn);
}
public static void testBookShopUpdateUserAccount(String name,int price)
{
bookShopDao.updateUserAccount(name,price);
}
public static void testBookShopService(String userName,int isbn)
{
bookShopService.purchase(userName,isbn);
}
public static void main(String[] args) {
testBookShopService("AA",1001);
}
}
上面的testBookShopService这个方法是我们执行事务的方法,但是我们可以发现,这个方法并不满足事务的ACID原则,如果余额不足的情况下,书的储藏数量还是会减少,这时候我们就该用上spring的事务管理器了
为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" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--自动扫描-->
<context:component-scan base-package="cn.limbo.spring"></context:component-scan>
<!--导入资源文件-->
<context:property-placeholder location="dataSource.properties"></context:property-placeholder>
<!--配置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>
<!--配置NamedParameterJdbcTemplate,该对象可以使用具名参数,其没有无参的构造器,所以要为构造器指定参数-->
<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>
注意,tx的命名空间不要导错了,刚开始我就是导错了然后没有出现transaction-manager这玩意,搞得我烦死了
然后在实现事务的方法上加上@Transactional注解就好了,重写后的BookShopServiceImpl.java如下
package cn.limbo.spring.tx;至此,我们就实现了Spring声明式事务的方法
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Created by Limbo on 16/7/15.
*/
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService{
@Autowired
private BookShopDao bookShopDao;
@Transactional //添加事务注解
@Override
public void purchase(String userName, int isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(userName,price);
}
}
事务的传播行为
指事务方法调用另外一个事务方法时,表现出的属性。打个比方,你去饭店吃饭,遇到熟人,然后熟人招呼你吃饭,这个时候你有两个选择,你可以选择和熟人一起吃,也可以自己吃。在上述的表述中,新事务就是我,旧事务是熟人,和熟人一起吃饭就是沿用原先事务,自己吃饭就是创建新的事务,下面看代码 新建一个接口和一个类 Cashier.javapackage cn.limbo.spring.tx;CashierImpl.java
import java.util.List;
/**
* Created by Limbo on 16/7/16.
*/
public interface Cashier {
public void checkOut(String userName,List<Integer> isbns);
}
package cn.limbo.spring.tx;Test.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* Created by Limbo on 16/7/16.
*/
@Service("cashier")
public class CashierImpl implements Cashier{
@Autowired
private BookShopService bookShopService;
@Transactional
@Override
//现在,我们要买一堆书
public void checkOut(String userName, List<Integer> isbns) {
for(Integer isbn : isbns)
{
bookShopService.purchase(userName,isbn);
}
}
}
public static void testTransactionalPropagation(String userName, List<Integer> isbns)
{
cashier.checkOut(userName,isbns);
}
public static void main(String[] args) {
testTransactionalPropagation("AA", Arrays.asList(1001,1002));
}
这时候,我们发现bookShopService.purchase是一个事务,但是checkOut也是一个事务,这时候执行会有什么样的结果呢(此时bookShopService.purchase()还是和上面一样)? 此时,我们发现,在余额足够买1001但是无法买1002时,事务会自动回滚,回滚到两本书都不买的情况,因为这个时候checkOut使用的是原先的事务,没有任何变化,如果我们要实现在余额只够买1001但是不够买1002时仍然可以买1001的话,只要把bookShopService.purchase上的@Transactional改成@Transactional(propagation = Propagation.REQUIRES_NEW)即可,修改后的booShopService.java如下
package cn.limbo.spring.tx;附上propagation所有的属性: PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* Created by Limbo on 16/7/15.
*/
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService{
@Autowired
private BookShopDao bookShopDao;
//添加事务注解
//使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用的时候
//如何使用事务,默认取值为REQUIRED,即使用调用方法的事务
//还有一个取值是REQUIRES_NEW:新建一个自己的事务,调用的事务方法被挂起
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void purchase(String userName, int isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(userName,price);
}
}
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
事务的隔离,回滚,只读,过期
一言不合,直接贴代码,要点写注释上咯package cn.limbo.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;
/**
* Created by Limbo on 16/7/15.
*/
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService{
@Autowired
private BookShopDao bookShopDao;
//添加事务注解
//1.使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用的时候
// 如何使用事务,默认取值为REQUIRED,即使用调用方法的事务
// 还有一个取值是REQUIRES_NEW:新建一个自己的事务,调用的事务方法被挂起
//2.使用isolation指定事务的隔离级别,最常用的是Isolation.READ_COMMITTED
//3.默认情况下,Spring的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置
// 通常情况下,取默认值即可
// noRollbackFor = {UserAccountException.class}指的是对事务发生这一类的异常时,不要回滚了,继续执行
//4.使用readOnly指定事务是否为只读,表示这个事务只读取数据但不更新数据,这样可以帮助数据库优化事务
// 若真的是一个只读取数据库的值的方法,应该设置readOnly=true, 默认值为false,表示可读写
//5.使用timeout指定强制回滚之前事务可以占用的时间,如果指定timeout=3,则事务执行3秒后强制回滚,不管是否执行成功
// @Transactional(propagation = Propagation.REQUIRES_NEW ,
// isolation = Isolation.READ_COMMITTED ,
// noRollbackFor = {UserAccountException.class})
@Transactional(propagation = Propagation.REQUIRES_NEW ,
isolation = Isolation.READ_COMMITTED ,
readOnly = false ,
timeout = 3) //timeout的单位是秒
@Override
public void purchase(String userName, int isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(userName,price);
}
}
事务的xml配置方法
1.配置普通的 controller,service ,dao 的bean.<!-- 配置 dao ,service -->2.配置事务管理器bean
<bean id="bookShopDao" class="com.liujl.spring.tx.xml.BookShopDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="bookShopService" class="com.liujl.spring.tx.xml.serivice.impl.BookShopServiceImpl">
<property name="bookShopDao" ref="bookShopDao"></property>
</bean>
<bean id="cashier" class="com.liujl.spring.tx.xml.serivice.impl.CashierImpl">
<property name="bookShopService" ref="bookShopService"></property>
</bean>
<!-- 配置事务管理器 hibernate、jpa都是类似的这样配 -->3.引入 tx 命名空间
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
xmlns:tx="http://www.springframework.org/schema/tx"4.配置事务各个属性(方法名可以使用通配符*)
<!-- 配置事务属性 -->5.引入aop命名空间
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="purchase" propagation="REQUIRED"/>
<tx:method name="checkout" propagation="REQUIRED"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
xmlns:aop="http://www.springframework.org/schema/aop"
<!-- 配置事务的切点,并把事务切点和事务属性不关联起来 --> <aop:config> <aop:pointcut expression="execution(* com.liujl.spring.tx.xml.serivice.*.*(..))" id="txPointCut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config>