SpringBoot(三十八)SpringBoot-mybatis开启事务

130 阅读4分钟

在浏览myBatis相关文档的时候,突然想到一个小问题,到目前为止,好像我还没有使用过事务,这个不太应该。

 

这里我们刚好来测试一下事务叭。

 

一:添加pom依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.3.23</version>
</dependency>

 

二:在启动类上边添加注解开启事务

在启动类加上启动事务注解:@EnableTransactionManagement

@ComponentScan(basePackages = "com")
@SpringBootApplication
// 添加这个注解扫描dao文件
@MapperScan(basePackages = "com.modules.dao")
// 开启定时任务
@EnableScheduling
// 开启mybatis数据库事务
@EnableTransactionManagement
public class EntryApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(EntryApplication.class, args);
    }
}

 

三:开启事务

开启事务其实很简单,就是在方法上添加注解@Transactional即可。

测试代码如下:

@Transactional // 开启事务
@GetMapping("java/testTrans")
public Map<StringObjecttest()
{
    String username = "Transactional";
    String ip = "0.0.0.0";
    Browse browse = new Browse();
    browse.setUsername(username.toString());
    browse.setArticleTitle("test:Transactional");
    browse.setIp(ip);
    browse.setIsWeixin((byte) '0');
    articleDao.addBrowse(browse);
    Map<StringObject> map = new HashMap<>(3);
    map.put("code",1);
    map.put("msg","数据写入成功!");
    return map;
}

如果代码中发生错误,则当前方法中的所有数据库操作都会回滚。运行方法,发现数据库中多了一条数据。

 

正常是不会发生错误的,我这里手动制造一个小错误,代码如下:

@Transactional // 开启事务
@GetMapping("java/testTrans")
public Map<StringObjecttest()
{
    String username = "Transactional";
    String ip = "0.0.0.0";
    Browse browse = new Browse();
    browse.setUsername(username.toString());
    browse.setArticleTitle("test:Transactional");
    browse.setIp(ip);
    browse.setIsWeixin((byte) '0');
    articleDao.addBrowse(browse);
    // 添加一个除0错误
    Integer res = 1 / 0;
    Map<StringObject> map = new HashMap<>(3);
    map.put("code",1);
    map.put("msg","数据写入成功!");
    return map;
}

因为我配置了全局异常捕获,因此访问上方代码,是直接被捕获返回:

{"msg":"/ by zero","code":-888}

查看数据库,事务生效,没有数据写入。

 

四:事务失效情况

1:在方法中使用try-catch会导致事务失效

@Transactional // 开启事务
@GetMapping("java/testTrans")
public Map<String, Object> test()
{
    Map<String, Object> map = new HashMap<>(3);
    try
    {
        String username = "Transactional";
        String ip = "0.0.0.0";
        Browse browse = new Browse();
        browse.setUsername(username.toString());
        browse.setArticleTitle("test:Transactional");
        browse.setIp(ip);
        browse.setIsWeixin((byte'0');
        articleDao.addBrowse(browse);
        // 添加一个除0错误
        Integer res = 1 / 0;
        map.put("code",1);
        map.put("msg","数据写入成功!");
        return map;
    }
    catch(Exception e)
    {
        map.put("code",-1);
        map.put("msg","出错了!");
        return map;
    }
}

上方的代码,事务就不会生效。

 

五:手动回滚事务

那这就有一个小问题,我需要使用try-catch来捕获代码中的异常,那事务和try-catch就不能共存了?

当然是不存在这个问题的,我们可以在catch中手动提交回滚。

需要对上边的代码做两个改动:

@Transactional(rollbackFor = Exception.class) // 开启事务

在注解中指定rollback是在Exception中。

在try-catch的catch中添加手动回滚代码:

// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

 

全部代码如下所示:

@Transactional(rollbackFor = Exception.class// 开启事务
@GetMapping("java/testTrans")
public Map<String, Object> test()
{
    Map<String, Object> map = new HashMap<>(3);
    try
    {
        String username = "Transactional";
        String ip = "0.0.0.0";
        Browse browse = new Browse();
        browse.setUsername(username.toString());
        browse.setArticleTitle("test:Transactional");
        browse.setIp(ip);
        browse.setIsWeixin((byte'0');
        articleDao.addBrowse(browse);
        // 添加一个除0错误
        Integer res = 1 / 0;

        map.put("code",1);
        map.put("msg","数据写入成功!");
        return map;
    }
    catch(Exception e)
    {
        // 手动回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        map.put("code",-1);
        map.put("msg","出错了!");
        return map;
    }
}

访问代码,发现事务执行成功,数据也回滚成功。

 

六:@Transactional经常遇到的几个场景:

@Transactional 加于private方法, 无效

@Transactional 加于未加入接口的public方法, 再通过普通接口方法调用, 无效

@Transactional 加于接口方法, 无论下面调用的是private或public方法, 都有效

@Transactional 加于接口方法后, 被本类普通接口方法直接调用, 无效

@Transactional 加于接口方法后, 被本类普通接口方法通过接口调用, 有效

@Transactional 加于接口方法后, 被它类的接口方法调用, 有效

@Transactional 加于接口方法后, 被它类的私有方法调用后, 有效

 

七:使用DataSourceTransactionManager和TransactionDefinition实现事务

除了使用@Transactional注解来实现事务,使用DataSourceTransactionManager和TransactionDefinition也可以实现事务。

代码如下:

// 这两个必须要注入
@Resource
DataSourceTransactionManager dataSourceTransactionManager;
@Resource
TransactionDefinition transactionDefinition;

//@Transactional(rollbackFor = Exception.class) // 开启事务
@GetMapping("java/testTrans")
public Map<StringObjecttest()
{
    // 开启事务
    TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
    Map<StringObject> map = new HashMap<>(3);
    try
    {
        String username = "Transactional";
        String ip = "0.0.0.0";
        Browse browse = new Browse();
        browse.setUsername(username.toString());
        browse.setArticleTitle("test:Transactional");
        browse.setIp(ip);
        browse.setIsWeixin((byte) '0');
        articleDao.addBrowse(browse);
        // 添加一个除0错误
        //Integer res = 1 / 0;

        //提交事务
        dataSourceTransactionManager.commit(transactionStatus);

        map.put("code",1);
        map.put("msg","数据写入成功!");
        return map;
    }
    catch(Exception e)
    {
        // 回滚事务
        dataSourceTransactionManager.rollback(transactionStatus);
        // 手动回滚
        //TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        map.put("code",-1);
        map.put("msg","出错了!");
        return map;
    }
}

 

这个不太推荐使用,如果没有特殊要求,还是推荐使用@Transactional注解来实现事务。

 

以上大概就是Springboot实现数据库事务的两种方式。

 

有好的建议,请在下方输入你的评论。