关系型数据库事务

MySQL MyBatis 事务处理

Posted by Yezhiwei on October 31, 2017

使用场景

在实际开发中,其实很少会用到事务,一般情况下事务用的比较多的是在金钱计算方面,或购买下单扣库存的过程(过程中一个购买操作包含多个执行过程:查询库存、下单、更新库存,实际操作时,由于高并发存在,可能到下单结束时,更新库存出错,那本次购买操作就是失败的,其下单结果应该被回滚)。如果在一些对一致性要求不高的情况下,可以通过最终一致来解决这个问题。

实现起来很简单

MyBatis与Spring集成后,其事务该怎么做?其实很简单,直接在上一节代码的基础上增加Service层,并在Service类上增加@Transactional(通常是在Service层注解)注解即可。

注意事项

默认会情况是抛出RuntimeException 运行时异常时,才会回滚事务。可以通过修改 @Transactional(rollbackFor = Exception.class) 来改变默认行为。


Service

package com.gemantic.simulate.service;

import com.gemantic.simulate.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Created by Yezhiwei on 17/10/31.
 */

@Transactional(rollbackFor = Exception.class)
@Service("us") // 给service取个名字,因为项目中的其他测试中已经存在
public class UserService {

    @Autowired
    private UserDao userDao;

    public boolean addUser() throws Exception {

        int result = userDao.insertUser("Yezhwi4", "123", "12345678901");

        // 故意抛出异常,进行事务测试
        if (1 == result) {
            System.out.println("result 1");
//            throw new RuntimeException();
            throw new Exception();
        }
        return true;
    }
}

Test

package com.gemantic.simulate.service;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

/**
 * Created by Yezhiwei on 17/10/31.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class UserServiceTest {

    @Resource(name = "us")
    private UserService userService;

    @Test
    public void testAddUser() throws Exception {
        userService.addUser();
    }
}

通过几次测试,查询数据库并没有产生脏数据。

实践

  1. 如果UserService中有读数据的方法,可以把事务修改为只读 @Transactional(readOnly = true) 增加到相应的方法上。
  2. 如果方法抛出的异常类型为checked异常,可以通过 try-catch块处理,在catch中抛出unchecked异常,实现回滚。
  3. 建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。
  4. 查了很多资料,为了添加事务,很多人会在MyBatisConfig这个类中增加@EnableTransactionManagement注解,但是实际测试中不加这句@Transactional注解事务依然有用。
  5. MyBatisConfig类中添加了获取事务管理器的方法,添加下面代码段的作用是在使用@Transactional注解的地方使用方法中的事务管理器进行事务管理。
/**
 * 配置事务管理器
 * @param dataSource
 * @return
 * @throws Exception
 */
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) throws Exception {
    return new DataSourceTransactionManager(dataSource);
}