事物
事物有四个特性:原子性,一致性,隔离性,持久性。在日常开发对数据库事物操作是非常常见的。如果我们手动写jdbc代码,需要手动调用Connection类的 con.setAutoCommit(false);设置其不自动提交,在try-catch块中显式的调用rollback()函数。
spring对事物的支持
PROPAGATION_REQUIRED | 如果存在已经一个事务,就支持当前事务。如果没有事务则开启新的事物 |
---|---|
PROPAGATION_SUPPORTS | 如果存在已经一个事务,就支持当前事务。如果没有事务,则非事务的执行。 |
PROPAGATION_MANDATORY | 如果已经存在一个事务,就支持当前事务。如果没有一个活动的事务,则抛出异常 |
PROPAGATION_REQUIRES_NEW | 总是开启一个新的事务。如果一个事务已经存在,就将这个存在的事务挂起 |
PROPAGATION_NOT_SUPPORTED | 总是非事务地执行,并挂起任何已经存在的事务 |
PROPAGATION_NEVER | 总是非事务地执行,如果存在一个活动事务,就抛出异常 |
PROPAGATION_NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中.如果没有活动事务,则按PROPAGATION_REQUIRED属性执行 |
使用AOP管理事物
我们通过一个例子,看下如何使用aop管理事物。
有2张表,一张商品表,一张销售流水表,卖出商品时需要在流水表中添加一条记录,同时更改商品表中的库存数。
create table good(id int UNSIGNED AUTO_INCREMENT,
gname varchar(50) NOT NULL COMMENT '商品名',
serialNumber varchar(20) NOT NULL COMMENT '商品编码',
price double(10,2) NOT NULL COMMENT '价格',
stock_number int UNSIGNED COMMENT '库存数量',
create_time datetime,
primary key(id)
)engine=innodb default charset=utf8;
insert into good (gname,price,create_time) values('牛奶',3.5,10,now());
create table sell_flow(
id int AUTO_INCREMENT,
serialNumber varchar(20) NOT NULL COMMENT '商品编码',
sell_number int UNSIGNED COMMENT '卖出数量',
create_time datetime,
primary key(id)
)engine=innodb default charset=utf8;
xml配置
<?xml version="1.0" encoding="UTF-8"?>
<!-- 查找最新的schemaLocation 访问 http://www.springframework.org/schema/ -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:property-placeholder location="classpath:mysqldb.properties"/>
<context:component-scan base-package="com.aop"></context:component-scan>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${uname}"/>
<property name="password" value="${password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 定义事物管理器-->
<bean id="transctionManger" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transctionManger"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transctionManger">
<tx:attributes>
<tx:method name="sell" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--定义切点,匹配sell方法 -->
<aop:pointcut expression="execution(* com.aop.service.ShopService.sell(..))" id="pointcut"/>
<!--定义事务管理通知 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
package com.aop.dao;
public interface ShopDao {
/**
* 出售方法
* @param serialNumber 商品编号
* @param Snumber 卖出数量
* @return
*/
int sellGoods(String serialNumber, int Snumber);
/**
* 根据商品编号更新库存
* @param serialNumber 商品编号
* @param Snumber 卖出数量
* @return
*/
public int updateStockByName(String serialNumber,int Snumber);
}
商品数据库操作类
package com.aop.dao.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
import com.aop.dao.ShopDao;
@Component
public class ShopDaoImpl implements ShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int sellGoods(String serialNumber, int snumber) {
String sql="insert into sell_flow"
+ "(serialNumber,sell_number,create_time) "
+ "values(?,?,now())";
Object[] args ={serialNumber,snumber};
return jdbcTemplate.update(sql, args);
}
@Override
public int updateStockByName(String serialNumber,int snumber) {
String sql ="update good g set stock_number = g.stock_number-? where serialNumber=?";
Object[] args ={snumber,serialNumber};
return jdbcTemplate.update(sql, args);
}
}
package com.aop.service;
public interface ShopService {
/**
* 卖方法
* @param serialNumber
* @param Snumber
*/
void sell(String serialNumber, int Snumber);
}
商品service类
@Service
public class ShopServiceImpl implements ShopService {
@Autowired
private ShopDao shopDao;
@Override
public void sell(String serialNumber, int Snumber) {
try {
shopDao.sellGoods(serialNumber, Snumber);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
shopDao.updateStockByName(serialNumber, Snumber);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-aop.xml")
public class springAopTest {
@Autowired
ShopService shopService;
@Test
public void sell() throws Exception{
shopService.sell("NO1", 100000);
}
}
测试,抛出异常,会自动回滚,利用aop我们无需显示的在回滚事物,核心的业务逻辑可以完全分开。
利用注解驱动事物通知
@Transactional(propagation=Propagation.REQUIRED,rollbackFor={Exception.class}
利用@Transactional注解,我们无需配置切点和切面。只需要在注解内指定传播方式,和需要回滚的异常类即可,默认回滚 RuntimeException 和 Error。
在xml文件,以注解驱动通知
<tx:annotation-driven transaction-manager="transctionManger" />
@Service
public class ShopServiceImpl implements ShopService {
@Autowired
private ShopDao shopDao;
@Override
@Transactional(propagation=Propagation.REQUIRED)
public void sell(String serialNumber, int Snumber) {
try {
shopDao.sellGoods(serialNumber, Snumber);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
shopDao.updateStockByName(serialNumber, Snumber);
}
}
使用 TransactionTemplate显式的回滚事物
public void sellTwo(String serialNumber, int Snumber) {
transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
try {
shopDao.sellGoods(serialNumber, Snumber);
shopDao.updateStockByName(serialNumber, Snumber);
} catch (Exception e) {
status.setRollbackOnly();//回滚事物
return false;
}
return true;
}
});
try {
shopDao.sellGoods(serialNumber, Snumber);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
shopDao.updateStockByName(serialNumber, Snumber);
}