同事碰到个问题,他们的数据库从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 2018年10月10日
\* @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>
例如,目前,项目中有如下的字段,是采用的DATE类型:
birthday = #{birthday, jdbcType=DATE},
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