目录
5.2 注解所在方法不是public修饰或者用final修饰
1 案例模型与代码环境
1.1 案例模型
1.2 事务说明
模拟一次购物,买若干数量的商品。
如果库存充足,则购买成功,库存数量减少,订单表、订单明细表中插入记录。
如果库存不足,购买失败,库存数量不变,订单表、订单明细表中无记录。
ShopDaoImpl:
@Repository("shopDao")
public class ShopDaoImpl implements ShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//查询库存
@Override
public Integer getGoodNum(Integer goodId) {
String sql = "select num from goods where goodsId = ?";
return jdbcTemplate.queryForObject(sql, Integer.class, goodId);
}
//更新库存
@Override
public void updateGoodNum(Integer goodId, Integer buyNum) {
String sql = "update goods set num = num- ? where goodsId = ?";
if(getGoodNum(goodId) < buyNum){
throw new StockException("库存不足");
}
jdbcTemplate.update(sql, buyNum, goodId);
}
//更新订单
@Override
public void updateOrder() {
String sql = "insert into order_ (clientId,createTime, note) values(1001, ?, '无')";
jdbcTemplate.update(sql, new Date());
}
//更新订单明细
@Override
public void updateOrderItem(Integer goodId, Integer num) {
Long orderId = jdbcTemplate.queryForObject("select max(orderId) from order_", Long.class);
String sql = "insert into orderItem (orderId, goodsId, goodsNum) values (?, ?, ?)";
jdbcTemplate.update(sql, orderId, goodId, num);
}
}
ShopServiceImpl:
@Service
public class ShopServiceImpl implements ShopService {
@Resource(name = "shopDao")
private ShopDao shopDao;
public void purchase(List<Integer[]> orderList) {
shopDao.updateOrder();
//order是一个两个数的数组,第一个数是商品Id,第二个数是购买数量
for(Integer[] order: orderList){
shopDao.updateOrderItem(order[0], order[1]);
shopDao.updateGoodNum(order[0], order[1]);
}
}
}
beans.xml配置
<!-- 配置c3p0数据源 -->
<context:property-placeholder location="classpath:sqlserver.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="driverClass" value="${driverClass}"></property>
<property name="initialPoolSize" value="${initPoolSize}"></property>
<property name="maxPoolSize" value="${maxPoolSize}"></property>
</bean>
<context:component-scan base-package="com.hwl.transaction_xml"></context:component-scan>
<!-- 配置jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
2 注解配置事务
2.1 配置事务管理器
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事物注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
2.2 添加Transactional注解
@Transactional
public void purchase(List<Integer[]> orderList) {
shopDao.updateOrder();
//order是一个两个数的数组,第一个数是商品Id,第二个数是购买数量
for(Integer[] order: orderList){
shopDao.updateOrderItem(order[0], order[1]);
shopDao.updateGoodNum(order[0], order[1]);
}
}
2.3 事务测试
2.3.1 异常测试
@Test
public void testPurchase(){
List<Integer[]> orderList = new ArrayList<Integer[]>();
Integer[] order1 = {1002,500};
Integer[] order2 = {1002,9999};
orderList.add(order1);
orderList.add(order2);
shopService.purchase(orderList);
}
测试结果:
数据库记录:
2.3.2 正常测试
@Test
public void testPurchase(){
List<Integer[]> orderList = new ArrayList<Integer[]>();
Integer[] order1 = {1002,500};
Integer[] order2 = {1003,500};
orderList.add(order1);
orderList.add(order2);
shopService.purchase(orderList);
}
2.3.3 不带事务的异常测试
数据还原,去掉@Transactional注解
一个商品减少,另一个商品数量不变,但是生成订单明细。
@Test
public void testPurchase(List<Integer[]> orderList){
List<Integer[]> orderList = new ArrayList<Integer[]>();
Integer[] order1 = {1002,500};
Integer[] order2 = {1003,9999};
orderList.add(order1);
orderList.add(order2);
shopService.purchase(orderList);
}
3 事物的传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为有如下几类,默认是REQUIRED。表明如果一个事务调用多个该事务,统一看成一个事务。
案例说明:
在ShopController中的purchase方法中用@Transactional调用两次shopService的purchase方法,相当于ShopController中的purchase中有两个事务。恢复数据,进行测试。
@Transactional
public void purchase(){
List<Integer[]> orderList = new ArrayList<Integer[]>();
Integer[] order1 = {1002,600};
orderList.add(order1);
shopService.purchase(orderList);
shopService.purchase(orderList);
}
测试:
@Test
public void testPurchase(){
List<Integer[]> orderList = new ArrayList<Integer[]>();
Integer[] order1 = {1002,600};
orderList.add(order1);
shopController.purchase();
}
测试结果
shopService的每一个purchase方法都没有成功。
修改事务的传播行为:
将shopService的purchase方法用@Transactional(propagation=Propagation.REQUIRES_NEW)标注,在测试
表名当该事务被另一个事务多次调用的时候,会出现多全新的事务。
4 Transactional其他属性
在@Transactional中注明isolation属性设置隔离级别;
注明noRollbackFor={}表明对哪些异常不回滚
注明readOnly=true为只读,如果事务不进行增删改,则设置只读,能优化数据库。
注明timeout设置事务从开始到结束的最长时间(单位为秒),如果超时则进行回滚。这样可以缩短连接占用时间。
5 @Transactional不生效的场景
5.1 数据库引擎是否支持事务
Mysql的MyIsam引擎就不支持事务
5.2 注解所在方法不是public修饰或者用final修饰
这是由Spring AOP 的本质决定的。如果你在 protected、private 或者default的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
@Transactional的实现是基于动态代理的。jdk动态代理是通过接口实现的,要求方法是public。cglib动态代理是基于子类来实现的,要求方法可以覆盖,不能用final修饰。
5.3 所用数据源是否加载了事务管理器
springboot项目通过@EnableTransactionManagement注解来配置事务管理器,新版本的springboot已经自动添加了该配置,不需要手动声明
5.4 同一个类中方法调用
还是由于使用Spring AOP代理造成的,同一个类方法调用是通过自身对象调用的,事务实现是通过AOP机制实现的,需要通过代理对象调用才能够生效。
5.5 异常被捕获导致事务不生效
手动的捕获这个异常并进行处理,spring会认为当前事务应该正常commit,不会对事务进行回滚。