spring学习四-Spring事务管理

目录

 

1 案例模型与代码环境

1.1 案例模型

1.2 事务说明

2 注解配置事务

2.1 配置事务管理器

2.2 添加Transactional注解

2.3 事务测试

2.3.1 异常测试

2.3.2 正常测试

2.3.3 不带事务的异常测试

3 事物的传播行为

4 Transactional其他属性

5 @Transactional不生效的场景

5.1 数据库引擎是否支持事务

5.2 注解所在方法不是public修饰或者用final修饰

5.3 所用数据源是否加载了事务管理器

5.4 同一个类中方法调用

5.5 异常被捕获导致事务不生效


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,不会对事务进行回滚。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值