关于java:面试突击83什么情况会导致Transactional事务失效

8次阅读

共计 3108 个字符,预计需要花费 8 分钟才能阅读完成。

一个程序中不可能没有事务,而 Spring 中,事务的实现形式分为两种:编程式事务和申明式事务,又因为编程式事务实现绝对麻烦,而申明式事务实现极其简略,所以在日常我的项目中,咱们都会应用申明式事务 @Transactional 来实现事务。

@Transactional 应用极其简略,只须要在类上或办法上增加 @Transactional 关键字,就能够实现事务的主动开启、提交或回滚了,它的根底用法如下:

@Transactional 
@RequestMapping("/add")
public int add(UserInfo userInfo) {int result = userService.add(userInfo);
    return result;
}

@Transactional 执行流程

@Transactional 会在办法执行前,会主动开启事务;在办法胜利执行完,会主动提交事务;如果办法在执行期间,呈现了异样,那么它会主动回滚事务。

然而,就是看起来极其简略的 @Transactional,却暗藏着一些“坑”,这些坑就是咱们明天要讲的主题:导致 @Transactional 事务生效的常见场景有哪些?

在开始之前,咱们先要明确一个定义,什么叫做“生效”?

本文中的“生效”指的是“ 失去(它的)效用 ”,也就是当 @Transactional 不合乎咱们预期的后果时,咱们就能够说 @Transactional 生效了。

那 @Transactional 生效的场景有哪些呢?接下来咱们一一来看。

1. 非 public 润饰的办法

当 @Transactional 润饰的办法为非 public 时,事务就生效了 ,比方以下代码当遇到异样之后,不能主动实现回滚:

@RequestMapping("/save")
int save(UserInfo userInfo) {
    // 非空效验
    if (userInfo == null ||
        !StringUtils.hasLength(userInfo.getUsername()) ||
        !StringUtils.hasLength(userInfo.getPassword()))
        return 0;
    // 执行增加操作
    int result = userService.save(userInfo);
    System.out.println("add 受影响的行数:" + result);
    int num = 10 / 0; // 此处设置一个异样
    return result;
}

以上程序的运行后果如下:

当程序呈现运行时异样时,咱们预期的后果是事务应该实现主动回滚,也就是增加用户失败,然而当咱们查询数据库时,却发现事务并未执行回滚操作 ,数据库的数据如下图所示:

2.timeout 超时

当在 @Transactional 上,设置了一个较小的超时工夫时,如果办法自身的执行工夫超过了设置的 timeout 超时工夫,那么就会导致原本应该失常插入数据的办法执行失败 ,示例代码如下:

@Transactional(timeout = 3) // 超时工夫为 3s
@RequestMapping("/save")
int save(UserInfo userInfo) throws InterruptedException {
    // 非空效验
    if (userInfo == null ||
        !StringUtils.hasLength(userInfo.getUsername()) ||
        !StringUtils.hasLength(userInfo.getPassword()))
        return 0;
    int result = userService.save(userInfo);
    return result;
}

UserService 的 save 办法实现如下:

public int save(UserInfo userInfo) throws InterruptedException {
    // 休眠 5s
    TimeUnit.SECONDS.sleep(5);
    int result = userMapper.add(userInfo);
    return result;
}

以上程序的运行后果如下:

数据库没有正确的插入数据,如下图所示:

3. 代码中有 try/catch

在后面 @Transactional 的执行流程中,咱们提到:当办法中呈现了异样之后,事务会主动回滚。然而,如果在程序中加了 try/catch 之后,@Transactional 就不会主动回滚事务了,示例代码如下:

@Transactional
@RequestMapping("/save")
public int save(UserInfo userInfo) throws InterruptedException {
    // 非空效验
    if (userInfo == null ||
        !StringUtils.hasLength(userInfo.getUsername()) ||
        !StringUtils.hasLength(userInfo.getPassword()))
        return 0;
    int result = userService.save(userInfo);
    try {int num = 10 / 0; // 此处设置一个异样} catch (Exception e) { }
    return result;
}

以上程序的运行后果如下:

此时,查询数据库咱们发现, 程序并没有执行回滚操作 ,数据库中被胜利的增加了一条数据,如下图所示:

4. 调用类外部 @Transactional 办法

当调用类外部的 @Transactional 润饰的办法时,事务是不会失效的,示例代码如下:

@RequestMapping("/save")
public int saveMappping(UserInfo userInfo) {return save(userInfo);
}
@Transactional
public int save(UserInfo userInfo) {
    // 非空效验
    if (userInfo == null ||
        !StringUtils.hasLength(userInfo.getUsername()) ||
        !StringUtils.hasLength(userInfo.getPassword()))
        return 0;
    int result = userService.save(userInfo);
    int num = 10 / 0; // 此处设置一个异样
    return result;
}

以上代码咱们在增加办法 save 中增加了 @Transactional 申明式事务,并且增加了异样代码, 咱们预期的后果是程序出现异常,事务进行主动回滚 ,以上程序的执行后果如下:

然而,当咱们查询数据库时发现,程序执行并不合乎咱们的预期,增加的数据并没有进行主动回滚操作,如下图所示:

5. 数据库不反对事务

咱们程序中的 @Transactional 只是给调用的数据库发送了:开始事务、提交事务、回滚事务的指令,然而如果数据库自身不反对事务,比方 MySQL 中设置了应用 MyISAM 引擎,那么它自身是不反对事务的,这种状况下,即便在程序中增加了 @Transactional 注解,那么仍然不会有事务的行为,这就是巧妇也难为无米之炊吧。

总结

当申明式事务 @Transactional 遇到以下场景时,事务会生效:

  1. 非 public 润饰的办法;
  2. timeout 设置过小;
  3. 代码中应用 try/catch 解决异样;
  4. 调用类外部 @Transactional 办法;
  5. 数据库不反对事务。

参考 & 鸣谢

www.cnblogs.com/frankyou/p/12691463.html

是非审之于己,毁誉听之于人,得失安之于数。

公众号:Java 面试真题解析

面试合集:https://gitee.com/mydb/interview

正文完
 0