暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

升级Oracle导致Java程序日期截断的场景

173

点击标题下「蓝色微信名」可快速关注

同事碰到个问题,他们的数据库从Oracle 11g升级到Oracle 19c,对应Java代码要将jdbc驱动从ojdbc14.jar升级到ojdbc7.jar,发现一个date参数的问题。

(1)ojdbc14.jar:DATE > java.sql.Date,转换后不带时分秒(时分秒是0:0:0)。

(2)ojdbc6.jar及之后版本:DATE > java.sql.Timestamp 转换后带时分秒。

这就导致查询数据可能会出现问题,原来存储的日期数据格式是"2025-01-01",之前程序检索时,会使用"2025-01-01",可以找到数据,但现在用的是"2025-01-01 01:00:00"进行查找,多了时分秒自然查询为空。

如果是改程序,需要做的,就是将一个时分秒是0的参数传给Oracle,例如SimpleDateFormat("yyyy-MM-dd")。如果改SQL,可以利用trunc()函数,对时间进行截断,select ... where cdate = trunc(:1)。

但是因为这个系统有几十个微服务,SQL很多,如果一个一个改,工作量很大,还得每个业务逻辑都要排查,对方要求最好能通过配置解决,不改动Java的代码。

他们尝试了向配置中增加V8Compatible和mapdatetotimestamp,但是没生效,

    <property name="connectionProperties" value="config.decrypt=true;config.decrypt.key=${jdbc_publicKey};oracle.jdbc.V8Compatible=true"/>
    <property name="connectionProperties" value="config.decrypt=true;config.decrypt.key=${jdbc_publicKey};oracle.jdbc.mapdatetotimestamp=false"/>

    V8Compatibl参数看名字,就能猜到是为了做到兼容的,但是从文档看,应该是个过时的,可能得确认什么版本可用,

    https://docs.oracle.com/en/database/oracle/oracle-database/23/jjdbc/JDBC-reference-information.html#GUID-FCB7E652-4532-47AF-9783-B7E2B6ADA41C

    mapdatetotimestamp参数作用是可以设置Date数据类型格式,默认包含时分秒的信息,设置为false,则只显示日期部分。

    但是通过打断点,看到配置的参数是生效的,但是执行结果跟预期不一致。

    还能怎么解决?

    通过检索资料,MyBatis提供了使用自定义TypeHandler转换类型的功能,可以自己写个TypeHandler来对 DATE 类型做特殊处理:

      /\*\*
       \* Welcome to https://waylau.com
       \*/
      package com.waylau.lite.mall.type;


      import java.sql.CallableStatement;
      import java.sql.PreparedStatement;
      import java.sql.ResultSet;
      import java.sql.SQLException;
      import java.text.DateFormat;
      import java.util.Date;


      import org.apache.ibatis.type.BaseTypeHandler;
      import org.apache.ibatis.type.JdbcType;
      import org.apache.ibatis.type.MappedJdbcTypes;
      import org.apache.ibatis.type.MappedTypes;


      /\*\*
       \* 自定义TypeHandler,用于将日期转为'yyyy-MM-dd'
       \* 
       \* @since 1.0.0 20181010
       \* @author <a href="https://waylau.com">Way Lau</a>
       \*/
      @MappedJdbcTypes(JdbcType.DATE)
      @MappedTypes(Date.class)
      public class DateShortTypeHandler extends BaseTypeHandler<Date> {


      @Override
      public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType)
      throws SQLException {
      DateFormat df = DateFormat.getDateInstance();
      String dateStr = df.format(parameter);
      ps.setDate(i, java.sql.Date.valueOf(dateStr));
      }


      @Override
      public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
      java.sql.Date sqlDate = rs.getDate(columnName);
      if (sqlDate != null) {
      return new Date(sqlDate.getTime());
      }
      return null;
      }


      @Override
      public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
      java.sql.Date sqlDate = rs.getDate(columnIndex);
      if (sqlDate != null) {
      return new Date(sqlDate.getTime());
      }
      return null;
      }


      @Override
      public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
      java.sql.Date sqlDate = cs.getDate(columnIndex);
      if (sqlDate != null) {
      return new Date(sqlDate.getTime());
      }
      return null;
      }


      }




      如果是Spring项目,以下面方式进行TypeHandler的配置:

        <!-- 自定义 -->
        <!--声明TypeHandler bean-->
        <bean id="dateShortTypeHandler" class="com.waylau.lite.mall.type.DateShortTypeHandler"/>


        <!-- MyBatis 工厂 -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />


        <!--TypeHandler注入-->
        <property name="typeHandlers" ref="dateShortTypeHandler"/>
        </bean>

        如何使用TypeHandler?

        方式1:指定jdbcType为DATE

        例如,目前,项目中有如下的字段,是采用的DATE类型:

          birthday = #{birthday, jdbcType=DATE},
          方式2:指定typeHandler
          指定typeHandler为我们自定义的TypeHandler:
            birthday = #{birthday, typeHandler=com.waylau.lite.mall.type.DateShortTypeHandler},

            经过测试,这种方案可行,其实就是在MyBatis传递参数时,对日期进行截断,相当于重写了jdbcType=Date和java.util.date中间传参转换的逻辑,达到了只用时分秒的效果。

            针对这个问题,涉及到的还是日期字段的规范使用,究竟存储yyyy-mm-dd,还是yyyy-mm-dd hh24:mi:ss,要结合具体场景,选择合适的方案,当然,对代码的管理,更是很重要,否则一旦核心的开发人员变更,这段code谁都不敢改,就只能石沉大海,不能扩展,不能调整,无论是使用,还是运维,都会带来风险。

            参考资料:

            https://blog.csdn.net/2501_90255767/article/details/145217993


            如果您认为这篇文章有些帮助,还请不吝点下文章末尾的"点赞"和"在看",或者直接转发朋友圈,


            可以到各大平台找我,
            • 微信公众号:@bisal的个人杂货铺
            • 腾讯云开发者社区:@bisal的个人杂货铺
            • 头条号:@bisal的个人杂货铺
            • CSDN:@bisal
            • ITPub:@bisal
            • 墨天轮:@bisal
            • 51CTO:@bisal
            • 小红书:@bisal
            • 抖音:@bisal
            近期更新的文章:
            金融知识小科普 - 集合竞价?
            这几种违规的骑行行为你踩了么?
            从国产化到出海,机场、医院、新零售、互联网企业的数据库选型趋势|8月9日厦门见!
            中国参加世界大赛的可能性?
            Word使用默认格式的操作
            热文鉴赏:
            揭开"仿宋"和"仿宋_GB2312"的神秘面纱
            Linux的"aarch"是多了个"a"?
            中国队“自己的”世界杯
            你不知道的C罗-Siu庆祝动作
            大阪环球影城避坑指南和功略
            推荐一篇Oracle RAC Cache Fusion的经典论文
            "红警"游戏开源代码带给我们的震撼
            文章分类和索引:
            公众号1800篇文章分类和索引

            文章转载自bisal的个人杂货铺,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

            评论