本次分析了mybatis的mapper XML文件,sql的where子句中使用java.util.Date
进行比较进行分析。
假设使用的是以下sql语句。
select * from xxx where create_time > '2024-06-20 20:38:38'
在mybatis中,会将java.util.Date
对象的值转为java.sql.Timestamp
,之后在mybatis取值的时候,会调用java.sql.Timestamp.toString()
方法获取字符串值(也就是'2024-06-20 20:38:38')。
带有日期对比字符串的sql通过jdbc传到MySQL服务器端,此时,mysql会怎么处理这种对比呢?答案就是**字符串会隐式的转换create_time
值所代表的MySQL类型,之后进行对比。
分析只关注核心位置,不对整个流程进行分析(你断点此位置一般就能看到)。
下面就着重分析**Mybatis的XML文件,对java.util.Date
类型的处理方式是怎样的?
首先进入到org.apache.ibatis.type.DateTypeHandler#setNonNullParameter
方法,这里没什么说明的。
在org.apache.ibatis.logging.jdbc.PreparedStatementLogger#invoke
方法中,
我们会看到SET_METHODS方法是个Set集合。
经过debug,我们会进到org.apache.ibatis.logging.jdbc.BaseJdbcLogger
类的位置。
getParameterValueString方法用于打印输出值和值的类型。
这一行是关键语句return method.invoke(statement, params);
,
接着进入这行代码,会执行到com.alibaba.druid.pool.DruidPooledPreparedStatement#setTimestamp(int, java.sql.Timestamp)
,
在debug进入 stmt.setTimestamp(parameterIndex, x);
,
会到com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl#setTimestamp(int, java.sql.Timestamp)
方法位置处,这里的选中这一行chain.preparedStatement_setTimestamp(this, parameterIndex, x);
是核心,
之后会到com.mysql.cj.jdbc.ClientPreparedStatement#setTimestamp(int, java.sql.Timestamp)
方法位置处。
最后会到com.mysql.cj.NativeQueryBindings#setTimestamp
,这里就是给PreparedStatement对象的sql占位符绑定值得位置了。
可以看到,到最后这个值还是java.sql.Timestamp类型的。
至于PreparedStatement之后怎么处理我觉的就可以不用关注了,因为PreparedStatement配套的提供了setTimestamp方法,就说明内部已有支持的转换方式,而且MySQL驱动本身自身也支持setTimestamp。
MySQL官方文档的对于DATETIME和TIMESTAMP类型进行对比的资料。
https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html
https://dev.mysql.com/doc/refman/8.0/en/type-conversion.html
如果其中一个参数是 TIMESTAMP 或 DATETIME 列,而另一个参数是常量,那么在进行比较之前,会将常量转换为时间戳。这样做是为了对 ODBC 更友好。但 IN() 的参数不会这样做。为了安全起见,在进行比较时应始终使用完整的日期时间、日期或时间字符串。例如,在使用带有日期或时间值的 BETWEEN 时,为了获得最佳结果,应使用 CAST() 将值明确转换为所需的数据类型。
来自一个或多个表的单行子查询不视为常量。例如,如果子查询返回一个要与 DATETIME 值比较的整数,那么比较将以两个整数的形式进行。整数不会转换为时间值。要将操作数作为 DATETIME 值进行比较,请使用 CAST() 将子查询值显式转换为 DATETIME 值。
其他资料
MySQL关于日期格式化的相关函数。
CAST转换类型
The DATE, DATETIME, and TIMESTAMP Types