Java业务开发常见错误100例
朱晔
贝壳金服资深架构师
立即订阅
6081 人已学习
课程目录
已完结 39 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 业务代码真的会有这么多坑?
免费
代码篇 (20讲)
01 | 使用了并发工具类库,线程安全就高枕无忧了吗?
02 | 代码加锁:不要让“锁”事成为烦心事
03 | 线程池:业务代码最常用也最容易犯错的组件
04 | 连接池:别让连接池帮了倒忙
05 | HTTP调用:你考虑到超时、重试、并发了吗?
06 | 20%的业务代码的Spring声明式事务,可能都没处理正确
07 | 数据库索引:索引并不是万能药
08 | 判等问题:程序里如何确定你就是你?
09 | 数值计算:注意精度、舍入和溢出问题
10 | 集合类:坑满地的List列表操作
11 | 空值处理:分不清楚的null和恼人的空指针
12 | 异常处理:别让自己在出问题的时候变为瞎子
13 | 日志:日志记录真没你想象的那么简单
14 | 文件IO:实现高效正确的文件读写并非易事
15 | 序列化:一来一回你还是原来的你吗?
16 | 用好Java 8的日期时间类,少踩一些“老三样”的坑
17 | 别以为“自动挡”就不可能出现OOM
18 | 当反射、注解和泛型遇到OOP时,会有哪些坑?
19 | Spring框架:IoC和AOP是扩展的核心
20 | Spring框架:框架帮我们做了很多工作也带来了复杂度
设计篇 (6讲)
21 | 代码重复:搞定代码重复的三个绝招
22 | 接口设计:系统间对话的语言,一定要统一
23 | 缓存设计:缓存可以锦上添花也可以落井下石
24 | 业务代码写完,就意味着生产就绪了?
25 | 异步处理好用,但非常容易用错
26 | 数据存储:NoSQL与RDBMS如何取长补短、相辅相成?
安全篇 (4讲)
27 | 数据源头:任何客户端的东西都不可信任
28 | 安全兜底:涉及钱时,必须考虑防刷、限量和防重
29 | 数据和代码:数据就是数据,代码就是代码
30 | 如何正确保存和传输敏感数据?
不定期加餐 (6讲)
加餐1 | 带你吃透课程中Java 8的那些重要知识点(一)
加餐2 | 带你吃透课程中Java 8的那些重要知识点(二)
加餐3 | 定位应用问题,排错套路很重要
加餐4 | 分析定位Java问题,一定要用好这些工具(一)
加餐5 | 分析定位Java问题,一定要用好这些工具(二)
加餐6 | 这15年来,我是如何在工作中学习技术和英语的?
结束语 (2讲)
结束语 | 写代码时,如何才能尽量避免踩坑?
结课测试 | 关于Java业务开发的100个常见错误,你都明白其中缘由了吗?
Java业务开发常见错误100例
15
15
1.0x
00:00/00:00
登录|注册

16 | 用好Java 8的日期时间类,少踩一些“老三样”的坑

朱晔 2020-04-16
你好,我是朱晔。今天,我来和你说说恼人的时间错乱问题。
在 Java 8 之前,我们处理日期时间需求时,使用 Date、Calender 和 SimpleDateFormat,来声明时间戳、使用日历处理日期和格式化解析日期时间。但是,这些类的 API 的缺点比较明显,比如可读性差、易用性差、使用起来冗余繁琐,还有线程安全问题。
因此,Java 8 推出了新的日期时间类。每一个类功能明确清晰、类之间协作简单、API 定义清晰不踩坑,API 功能强大无需借助外部工具类即可完成操作,并且线程安全。
但是,Java 8 刚推出的时候,诸如序列化、数据访问等类库都还不支持 Java 8 的日期时间类型,需要在新老类中来回转换。比如,在业务逻辑层使用 LocalDateTime,存入数据库或者返回前端的时候还要切换回 Date。因此,很多同学还是选择使用老的日期时间类。
现在几年时间过去了,几乎所有的类库都支持了新日期时间类型,使用起来也不会有来回切换等问题了。但,很多代码中因为还是用的遗留的日期时间类,因此出现了很多时间错乱的错误实践。比如,试图通过随意修改时区,使读取到的数据匹配当前时钟;再比如,试图直接对读取到的数据做加、减几个小时的操作,来“修正数据”。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java业务开发常见错误100例》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(18)

  • 👽
    思考题1:
    根本原因在于toString的源代码:
    sb.append(zi.getDisplayName(date.isDaylightTime(), TimeZone.SHORT, Locale.US)); // zzz 这一行。
    Date的toString实际上,是新建一个StringBuilder,然后根据Date对象里的年月日周,将其格式化。
    格式化过程中,似乎会获取系统默认的时区,如果取不到系统默认时区,就使用GMT。
    为了测试我的猜想,我尝试更改本机时区,结果输出:
    Wed Apr 15 21:40:10 EDT 2020
    相比较正常自动时区
    Thu Apr 16 09:41:31 CST 2020
    基本与我的猜测一致。



    2020-04-16
    8
  • Darren
    试着回到下问题:
    第一个:
    Date的toString()方法处理的,同String中有BaseCalendar.Date date = normalize();
    而normalize中进行这样处理cdate = (BaseCalendar.Date) cal.getCalendarDate(fastTime,TimeZone.getDefaultRef();
    因此其实是获取当前的默认时区的。
    第二个:
    从下面几个维度进行区分:
    占用空间:datetime:8字节。timestamp 4字节
    表示范围:datetime '1000-01-01 00:00:00.000000' to '9999-12-31 23:59:59.999999'
    timestamp '1970-01-01 00:00:01.000000' to '2038-01-19 03:14:07.999999'
    时区:timestamp 只占 4 个字节,而且是以utc的格式储存, 它会自动检索当前时区并进行转换。
    datetime以 8 个字节储存,不会进行时区的检索.
    也就是说,对于timestamp来说,如果储存时的时区和检索时的时区不一样,那么拿出来的数据也不一样。对于datetime来说,存什么拿到的就是什么。
    更新:timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
    这个特性是自动初始化和自动更新(Automatic Initialization and Updating)。
    自动更新指的是如果修改了其它字段,则该字段的值将自动更新为当前系统时间。
    它与“explicit_defaults_for_timestamp”参数有关。

    By default, the first TIMESTAMP column has both DEFAULT CURRENT_TIMESTAMP and ON UPDATE CURRENT_TIMESTAMP if neither is specified explicitly。
    很多时候,这并不是我们想要的,如何禁用呢?
    1. 将“explicit_defaults_for_timestamp”的值设置为ON。
    2. “explicit_defaults_for_timestamp”的值依旧是OFF,也有两种方法可以禁用
          1> 用DEFAULT子句该该列指定一个默认值
          2> 为该列指定NULL属性。


        在MySQL 5.6.5版本之前,Automatic Initialization and Updating只适用于TIMESTAMP,而且一张表中,最多允许一个TIMESTAMP字段采用该特性。从MySQL 5.6.5开始,Automatic Initialization and Updating同时适用于TIMESTAMP和DATETIME,且不限制数量。

    作者回复: 很全面

    2020-04-16
    6
  • 👽
    对于时间,我个人的理解和目前的使用经验是——能用时间戳就用时间戳。
    时间戳有几个优势:
    1,便于比较和排序,无论数据库还是后台业务中都是如此。
    2,也比较便于计算,虽然文中提到了Long的问题,但是,我认为L的问题的根本在于Long类型的理解,不是时间戳这个业务的问题。对Long的基础比较好了之后,也就足以应对计算中的问题了。
    3,多端统一,现在提供给前端的很多服务都采用直接转换好年月日的字符串了,但是有时候,前端需要对时间进行比较的时候还是需要额外转化,会很麻烦。而且不利于格式化。时间戳的话就避免了这个问题,自己进行计算,自己格式化。前端自己随便玩。

    作者回复: 保存和传输用时间戳的确比较方便,用带有时区的字面量时间表示字符串,还有格式不统一的问题

    2020-04-16
    1
    5
  • pedro
    第一个问题,虽然 Date 本质是一个时间戳没有时区的概念,但是在 toString 的时候为了可读性会推测当前时区,如果得不到就会使用 GMT。

    作者回复: 是

    2020-04-16
    4
  • 👽
    思考题2:
    说实话,数据库相关知识是我的弱项。
    查了一下,大概是TIMESTAMP包含了时区信息,而DATETIME不包含。另外有一个是,我印象中5.7之后的mysql版本,最多只能有一个TIMESTAMP的字段。这也算是个区别吧。

    作者回复: https://dev.mysql.com/doc/refman/5.6/en/timestamp-initialization.html

    你说的应该是『As of MySQL 5.6.5, TIMESTAMP and DATETIME columns can be automatically initializated and updated to the current date and time (that is, the current timestamp). Before 5.6.5, this is true only for TIMESTAMP, and for at most one TIMESTAMP column per table. The following notes first describe automatic initialization and updating for MySQL 5.6.5 and up, then the differences for versions preceding 5.6.5.』这个问题,其实并不是指只能有一个TIMESTAMP 列,而是只能有一个TIMESTAMP列使用CURRENT_TIMESTAMP来初始化或自动更新时间戳

    2020-04-16
    2
  • Monday
    今天踩坑private static simpledateformat,高并发下出现numberformatexception错误。单笔数据重放没有问题。初看到这个异常一脸懵,完全联系不到是simpledateformat的坑。
    后面突然想起老师这篇文章。。。完全是坑二的重现。因为是jdk6所以选择了去掉static解决,每次都会新建一个对象

    作者回复: 好吧,不过ThreadLocal可以用的

    2020-06-03
    1
  • eazonshaw
    问题二:
    首先,为了让docker容器的时间格式和宿主机一致,可以在environment中添加TZ: Asia/Shanghai。
    实验发现,切换mysql的TIME_ZONE到“america/new_york”后,发现datetime格式字段不发生变化,而timestamp格式会换算成纽约时区时间,所以timestamp格式的日期保存了时区信息,而datetime没有。
    感觉在业务场景中,有可能出现服务器或容器系统时间并未设置时区,导致保存的数据并不是我们想要的。因此,是不是更推荐使用timestamp格式来保存日期,避免这种情况发生呢?

    作者回复: TIMESTAMP保存的时候根据当前时区转换为UTC,查询的时候再根据当前时区从UTC转回来,而DATETIME就是一个死的字符串时间(仅仅对MySQL本身而言)表示。有关mysql时间类型可以详细看一下这个ppt
    http://cdn.oreillystatic.com/en/assets/1/event/36/Time%20Zones%20and%20MySQL%20Presentation.pdf

    如果你的项目有国际化需求,推荐使用时间戳,并且需要确保你的应用服务器和数据库服务器设置了正确的匹配当地时区的时区配置(其实,即便你的项目没有国际化需求,设置正确的需求,至少是应用服务器和数据库服务器设置一致的时区,也是需要的)

    2020-04-16
    1
  • pedro
    第二个问题,timestamp 会把传入的时间转化为 UTC 即时间戳进行存储,而 datetime 也直接将传入的时间存储。

    作者回复: 大方向对,可以再考虑一下我们如何选择

    2020-04-16
    1
  • lee
    老师好,上海时区和纽约时区下,格式化同一个时间串得到的当前时差有时候是12小时,有时候是13小时呢,把stringDate改成2020-05-02 22:00:00得到的相差12小时
    String stringDate = "2020-05-02 22:00:00";
    SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //同一Date
    Date date = inputFormat.parse(stringDate);
    //默认时区格式化输出:
    System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss Z]").format(date));
    //纽约时区格式化输出
    TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
    System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss Z]").format(date));

    作者回复: 夏令时的关系,有关夏令时你可以网上搜索一些资料

    System.out.println(TimeZone.getDefault().inDaylightTime(date));

    2020-05-11
  • grandcool
    老师,为啥“不同的本地时间可能对应同一个 UTC”啊?

    作者回复: 因为不同时区的人都可以有自己的本地/当地时间,对应UTC是一个

    2020-04-30
  • 俊柱
    老师,我有一个问题困扰已久,希望能够解答一下,目前我们对外输出的 API ,时间都是时间戳的形式, 内部系统的交互,时间也是时间戳的形式。 那我用 Instant 去映射数据库的 Timestamp/DateTime 字段,会不会更好? 否则的话,需要在多处都要注意 LocalDateTime 和 时间戳的相互转换 (比如 redis 的序列化反序列化,json 的序列化、反序列化)

    作者回复: 当然可以使用Instant映射,参考https://i.stack.imgur.com/idLPT.png

    2020-04-22
  • Michael
    private static final DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    **
         * 获取本周一开始时间
         * @return
         */
        public static String getFirstTimeByWeek() {
            LocalDateTime currentTime = LocalDateTime.now();
            LocalDateTime weekStartTime = currentTime.with(TemporalAdjusters.previous(DayOfWeek.SUNDAY)).plusDays(1).truncatedTo(ChronoUnit.DAYS);
            //求出这个日期所在周的星期一
            return weekStartTime.format(df);
        }


        /**
         * 获取本周一截止时间
         * @return
         */
        public static String getLastTimeByWeek() {
            LocalDateTime currentTime = LocalDateTime.now();
            LocalDateTime weekEndTime = currentTime.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).minusDays(1);
            //求出这个日期所在周的星期一
            return weekEndTime.format(df);
        }


        /**
         * 获取当月第一天其实时间
         * @return
         */
        public static String getFirstTimeByMonth() {
            LocalDateTime currentTime = LocalDateTime.now();
            LocalDateTime monthStartDate = currentTime.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS);
            return monthStartDate.format(df);
        }

        /**
         * 获取当月最后一天截止时间
         * @return
         */
        public static String getLastTimeByMonth() {
            LocalDateTime currentTime = LocalDateTime.now();
            LocalDateTime monthEndDate = currentTime.plusMonths(1L).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS).plus(-1L, ChronoUnit.MILLIS);
            return monthEndDate.format(df);
        }

    获取本周日截止时间的时候总是获取不到时分秒的那部分:23:59:59

    作者回复: 不太明白啥意思,代码中没有贴出获取本周日截止时间的方法,获取到下周一-1s即可

    2020-04-19
    2
  • Wiggle Wiggle
    我在外企,公司大部分数据用的洛杉矶时间(带了夏令时),处理数据的时候要面对各种不带时区的string或datetime,无比酸爽

    作者回复: 处理数据的时候要面对各种不带时区的string或datetime。。。这显然是会有问题的

    2020-04-18
  • wang
    感觉LocalDateTime像是存了当前时区的zoneddatetime?

    作者回复: 它就是一个时间表示,没有保存时区,理解为存了当前时区的ZonedDateTime不太准确

    2020-04-18
  • 刘大明
    思考题1.是toString 方法里面if (zi != null) {
                sb.append(zi.getDisplayName(date.isDaylightTime(), TimeZone.SHORT, Locale.US)); // zzz
            } else {
                sb.append("GMT");
            }
    加了这个输出结果

    作者回复: 是

    2020-04-16
  • jiarupc
    在日期上,我没踩过坑。

    首先,我做的都是国内项目,不会出现时区的问题;
    其次,对时间的应用真不多,就是时间的保存、格式化、比较;

    看来我还是太狭隘了,最近三节课说到的坑都没见过。周末得多跑几遍老师的代码,加深感受才行。

    作者回复: 没关系没踩坑是好事情,先了解一下

    2020-04-16
  • Planeswalker23
    我虽然现在用的是jdk1.8,但对于日期的操作一般还是习惯于用Date或long,以后可以尝试用LocalDateTime等类。

    思考题1
    Date#toString方法中,会将当前时间转化为BaseCalendar.Date类,这个类有一个Zone属性,在toString的时候会被追加到字符串中(默认是GMT)

    思考题2
    datetime占用8字节,不受时区影响,表示范围'1000-01-01 00:00:00' to '9999-12-31 23:59:59'
    timestamp占用4字节,受时区影响,表示范围'1970-01-01 00:00:01' to '2038-01-19 03:14:07',若插入null会自动转化为当前时间

    作者回复: 👍🏻

    2020-04-16
  • 俊柱
    老师,映射表的bean,若数据库字段为 Timestamp,那 java 的字段应该设为 ZonedDateTime 最为合理吗? 因为我看网上很多人都是用 LocalDateTime 进行映射

    作者回复: 大多数时候项目没有全球化需求映射到本地时区即可,可以使用LocalDateTime。

    不过我们说datetime不包含时区,是固定的时间表示仅仅是指MySQL本身。使用timestamp,需要考虑Java进程的时区和MySQL连接的时区。而使用datetime类型,则只需要考虑Java进程的时区(因为MySQL datetime没有时区信息了,JDBC时间戳转换成MySQL datetime,会根据MySQL的serverTimezone做一次转换)

    具体你可能需要自己多做一些实验来理解,几个层面
    1、mysql本身对于datetime和timestamp的区别
    2、java应用和mysql交互时的关系

    2020-04-16
收起评论
18
返回
顶部