<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    MySQL驅(qū)動(dòng)中關(guān)于時(shí)間的坑

    共 3432字,需瀏覽 7分鐘

     ·

    2022-02-28 10:11

    MySQL驅(qū)動(dòng)中關(guān)于時(shí)間的坑

    背景:MySQL 8.0數(shù)據(jù)庫(kù);

    最近在做一個(gè)小框架,因?yàn)楸旧肀容^精簡(jiǎn),就沒(méi)有引入太多依賴,直接用了JDBC來(lái)操作數(shù)據(jù)庫(kù),因?yàn)槲业谋碇杏幸粋€(gè)datetime類型的字段,對(duì)應(yīng)的Java代碼中使用的是java.time.LocalDateTime,在處理這個(gè)日期字段的時(shí)候,就遇到了一個(gè)有趣的問(wèn)題;

    在我的數(shù)據(jù)庫(kù)表建好后,在Java中使用JDBC原生API實(shí)現(xiàn)了一個(gè)repository,包含一些數(shù)據(jù)庫(kù)的操作,因?yàn)榇a中有java.time.LocalDateTime字段,在使用java.sql.PreparedStatement的時(shí)候不確認(rèn)java.sql.PreparedStatement.setObject(int, java.lang.Object)方法接收java.time.LocalDateTime類型的入?yún)⒑笫欠窨梢哉_處理,就寫(xiě)了一小段測(cè)試用例來(lái)測(cè)試,核心代碼如下:

    preparedStatement.setObject(1, LocalDateTime.now());

    一行簡(jiǎn)單的JDBC調(diào)用設(shè)置參數(shù),參數(shù)類型是LocalDateTime,最終發(fā)現(xiàn)沒(méi)有報(bào)錯(cuò),如果一切OK,那么事情到這里就結(jié)束了,但是.......






    當(dāng)我到數(shù)據(jù)庫(kù)查看數(shù)據(jù)的時(shí)候,發(fā)現(xiàn)日期竟然不對(duì),日期存到數(shù)據(jù)庫(kù)后竟然少了14個(gè)小時(shí),整整14個(gè)小時(shí),當(dāng)時(shí)的我真的是....








    還好,機(jī)智的我馬上就醒悟過(guò)來(lái)了,這肯定是時(shí)區(qū)問(wèn)題,我們現(xiàn)在是東8區(qū),比UTC時(shí)間是快8小時(shí),存儲(chǔ)到數(shù)據(jù)庫(kù)后時(shí)間比我們現(xiàn)在慢了14個(gè)小時(shí),也就是比UTC時(shí)間慢了6個(gè)小時(shí),而這正好處于CST時(shí)區(qū),CST時(shí)區(qū)中的代表地區(qū)就是北美中部,難道我的服務(wù)器人被搬到了北美?








    那當(dāng)然不是了,應(yīng)該是MySQL的時(shí)區(qū)設(shè)置有問(wèn)題,MySQL數(shù)據(jù)庫(kù)是美國(guó)開(kāi)發(fā)的軟件,默認(rèn)情況下時(shí)區(qū)是CST很符合邏輯,而在我登錄到服務(wù)器上之后,查看服務(wù)器時(shí)區(qū),服務(wù)器的時(shí)區(qū)是對(duì)的,然后又查看了MySQL的默認(rèn)時(shí)區(qū),果然,我發(fā)現(xiàn)MySQL的默認(rèn)時(shí)區(qū)設(shè)置是CST,使用如下代碼將其修改為UTC+8后就正常了:

    set global time_zone = '+8:00';
    flush privileges;

    # 使用上述命令修改完畢后使用下面的命令查看修改是否成功
    show variables where Variable_name like "%time_zone%";

    如果事情到這里就結(jié)束了,那這也太簡(jiǎn)單了,而且跟標(biāo)題似乎沒(méi)有半毛錢(qián)關(guān)系呀?別急,其實(shí)在執(zhí)行上一步修改時(shí)區(qū)之前,機(jī)智的我忽然想起來(lái)我還有另外一個(gè)項(xiàng)目,也用到了java.time.LocalDateTime,并且使用的測(cè)試數(shù)據(jù)庫(kù)與我當(dāng)前這個(gè)項(xiàng)目是同一個(gè),但是那個(gè)項(xiàng)目從來(lái)沒(méi)有發(fā)現(xiàn)過(guò)這個(gè)問(wèn)題,難道是因?yàn)槟莻€(gè)項(xiàng)目中使用了mybatis,mybatis對(duì)此做了什么特殊處理?想到這里,筆者趕緊去翻了下mybatis相關(guān)源碼,發(fā)現(xiàn)mybatis對(duì)于LocalDateTime也是直接調(diào)用了java.sql.PreparedStatement.setObject(int, java.lang.Object)方法將LocalDateTime傳給了MySQL驅(qū)動(dòng),沒(méi)有做任何處理,那怎么就沒(méi)問(wèn)題呢?

    既然想不明白,那就debug一下吧,看下在我們JDBC版本的項(xiàng)目中debug LocalDateTime參數(shù)是如何傳輸?shù)組ySQL的,最終debug到如下關(guān)鍵源碼:

    代碼在com.mysql.cj.ClientPreparedQueryBindings.setTimestamp(int, java.sql.Timestamp)處:

    // PS:這里x是Timestamp類型的,在上層調(diào)用時(shí)將LocalDateTime轉(zhuǎn)換為了Timestamp類型,這個(gè)不影響我們的分析
    setTimestamp(parameterIndex, x, this.session.getServerSession().getDefaultTimeZone());

    在上述代碼中this.session.getServerSession().getDefaultTimeZone()最終返回了MySQL數(shù)據(jù)庫(kù)中的默認(rèn)時(shí)區(qū),因?yàn)槲覀僊ySQL中默認(rèn)時(shí)區(qū)是CST,所以這里也符合我們觀察到的現(xiàn)象,最終保存到數(shù)據(jù)庫(kù)的日期比實(shí)際我們當(dāng)前的東8區(qū)慢14個(gè)小時(shí),但是為什么使用mybatis的另外一個(gè)項(xiàng)目就沒(méi)有問(wèn)題呢,要知道這兩個(gè)項(xiàng)目使用的測(cè)試數(shù)據(jù)庫(kù)是同一個(gè)的,這就很奇怪,到這里,我決定再在另一個(gè)項(xiàng)目中也debug一下,看下問(wèn)題到底出在了哪里;

    然后我就在使用mybatis版本的項(xiàng)目中做了一個(gè)插入測(cè)試,然后debug看是怎么處理的,最終debug到如下源碼處:

    代碼在com.mysql.cj.ClientPreparedQueryBindings#setLocalDateTime處,關(guān)鍵代碼如下:

    formatter = new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd HH:mm:ss").appendFraction(ChronoField.NANO_OF_SECOND, 0, 6, true)
    .toFormatter();
    sb = new StringBuilder("'");
    sb.append(x.format(formatter));
    sb.append("'");
    setValue(parameterIndex, sb.toString(), targetMysqlType);

    可以看到這里并沒(méi)有使用MySQL數(shù)據(jù)庫(kù)的時(shí)區(qū)信息,所以肯定不會(huì)出錯(cuò),但是這怎么跟沒(méi)有使用mybatis的項(xiàng)目行為不一致呢?







    就在這時(shí),我忽然瞥見(jiàn)了IDEA上方tab標(biāo)簽上有兩個(gè)ClientPreparedQueryBindings文件,如下所示(這里是給其他不必要的文件關(guān)了后的效果,為了更好看出效果):






    然后我瞬間明白了,這個(gè)問(wèn)題與mybatis沒(méi)有任何關(guān)系,是兩個(gè)項(xiàng)目的MySQL驅(qū)動(dòng)不一致導(dǎo)致的,使用mybatis的項(xiàng)目使用的MySQL驅(qū)動(dòng)版本是8.0.26,其中日期處理沒(méi)有去使用MySQL服務(wù)端的時(shí)區(qū),所以使用mybatis的項(xiàng)目存儲(chǔ)到MySQL中的日期是沒(méi)問(wèn)題的,而我當(dāng)前這個(gè)直接使用JDBC項(xiàng)目的MySQL驅(qū)動(dòng)版本是8.0.11,在這個(gè)版本的驅(qū)動(dòng)中對(duì)于LocalDateTime的處理使用了MySQL服務(wù)端的時(shí)區(qū),這就導(dǎo)致了存儲(chǔ)到MySQL中日期變了;

    最后,這個(gè)問(wèn)題到這里有兩個(gè)最簡(jiǎn)單的方案解決,一個(gè)是修改數(shù)據(jù)庫(kù)默認(rèn)時(shí)區(qū),還有一個(gè)就是升級(jí)使用MySQL8.0.26版本的驅(qū)動(dòng),而我使用了第三種更復(fù)雜一點(diǎn)兒的方案:

    1、將數(shù)據(jù)庫(kù)默認(rèn)時(shí)區(qū)修改為東8區(qū);
    2、將項(xiàng)目的MySQL驅(qū)動(dòng)升級(jí)為8.0.26版本;
    3、對(duì)LocalDateTime提前處理,在我自己的代碼中將其格式化為字符串,然后調(diào)用`java.sql.PreparedStatement.setObject(int, java.lang.Object)`將其設(shè)置進(jìn)去;

    為什么使用這種方案呢?

    • 1、將數(shù)據(jù)庫(kù)默認(rèn)時(shí)區(qū)修改為東8區(qū)防止其他人出相同問(wèn)題,也防止在其他地方有用到這個(gè)時(shí)區(qū)數(shù)據(jù)時(shí)出錯(cuò);

    • 2、將項(xiàng)目的MySQL驅(qū)動(dòng)升級(jí)為8.0.26是因?yàn)槠渌?xiàng)目已經(jīng)在用這個(gè)版本的驅(qū)動(dòng)了,盡量保持一致防止再出一些其他兼容性問(wèn)題;

    • 3、對(duì)LocalDateTime提前處理,在自己的代碼中將其格式化為字符串而不是等待MySQL驅(qū)動(dòng)去格式化,防止未來(lái)某天升級(jí)MySQL驅(qū)動(dòng)時(shí)MySQL驅(qū)動(dòng)行為再次修改導(dǎo)致出現(xiàn)問(wèn)題;

    最后,建議大家沒(méi)有必要不要隨意升級(jí)jar包,特別是這種涉及底層驅(qū)動(dòng)的jar包,防止出現(xiàn)兼容性問(wèn)題;

    聯(lián)系我

    • 作者微信:JoeKerouac

    • 微信公眾號(hào)(文章會(huì)第一時(shí)間更新到公眾號(hào),如果搜不出來(lái)可能是改名字了,加微信即可=_=|):代碼深度研究院

    • GitHub:https://github.com/JoeKerouac


    瀏覽 84
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)
    評(píng)論
    圖片
    表情
    推薦
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    午夜成人精品偷拍在线 | 国产操九千| 午夜999| 男人v天堂| 搔视频网址在线观看 |