大话spring

  • MANDATORY

  • NEVER

  • NOT_SUPPORTED

  • SUPPORTS

  • REQUIRED(默认)

  • REQUIRES_NEW

  • NESTED

所以,这个面试题的答案就是:“2个事务性的方法,一个调用另一个,由于事务传播的默认值是REQUIRED,则表现为:如果当前无事务,则创建,如果当前有事务,则使用”,为了完善我的答案,我还继续补充了:“如果将@Transactional注解的propagation属性配置为其它值,则会不同”。当我非常流利的把我脑中的答案说完之后,面试官笑了笑,说了两个字:“不对”,我当时就懵了,最后,面试官也没有告诉我答案,只是让我自己回去找答案……不过运气还算不错,由于只错了这一题,最后还是顺利入职了。

我觉得每个码农对技术都是有一定的执着的,前天面试完后,自己也上网看了一些文章,大多都只说了事务的传播类型,及各种类型的表现,根本没有我想要的答案,于是,昨天我联系了一下当年培训时的苍老师,他听了题目和我的答案后,也是“呵呵”一笑,说这是Spring认证考试中的原题,被考到这一题的概率至少有70%,而且,最近好多公司都直接拿Spring题库里的题当面试题……然后他就让我等着,过一会给我发了个压缩包,是一份Demo代码,果然是人狠话不多,直接拿代码讲道理,我看了看代码,按照苍老师在代码里留的注释改动了几下,基本上就有答案了!

虽然答案本身很简单,但是又领悟了不少东西,为了“纪念”一下这个错题,和大家分享一下Spring中@Transactional的细节!

首先,项目结构是这样的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sr0TLW2I-1625749860880)(image-20210630170414935.png)]

这个项目中主要用到了Spring、Mybatis和单元测试,比较基础的环境搭建和配置就不说了,如果需要这个代码的,可以从 http:// 下载。

大概就是:项目中使用了t_usert_order这2张表,且都有几条初始数据,在这2张表对应的持久层都编写了根据id修改数据的功能。

重点是业务部分,我们都知道,事务是在业务层进行管理的,业务层的结构是这样的(暂时不用的先不贴出来):

[src]

[main]

[java]

[cn.tedu]

[service]

[impl]

UserServiceImpl

UserService

很显然,以User前缀开头的都是处理t_user表的数据的,在最初的实验中只需要观察这1张表就可以了。

关于UserService接口:

package cn.tedu.service;

public interface UserService {

void update1();

void update2();

}

关于UserServiceImpl类:

package cn.tedu.service.impl;

import cn.tedu.mapper.UserMapper;

import cn.tedu.service.OrderService;

import cn.tedu.service.UserService;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;

@Service

public class UserServiceImpl implements UserService {

private UserMapper userMapper;

private OrderService orderService;

public UserServiceImpl(UserMapper userMapper, OrderService orderService) {

this.userMapper = userMapper;

this.orderService = orderService;

}

// TODO-01:调整是否使用以下@Transactional注解,并运行单元测试,以观察效果

// @Transactional

public void update1() {

int rows;

// 更新id=1的数据,会成功

rows = userMapper.updateUserNameById(1, “USER-1000001”);

if (rows != 1) {

throw new RuntimeException(“更新User:id=1数据失败!”);

}

// 更新id=1000000,会失败

rows = userMapper.u 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 pdateUserNameById(1000000, “USER-1000001”);

if (rows != 1) {

throw new RuntimeException(“更新User:id=1000000数据失败!”);

}

}

// TODO-02:调整是否使用以下@Transactional注解,并运行单元测试,以观察效果

@Transactional

public void update2() {

update1();

}

}

可以看到,以上update1()方法中有2次更新操作,第1次肯定会成功的,第2次则会因为id值不存在而失败,失败后抛出了RuntimeException对象,符合Spring管理事务的默认回滚规则,但是,update1()方法不一定有@Transactional注解,这是苍老师留着我自己测试效果的,下面的update2()就比较简单了,它直接调用了update1()方法。

苍老师写的测试也非常有趣,使用了@Sql注解处理初始化数据库与数据,使用了断言,和我们平时偷懒写的完全不同,那天我也问过他,他说Spring认证考试也会考这个,以后搞不好也会成为用人单位的面试题(毕竟有不少用人单位都是直接上网百度找面试题,根本不自己出错,大家都懂的)……他是这么写的:

package cn.tedu.service;

import cn.tedu.config.ApplicationConfig;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.context.jdbc.Sql;

import org.springframework.test.context.jdbc.SqlConfig;

import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

import static org.junit.jupiter.api.Assertions.assertThrows;

@SpringJUnitConfig(ApplicationConfig.class)

@Sql(config = @SqlConfig(dataSource = “dataSource”),

scripts = {“classpath:/sql/schema.sql”, “classpath:/sql/data.sql”})

public class UserServiceTests {

@Autowired

UserService userService;

@Test

public void testUpdate1() {

assertThrows(RuntimeException.class, () -> {

userService.update1();

});

}

@Test

public void testUpdate2() {

assertThrows(RuntimeException.class, () -> {

userService.update2();

});

}

}

其实,现在就可以测试出效果了,根据在业务类中的2个方法上是否使用@Transactional注解,观察数据是否回滚即可判断,我测试的结果如下:

| 是否在update1()上使用注解 | 是否在update2()上使用注解 | 是否回滚 |

| — | — | — |

| 是 | 是 | 是 |

| 否 | 是 | 是 |

| 是 | 否 | 否 |

| 否 | 否 | 否 |

可以看到,事务是否回滚完全取决于update2()方法有没有@Transactional注解,与update1()方法是否有注解无关

苍老师说,Spring官方给出的文档中明确指出:Propagation Rules Are Enforced by a Proxy,即“传播规则是由代理强制执行的”。所以,Spring管理事务是基于接口进行代理的,在调用@Transactional注解的方法之前就会开启事务,并在过程中决定是否回滚或最终提交!

在以上代码中,由于update1()是在update2()内部调用的,不是由代理对象来调用的,所以,执行update2()方法的过程大致上是:

开启事务

执行update2()方法

调用update1()方法

因update1()方法抛出异常且符合回滚规则,执行回滚事务

若未出现回滚,则提交事务(本例会回滚,不会执行这一步)

所以,回到我面试的那个题目,正确的答案应该是:只会在调用update2()方法时开启1个事务,内部调用的update1()根本不是事务性的(不管有没有@Transactional注解),既然只有1个事务,也就不存在事务的传播了

其实,到这里,我的问题已经解决了,但是苍老师还帮我写好了后续的Demo代码,让我更深刻的理解,这可能就是老师的职业病吧,要么不讲,要讲就一讲到底。

接下来就要涉及更新t_order表的数据了,对应的业务接口和业务实现类分别是OrderServiceOrderServiceImpl,关于OrderService接口:

package cn.tedu.service;

public interface OrderService {

void updateSuccessfully();

}

关于OrderServiceImpl类:

package cn.tedu.service.impl;

import cn.tedu.mapper.OrderMapper;

import cn.tedu.service.OrderService;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Propagation;

import org.springframework.transaction.annotation.Transactional;

@Service

public class OrderServiceImpl implements OrderService {

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值