Spring 事务和事务的传播机制

news2024/10/5 21:17:30

1.Spring 中事务的实现方式

Spring 中的操作主要分为两类:

  • 编程式事务 (了解)

  • 声明式事务

编程式事务就是手写代码操作事务, 而声明式事务是利用注解来自动开启和提交事务. 并且编程式事务用几乎不怎么用. 这就好比汽车的手动挡和自动挡, 如果有足够的的钱, 大部分人应该都会选择自动挡.
声明式事务也是如此, 它不仅好用, 还特别方便.

1.1 Spring 编程式事务 (了解)

编程式事务和 MySQL 中操作事务类似, 也是三个重要步骤:

  1. 开启事务

  1. 提交事务

  1. 回滚事务

【代码实现】

@RequestMapping("/user")
public class UserController1 {
    @Autowired
    private UserService userService;

    @Autowired  // JDBC 事务管理器
    private DataSourceTransactionManager dataSourceTransactionManager;

    @Autowired  // 定义事务属性
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/add")
    public int add(String username, String password) {
        // 非空校验
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return 0;
        }
        // 事务 [得到并开启事务]
        TransactionStatus transactionStatus =
                dataSourceTransactionManager.getTransaction(transactionDefinition);

        int result = userService.add(username, password, null);
        System.out.println("添加影响行数: " + result);

        // 提交事务 or 回滚
        dataSourceTransactionManager.rollback(transactionStatus);
        //  dataSourceTransactionManager.commit(transactionStatus);
        return result;
    }
}
DataSourceTransactionManager 和 TransactionDefinition 是 SpringBoot 内置的两个对象.
DataSourceTransactionManager : 用来获取事务(开启事务)、提交或回滚事务.
TransactionDefinition : 它是事务的属性,在获取事务的时候需要将这个对象传递进去从而获得⼀个事务 TransactionStatus.

【测试编式事务】

上述代码的主要要业务逻辑就是基于 MyBatis实现了一个新增方法, 接下来我们测试一下编程式事务中的回滚操作是否生效.

  1. 在测试之前先查看一下数据库中的用户信息 (userinfo) :

  1. 启动程序后, 在浏览器输入: 127.0.0.1:8080/user/add?username=李华&password=123

3. 此时我们看见控制台显示添加数据成功, 那么要知道代码中的回滚是否生效, 需要查看数据库是否真正的把数据添加进去了.

4. 发现数据库中并没有添加数据, 说明回滚操作生效了. 而提交事务 commit 就和普通的添加操作差不多, 下来可以自己试一下.

1.2 Spring 声明式事务

声明式事务的实现相较于编程式事务来说, 就要简单太多了, 只需要在需要的方法上添加 @Transactional注解就可以实现了.

@Transactional 注解的作用:

当进入方法的时候, 它就会自动开启事务, 当方法结束后, 它就会自动提交事务. 说白了它就是 AOP 的一个环绕通知. 只要加了 @Transactional 注解的方法, 都有一个事务的 AOP , 这都是 Spring 帮我们封装好的.

@Transactional 注解的执行流程:

1. 方法执行之前, 先开启事务, 当方法成功执行完成之后会自动提交事务.
2. 如果方法在执行过程中发生了异常, 那么事务会自动回滚.

【代码实现】

    @RequestMapping("/add2")
    @Transactional // 声明式事务
    public int add2(String username, String password) {
        // 非空校验
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return 0;
        }
        int result = userService.add(username, password, null);
        System.out.println("添加影响行数: " + result);
        return result;
    }

对于方法执行成功的情况就不测试了, 它和普通的插入数据没有多大区别, 重点在于理解 @Transactional注解的含义和作用即可.

【异常情况一】

    @RequestMapping("/add2")
    @Transactional // 声明式事务
    public int add2(String username, String password) {
        // 非空校验
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return 0;
        }
        int result = userService.add(username, password, null);
        System.out.println("添加影响行数: " + result);
        int num = 10 / 0;
        return result;
    }

当我们写出 int num = 10 / 0; 这样一条语句的时候, 看看 @Transactional 是否会进行回滚操作:

启动程序, 浏览器访问 : 127.0.0.1:8080/user/add2?username=王五&password=123

此时程序已经报错了, 并且打印了添加成功语句, 是否真正添加成功, 还是说进行了回滚操作, 就要查询数据库:

发现数据库中并没有王五这条数据, 说明在发生异常的时候, @Transactional 注解帮我们做了回滚操作.

【异常情况二】

对于上述代码抛出异常后, @Transactional 注解帮我们进行回滚, 这一点很好理解, 那么如果我们将这个异常捕获了, @Transactional 注解是否还会进行回滚操作呢 >>

    @RequestMapping("/add2")
    @Transactional // 声明式事务
    public int add2(String username, String password) {
        // 非空校验
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return 0;
        }
        int result = userService.add(username, password, null);
        System.out.println("添加影响行数: " + result);
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            
        }
        return result;
    }

执行结果: 此时程序没有发生报错了.

为了验证是否进行回滚, 继续查询数据库

此时我们发现, @Transactional 注解并没有进行回滚操作, 而是提交了事务. 这是为什么 ??

因为当我们捕捉到异常的时候, Spring 框架会认为我们有能力处理, 所以就不会进行回滚, 而当发生异常我们不处理的时候, Spring 框架就会采取保守的做法, 他知道我们没有能力去处理这个异常, 所以就会帮我们回滚. 所以当出现异常的时候, 我们要根据这个异常是否被处理来判断最终是提交数据了, 还是进了回滚操作.

1.2.1 声明式事务的手动回滚

当第二种异常情况, 捕获异常之后, 事务并没有进行回滚, 我们是需要做出一些处理的. 既然程序发生了异常, 我们一般就需要进行回滚操作的. 对于这种捕获异常的情况,我们有两种方式进行回滚:

  • 将异常继续抛出.

  • 通过代码手动回滚事务.

【代码示例】- 将异常继续抛出

    @RequestMapping("/add2")
    @Transactional // 声明式事务
    public int add2(String username, String password) {
        // 非空校验
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return 0;
        }
        int result = userService.add(username, password, null);
        System.out.println("添加影响行数: " + result);
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            throw e; // 将异常继续抛出
        }
        return result;
    }

测试代码是否回滚,还是和前面一样的操作,就不赘述了. 代码的最终执行结果肯定是进行了回滚操作.

【代码示例】- 手动回滚事务

    @RequestMapping("/add2")
    @Transactional // 声明式事务
    public int add2(String username, String password) {
        // 非空校验
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return 0;
        }
        int result = userService.add(username, password, null);
        System.out.println("添加影响行数: " + result);
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            // throw e;
            System.out.println("程序发生异常: " + e.getMessage());
            // 手动回滚事务 [得到当前事务并设置回滚] - 通过事务的切面拿到当前事务, 再设置回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }
手动回滚事务 : 通过事务的 AOP 拿到当前的事务, 然后设置回滚.

这种方式来处理事务的回滚, 显得更加优雅, 更推荐使用.

1.2.2 @Transactional 的工作原理

@Transactional 是基于 AOP实现的, AOP又是基于动态代理实现的 (JDK, CGLIB), 它在开始执行业务之前, 会通过代理实现开启事务, 在执行成功之后再提交事务, 如果中途出现了异常, 就会回滚事务.

@Transactional 实现思路

切面会拦截所有加了 @Transactional 注解的方法, 于是切点就有了, 然后开启事务与提交事务/回滚事务之间相当于是一个环绕通知.

2.事务隔离级别

在学 MySQL 的时候, 我们就已经知道了事务有四大特性: (ACID)

原子性 (Atomicity),
持久性(Consistency),
一致性 (Isolation) ,
隔离性 (Durability);

具体的概念在这篇博客中已经做过说明. - MySQL 事务的四大特性.

这四种特性中只有隔离性是可以设置的, 那么为什么要设置事务的隔离级别呢 ??

-- 为了保障多个并发事务执行更可控, 更符合操作者的预期.

2.1 Spring 中设置事务的隔离级别

MySQL 中事务的隔离级别分为四种:

事务隔离级别

脏读

不可重复读

幻读

读未提交 (READ UNCOMMITTED)

读已提交 (READ COMMITTED)

×

可重复读 (REPEATABLE READ)

×

×

串行化 (SERIALIZABLE)

×

×

×

MySQL 默认事务的隔离级别 : 可重复读, 可通过命令 select @@global.tx_isolation,@@tx_isolation; 来进行查看.

1. 脏读 : 一个事务A,在执行的过程中,对数据进行了一系列修改,在提交到数据库之前(完成事务之前),另一个事务B,读取了对应的数据,此时这个B读到的数据都是一些临时的结果,后续可能马上就被A给改了,此时B的读取行为就是"脏读"!

2. 不可重复读 : 事务A提交了事务之后,事务B才开始读(读的时候加了锁),然后B在执行的过程中,A再次开启了事务, 修改了 B 读取的数据,此时B执行中,就导致两次读取操作结果可能就不一致!(侧重于修改)

3. 幻读 : 事务B读取过程中,事务A进行了更新操作 ( 新增/删除/修改),没有直接影响B正在读取的数据,但是影响到了B读取的结果集,事务B两次读取到的结果集不一样,这个就是幻读!幻读相当于不可重复读的特殊情况。(侧重于新增和删除)

2.1.1 Spring 中事务的隔离级别

Spring 中事务的隔离级别有五个, MySQL 中的四个加上 DEFAULT 级别;

Isolation.DEFAULT : 以连接的数据库的事务隔离级别为主.(以数据库的全局事务隔离级别为主)

在 Spring 中如何设置事务的隔离级别>>>

@RequestMapping("/add")
@Transactional(isolation = Isolation.DEFAULT)
public int add(String username, String password) {
    // 业务逻辑
}

3. Spring 事务的传播机制

什么是事务的传播机制 ??

在回答这个问题前, 先给大家举个例子 >>

1. 抛开事务的传播机制不说, 我们的业务就像 UU 跑腿一样, 你在你家附近买了一样东西, 然后让 UU跑腿的人送到你的手里, 如果送到了就没事 (commit), 如果没送到, 就可以找到对应跑腿的人 (或店家) 进行相应的赔偿 (rollback).
2. 可是实际生活中, 在网上买东西的人相对来说要多一些, 如果你在深圳, 但是在北京的一家网店上买了东西, 这时候就不可能叫 UU跑腿来送到你手里了. 那么快递一般都要经过很多个运输点, 这多个运输点对应着多批人, 如果你的快递在中途被弄丢了, 他们应该要怎样赔偿, 由谁来赔偿, 这就需要牵扯到了多个事务之间的传播机制了.

事务的隔离级别 : 解决的是多个事务同时调用数据库的问题!

而事务的传播机制解决的是一个事务在多个节点 (方法) 中传递问题!

上述例子将的就是上图中多个方法调用的时候, 发生异常应该要怎么去处理, 这就是传播机制的意义所在!

3.1 事务传播机制的级别

事务传播机制的级别分为 7 种:

1. Propagation.REQUIRED : 默认的事务传播级别, 它表示如果当前存在事务,则加入该事务; 如果当前没有事务, 则创建⼀个新的事务.
2. Propagation.SUPPORTS : 如果当前存在事务, 则加入该事务;如果当前没有事务,则以非事务的
方式继续运行.
3. Propagation.MANDATORY : (mandatory:强制性) 如果当前存在事务,则加入该事务; 如果当
前没有事务,则抛出异常.
4. Propagation.REQUIRES_NEW:表示创建一个新的事务, 如果当前存在事务, 则把当前事务挂
起. 也就是说不管外部方法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部方法会新开
启自己的事务, 且开启的事务相互独立, 互不干扰.
5. Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起.
6. Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常.
7. Propagation.NESTED:如果当前存在事务,则 创建⼀个事务作为当前事务的嵌套事务来运行;如
果当前没有事务,则该取值等价于 Propagation.REQUIRED

这 7 中事务传播级别又可以分为三大类:

如果对于这三类事务传播机制, 不太理解的话, 下面把事务比做房子, 举一个生活中的例子 :

😁😁😁😁😁😁😁😁😁

  1. 支持当前事务 (普通伴侣)

  • REQUIRED (需要有) : 有房子就一起住, 没房子就一起赚钱买房子. (愿意陪你吃苦, 但一定要有房子)

  • SUPPORTS (可以有) : 有房子就一起住, 没房子就租房子住. (随缘的, 没房子也无所谓)

  • MANDATORY (强制有) : 有房子一起住, 没房子就分手. (不愿陪你吃苦)

  1. 不支持当前事务 (强势型伴侣)

  • REQUIRES_NEW : 不要你的房子, 咱们必须一起赚钱买房子. (看不上你的房子, 必须买新房子)

  • NOT_SUPPORTED : 不要你的房子, 咱们必须一起租房子. (不住你的房子, 必须租房子)

  • NEVER : 必须一起租房子, 你要有房子就分手. (看不上你的房子, 还得陪你环房贷)

  1. 嵌套事务 (懂事型伴侣)

  • NESTED : 有房子就以房子为根据地做点小生意, 赚钱了就继续发展, 赔钱至少还有房子; 如果没房子就一起赚钱买房子. (无风险创业, 保本懂事型伴侣)

对于上述3 类事务传播机制, 主要就是 REQUIRED (默认级别) NESTED (嵌套事务) 不好区分>>

1. REQUIRED (默认级别) : 一荣俱荣, 一损俱损. 如果当前有事务, 执行过程中, 如果抛出异常, 那么就一起回滚, 如果否则一起提交.
2. NESTED (嵌套事务) : 如果当前有事务, 创建一个事务作为当前的嵌套事务来执行, 相当于在当前事务这里有一个保存点, 如果执行过程中嵌套事务抛出异常, 就回滚到保存点, 只回滚嵌套事务(局部回滚), 不会影响上一个方法中执行的结果.

【代码实现】 针对默认级别和嵌套事务的一个代码实现 >>

下面的代码针对 add 方法和 save 方法做了一个事务默认传播级别的测验, 两个方法都是添加方法, 如果途中没有抛异常, 数据库库就会新增两条数据, 否则一条也不新增.

Controller :

    @RequestMapping("/add2")
    @Transactional(propagation = Propagation.REQUIRED)
    public int add2(String username, String password) {
        // 非空校验
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return 0;
        }
        int result = userService.add(username, password, null);
        System.out.println("添加影响行数: " + result);

        int result2 = userService.save(username, password, null);
        System.out.println("添加影响行数: " + result2);
        
        return result;
    }

Service :

save 方法中有一个除 0 异常 >>

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(String username, String password, String photo) {
        return userMapper.add(username, password, photo);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public int save(String username, String password, String photo) {
        try {
            int result = 10 / 0;
        } catch (Exception e) {
            System.out.println("ex: " + e.getMessage());
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return userMapper.add(username, password, photo);
    }

数据库当前信息 :

浏览器访问 add2 方法, 并传入参数, username=老六&password=123:

可以看到控制台打印了两次添加影响行数 : 1, 且出现一个除 0 异常, 那么是否真正插入到数据库中了, 需要查看一下数据库>>

发现并没有, 和上次查询的结果还是一样的. 所以符合我们的预期. (查看细致过程可以打断点进行调试)

【测试NESTED】

还是上述两个方法, 只不过把 Service 种的 save 方法的事务传播级别改为 NESTED.

浏览器访问 add2 方法, 并传入参数 usename=老六&passowrd=123

此时控制台依然打印了两次添加影响行数 : 1, 查询数据库验证插入情况 :

发现 add 方法的新增成功了, 而 save 方法的的新增回滚了, 也就是回滚到保存点, 这也符合我们的预期. (细致过程可以通过打断点的方式进行调试查看).


本篇文章就到这里了, 谢谢观看!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/157393.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NPDP认证|如何实现产品的组合管理?

随着企业中研发项目类型和数量的增多,涉及的范围越来越宽广,内容越来越复杂,时效性也越来越强,传统的分散式的项目管理思想已经很难满足企业的需求。 为了使技术和资源能够得到有限的配置和利用,企业就需要把各种类型的研发项日进行有机的结合。 组合管理很重要吗? 答案是勿庸…

Vue知识点

Vue基础语法 插值操作 Mustache语法 可以直接写变量&#xff0c;也可以写简单的表达式 {{firstName lastName}}’ {{firstName lastName}} {{firstName}} {{lastName}} 其他指令使用 v-noce&#xff1a; <h2 v-once>{{message}}</h2> 某些情况下&#xff…

shell 脚本实现 k8s 集群环境下指定 ns 资源的 yaml 文件备份

shell 脚本实现 k8s 集群环境下指定 ns 资源的 yaml 文件备份需求说明功能实现shell 脚本实现shell 使用方式前置工具环境安装dump-k8s-yaml.sh 使用方式输入命令 bash ./dump-k8s-yaml.shdump-k8s-yaml.sh 应用举例dump-k8s-yaml.sh 输出日志信息参考文档需求说明 在基于 k8s…

【Java寒假打卡】Java基础-字符流

【Java寒假打卡】Java基础-字符流编码表字符串中的编码和解码问题字节流读取文本文件出现乱码的原因字符流读取中文的过程字符流写出数据字符流输出数据注意事项flush和close方法字符流读取数据案例-保存键盘录入的数据字符缓冲输入流字符缓冲输出流缓冲流的特有方法案例-读取文…

【算法】广度优先遍历 (BFS)

目录1.概述2.代码实现3.应用1.概述 &#xff08;1&#xff09;广度优先遍历 (Breadth First Search)&#xff0c;又称宽度优先遍历&#xff0c;是最简便的图的搜索算法之一。 &#xff08;2&#xff09;已知图 G (V, E) 和一个源顶点 start&#xff0c;宽度优先搜索以一种系…

让我用Python自制软件,看视频畅通无阻

前言 一个账号只能登录一台设备&#xff1f;涨价就涨价&#xff0c;至少还能借借朋友的&#xff0c;谁还没几个朋友&#xff0c;搞限制登录这一出&#xff0c;瞬间不稀罕了 这个年头谁还不会点技术了&#xff0c;直接拿python自制一个可以看视频的软件… 话不多说&#xff0…

终于弄懂了 非极大抑制 NMS

NMS的作用就是有效地剔除目标检测结果中多余的检测框&#xff0c;保留最合适的检测框。 以YOLOv5为例&#xff0c;yolov5模型的输入三个feature map的集合&#xff0c;加上batch的维度&#xff0c;也就是三维张量&#xff0c;即[batch&#xff0c;(p0∗p0p1∗p1p2∗p2)∗3&…

SWC步骤

纲要&#xff1a; SWC属于AUTOSAR的Component文件夹下&#xff0c;而Composition属于Composition文件夹下。 目录 1. Import "Data Type" and "Interface" information 2. Creat Software Component(SWC) 3. Create "Port" for this SWC 4.…

nexus raw 仓库代理(node-sass离线安装node-sass: Command failed)

问题背景 内网环境中使用 node 构建项目&#xff0c;项目中依赖了 node-sass&#xff0c;环境自动下载 node-saas 失败&#xff08;内网&#xff09;。 下面是构建 node-sass 的错误代码&#xff1a; [5/5] Building fresh packages... error /workspace/node_modules/node-…

nuxt概念

文章目录前言nuxt项目结构介绍网页导航文字显示&#xff08;商标&#xff09;package.jsonnuxt.config.js路由固定路由动态路由总结前言 首先了解下B2C模式&#xff0c;分前后台&#xff0c;后台一般为管理系统&#xff0c;不需要展示给过多的用户&#xff0c;而前台需要展示给…

2023年有哪些具备潜力的加密投资标的?

随着2022年一系列的黑天鹅事件&#xff08;Terra、Luna的暴雷、FTX、Three Arrows Capital等知名加密机构的破产&#xff09;&#xff0c;加密货币总市值已经从最高点的2.9万亿美元&#xff08;2021年的11月&#xff09;&#xff0c;下降到8500亿美元&#xff08;与2021年1月的…

NKOJ P7842 疫情防控

分析 这道题的本质就是找可以使得每座城市有且仅有一条道单行路进入该市的图有什么特点; 首先,我们假设图联通,则由于每个城市只有一条单行道可以进入,即一个城市必须有且仅有一条单行道与之配对,所以这个图至少要有nnn条边,即图中必须要有环才可以满足要求! 那如果图不连通…

Java多线程之读写锁ReentrantReadWriteLock类使用

在JDK中提供了一种读写锁ReentrantReadWriteLock类&#xff0c;相比ReentrantLock类&#xff0c;使用前者可以加快运行效率。ReentrantLock类是具有完全互斥排他的效果&#xff0c;即同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务&#xff0c;这样做虽然保证了…

【UE4 第一人称射击游戏】40-改变武器的可见性

上一篇&#xff1a;【UE4 第一人称射击游戏】39-“M4A1”武器设置本篇效果&#xff1a;步骤&#xff1a;打开“Weapon_M4A1”&#xff0c;删除带有“AK47”的那个骨架网格体打开事件图表&#xff0c;将“SkeletalMesh1”拖入打开“ThirdPersonCharacter”&#xff0c;在事件图表…

Docker:独具魅力的开源容器引擎

Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中&#xff0c;然后发布到任何流行的 Linux 或 Windows操作系统的机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互之间不会有任何接口&#…

MATLAB-控制系统模型之间的转换

系统的线性时不变&#xff08;LTI&#xff09;模型有传递函数&#xff08;tf&#xff09;模型、零极点增益&#xff08;zpk)模型和状态空间(ss&#xff09;模型,它们之间可以相互转换。模型之间的转换函数可以分为以下两类。第一类是把其他类型的模型转换为函数表示的模型自身&…

PHP多进程(一)

多进程的作用是一个程序启动多个进程。 一个程序启动起来本应该是一个进程&#xff0c;但它可作为父进程启动多个子进程来一起操作 形成并发操作 pcntl是php官方的多进程扩展,只能在linux环境使用 以下所有操作请在Linux环境下操作: 先认识两个函数,下面是官方文档地址: …

铜缆测试——近端和远端串扰(NEXT和FNEXT)

如果您非常熟悉铜缆测试&#xff0c;那么很可能听说过串扰——一对或一个通道上传输的信号对另外一对或一个通道产生不良影响的现象。(杂讯) 串扰会对具体的一对导线或整根电缆形成干扰&#xff0c;导致误码或数据无法传输。例如&#xff0c;您是否曾经在电话中听到有其他人说话…

马蹄集 数组最大公约数

给定一个由N个正整数组成的数组&#xff0c;求所有数组元素的最大公约数。 格式 输入格式&#xff1a;第一行输入数组长度N,第二行输入数组元素&#xff0c;整型 空格分隔。 输出格式&#xff1a;输出整型 #include <bits/stdc.h> using namespace std;int gcd(int a…

(十八)Java的时间与日期(2)

目录 前言: 一、JDK8新增日期类 二、LocalDate&#xff0c;LocalTime,LocalDateTime 三、Instant时间戳 四、DateTimeFormatter类 五、Duration/Period类 六、ChronoUnit类 前言: JDK 8中增加了一套全新的日期时间API&#xff0c;这套API设计合理&#xff0c;是线程安全的。新的…