`

Query.setDate和Query.setTimestamp区别,附赠log4jdbc的bug

阅读更多

前提

应用中使用hibernate+jtds(1.2.7)+log4jdbc-log4j2-jdbc3-1.16

前景

同事使用hibernate的hql查询当天的业务数据,其中 begin=2017/12/06 00:00:00, end=2017/12/06 23:59:59,具体如下:

  1. //begin 2017/12/06 00:00:00.000
  2. //end 2017/12/06 23:59:59
  3. publicList<FeedbackInfoBean> getFeedbackByFyid(finalInteger fyid,finalDate begin,finalDate end){
  4. Object r = dzjzFoundationDao.getHibernateTemplate().execute(newHibernateCallback(){
  5. @Override
  6. publicObject doInHibernate(Session session)throwsHibernateException,SQLException{
  7. String hql =newString(
  8. "SELECT new com.thunisoft.fy.dzjz.httpapi.prescanner.timer.bean.FeedbackInfoBean ( clfx.CBhDsf as cBhDsf, fd.NAjbs as nAjbs, "+
  9. "fd.NJbfy as nJbfy, fd.NAjlb as nAjlb, jz.CMc as cAh, clfx.dtScanTimesstamp as dScanTimesstamp, clfx.CSerialNum as cSerialNum )"+
  10. " FROM TYwgyDzjzPrescannerClfx clfx, TYwgyDzjzPrescannerInfoFeedback fd, TYwgyDzjzJz jz"+
  11. " WHERE fd.CBhClfx = clfx.CBh AND clfx.CBhAj = jz.CBhAj AND clfx.NState = 1 AND clfx.NJbfy = :JBFY"+
  12. " AND clfx.dtScanTimesstamp > (:start) AND clfx.dtScanTimesstamp < (:end)"
  13. );
  14. Query q = session.createQuery(hql);
  15. try{
  16. q.setParameter("JBFY", fyid);
  17. q.setDate("start", begin);
  18. q.setDate("end", end);
  19. }catch(ParseException e){
  20. thrownewSQLException(String.format("%s or %s is not in a valid date format", begin, end), e);
  21. }
  22. return q.list();
  23. }
  24. });
  25. return(List<FeedbackInfoBean>) r;
  26. }
  27. }

但是,程序就是没有查到数据,jdbc日志输出sql如下:

  1. select tywgydzjzp0_.DT_SCANTIMESSTAMP,tywgydzjzp0_.C_BH_DSF as col_0_0_, tywgydzjzp1_.N_AJBS as col_1_0_, tywgydzjzp1_.N_JBFY as col_2_0_, tywgydzjzp1_.N_AJLB as col_3_0_, tywgydzjzj2_.C_MC
  2. as col_4_0_, tywgydzjzp0_.DT_SCANTIMESSTAMP as col_5_0_, tywgydzjzp0_.C_SERIALNUM as col_6_0_ from YWST.dbo.T_YWGY_DZJZ_PRESCANNER_CLFX tywgydzjzp0_, YWST.dbo.T_YWGY_DZJZ_PRESCANNER_FEEDBCK
  3. tywgydzjzp1_, YWST.dbo.T_YWGY_DZJZ_JZ tywgydzjzj2_ where tywgydzjzp1_.C_BH_CLFX=tywgydzjzp0_.C_BH and tywgydzjzp0_.C_BH_AJ=tywgydzjzj2_.C_BH_AJ and tywgydzjzp0_.N_STATE=1
  4. and tywgydzjzp0_.N_JBFY=2400and tywgydzjzp0_.DT_SCANTIMESSTAMP>'12/06/2017 00:00:00.000'and tywgydzjzp0_.DT_SCANTIMESSTAMP<'12/06/2017 23:59:59.000'

通过sql查询结果是有数据的:

猜想

为此,猜测,内存hibernate或jtds解析参数时,end对应的值为 2017/12/06 00:00:00,即与start对应的值相同,可能出现查询结果为空。

验证

AbstractQueryImpl.setDate 方法具体实现如下:

  1. /**
  2. * AbstractQueryImpl.java
  3. */
  4. publicQuery setDate(String name,Date date){
  5. setParameter(name, date,Hibernate.DATE);
  6. returnthis;
  7. }
  8. /**
  9. * Hibernate.java
  10. * Hibernate <tt>date</tt> type.
  11. */
  12. publicstaticfinalNullableType DATE =newDateType();

DateType中值替换参数时调用的set方法代码如下:

  1. /**
  2. * 至于啥时候调用,就是hibernate bind的时候会用,具体得自己see一眼
  3. * bind
  4. * 注意:此处的sqlDate类型是java.sql.Date
  5. */
  6. publicvoid set(PreparedStatement st,Object value,int index)throwsSQLException{
  7. Date sqlDate;
  8. if( value instanceofDate){
  9. sqlDate =(Date) value;
  10. }
  11. else{
  12. sqlDate =newDate(((java.util.Date) value ).getTime());
  13. }
  14. st.setDate(index, sqlDate);
  15. }

而真正使用的是jtds的JtdsPreparedStatement.setParameter(中间跳过JtdsPreparedStatement.setDate和log4jdbc.PreparedStatementSpy.setDate)有关键代码如下:

  1. if(x instanceofDate){
  2. x =newDateTime((Date) x);
  3. }elseif(x instanceofTime){
  4. x =newDateTime((Time) x);
  5. }elseif(x instanceofTimestamp){
  6. x =newDateTime((Timestamp) x);
  7. }

所以,最后Date或Timestamp对象的还是会转换成jtds的DateTime对象。对于,Date类型,初始化时就没有初始化time部分数据。

  1. DateTime(Date d)throwsSQLException{
  2. dateValue = d;
  3. GregorianCalendar cal =newGregorianCalendar();
  4. cal.setTime(d);
  5. if(cal.get(Calendar.ERA)!=GregorianCalendar.AD)
  6. thrownewSQLException(Messages.get("error.datetime.range.era"),"22007");
  7. year =(short)cal.get(Calendar.YEAR);
  8. month =(short)(cal.get(Calendar.MONTH)+1);
  9. day =(short)cal.get(Calendar.DAY_OF_MONTH);
  10. //请关注到这里,time是没被使用的,时 分 秒都是0
  11. hour =0;
  12. minute =0;
  13. second =0;
  14. millis =0;
  15. packDate();
  16. time = TIME_NOT_USED;
  17. unpacked =true;
  18. }
  19. //but timestamp with 时分秒
  20. DateTime(Timestamp ts)throwsSQLException{
  21. tsValue = ts;
  22. GregorianCalendar cal =newGregorianCalendar();
  23. cal.setTime(ts);
  24. if(cal.get(Calendar.ERA)!=GregorianCalendar.AD)
  25. thrownewSQLException(Messages.get("error.datetime.range.era"),"22007");
  26. if(!Driver.JDBC3){
  27. // Not Running under 1.4 so need to add milliseconds
  28. cal.set(Calendar.MILLISECOND,
  29. ts.getNanos()/1000000);
  30. }
  31. year =(short)cal.get(Calendar.YEAR);
  32. month =(short)(cal.get(Calendar.MONTH)+1);
  33. day =(short)cal.get(Calendar.DAY_OF_MONTH);
  34. hour =(short)cal.get(Calendar.HOUR_OF_DAY);
  35. minute =(short)cal.get(Calendar.MINUTE);
  36. second =(short)cal.get(Calendar.SECOND);
  37. millis =(short)cal.get(Calendar.MILLISECOND);
  38. packDate();
  39. packTime();
  40. unpacked =true;
  41. }

而只有原本是Timestamp类型的才会使得发送到数据库的查询中包含time部分。

hibernate有一个TimeStampType类。(并附上AbstractQueryImpl.setTimestamp代码)

  1. /**
  2. * TimeStampType类
  3. * Hibernate <tt>timestamp</tt> type.
  4. */
  5. publicvoid set(PreparedStatement st,Object value,int index)throwsSQLException{
  6. Timestamp ts;
  7. if(value instanceofTimestamp){
  8. ts =(Timestamp) value;
  9. }
  10. else{
  11. ts =newTimestamp(((java.util.Date) value ).getTime());
  12. }
  13. st.setTimestamp(index, ts);
  14. }
  15. /**
  16. * AbstractQueryImpl类
  17. * Hibernate <tt>timestamp</tt> type.
  18. */
  19. publicQuery setTimestamp(int position,Date date){
  20. setParameter(position, date,Hibernate.TIMESTAMP);
  21. returnthis;
  22. }
  23. /**
  24. * Hibernate类
  25. * Hibernate <tt>timestamp</tt> type.
  26. */
  27. publicstaticfinalNullableType TIMESTAMP =newTimestampType();

so:

如果仅精确到日,注意请使用Query.setDate,就算给的Date有时分秒的值

如果要精确到时分秒的,注意请使用Query.setTimestamp

注意: 版本是jtds-1.2.7,没有验证其他版本是否存在这个差异,不过想想也觉得应该有这个差异,这个差异是正常的:)

有同学会注意到一个问题:为啥jdbc sql日志却输出的是时间格式?请看如下摘取代码:

  1. //PreparedStatementSpy类 仅贴关键代码
  2. protectedvoid argTraceSet(int i,String typeHelper,Object arg){
  3. // 替换的预编译的参数值
  4. synchronized(argTrace)
  5. {
  6. try
  7. {
  8. arg = argTrace.get(argIdx);
  9. }
  10. catch(IndexOutOfBoundsException e)
  11. {
  12. arg ="?";
  13. }
  14. }
  15. if(arg ==null)
  16. {
  17. arg ="?";
  18. }
  19. argIdx++;
  20. dumpSql.append(sql.substring(lastPos,Qpos));// dump segment of sql up to question mark.
  21. lastPos =Qpos+1;
  22. Qpos= sql.indexOf('?', lastPos);
  23. dumpSql.append(arg);
  24. }
  25. //替换的对象
  26. publicvoid setDate(int parameterIndex,Date x)throwsSQLException
  27. {
  28. String methodCall ="setDate("+ parameterIndex +", "+ x +")";
  29. argTraceSet(parameterIndex,"(Date)", x);
  30. try
  31. {
  32. realPreparedStatement.setDate(parameterIndex, x);
  33. }
  34. catch(SQLException s)
  35. {
  36. reportException(methodCall, s);
  37. throw s;
  38. }
  39. reportReturn(methodCall);
  40. }
  41. protectedvoid argTraceSet(int i,String typeHelper,Object arg)
  42. {
  43. String tracedArg;
  44. try
  45. {
  46. //注意这行是关键
  47. tracedArg = rdbmsSpecifics.formatParameterObject(arg);
  48. }
  49. catch(Throwable t)
  50. {
  51. // rdbmsSpecifics should NEVER EVER throw an exception!!
  52. // but just in case it does, we trap it.
  53. log.debug("rdbmsSpecifics threw an exception while trying to format a "+
  54. "parameter object ["+ arg +"] this is very bad!!! ("+
  55. t.getMessage()+")");
  56. // backup - so that at least we won't harm the application using us
  57. tracedArg = arg==null?"null":arg.toString();
  58. }
  59. i--;// make the index 0 based
  60. synchronized(argTrace)
  61. {
  62. // if an object is being inserted out of sequence, fill up missing values with null...
  63. while(i >= argTrace.size())
  64. {
  65. argTrace.add(argTrace.size(),null);
  66. }
  67. if(!showTypeHelp)
  68. {
  69. argTrace.set(i, tracedArg);
  70. }
  71. else
  72. {
  73. argTrace.set(i, typeHelper + tracedArg);
  74. }
  75. }
  76. }
  77. //RdbmsSpecifics类
  78. protectedstaticfinalString dateFormat ="MM/dd/yyyy HH:mm:ss.SSS";
  79. publicString formatParameterObject(Object object)
  80. {
  81. if(object ==null)
  82. {
  83. return"NULL";
  84. }
  85. if(object instanceofString)
  86. {
  87. return"'"+ escapeString((String)object)+"'";
  88. }
  89. elseif(object instanceofDate)
  90. {
  91. return"'"+newSimpleDateFormat(dateFormat).format(object)+"'";
  92. }
  93. elseif(object instanceofBoolean)
  94. {
  95. returnProperties.isDumpBooleanAsTrueFalse()?
  96. ((Boolean)object).booleanValue()?"true":"false"
  97. :((Boolean)object).booleanValue()?"1":"0";
  98. }
  99. else
  100. {
  101. return object.toString();
  102. }
  103. }

结论:因为log4jdbc认为参数对象如果是Date类型的都会按照格式:MM/dd/yyyy HH:mm:ss.SSS 做format,不区分Date和Timestamp,导致拼出的sql带有时分秒精度

如何解决log4jdbc打出的sql在Date处理和真实sql不一致

方案一

找到了log4jdbc-log4j2的github,他们目前最新版本也是1.16。所以,在他们的issues上提了问题,看看他们给不给答复吧! issues link

方案二

逛逛新的log4jdbc开源组件,待续

分享到:
评论

相关推荐

    第10章 JDBC-课后习题1

    5. 错误,`PreparedStatement`接口中的`setDate()`方法可以设置日期内容,但参数`Date`的类型必须是`java.sql.Date`,而不是`java.util.Date`。 【选择题】 1. A,JDBC驱动器API是接口,而JDBC驱动器是实现类。 2....

    关于PreparedStatement插入Date类型值的方法.txt

    当我们在应用程序中需要向数据库中插入日期(`Date`类型)时,就需要用到`PreparedStatement`中的`setDate()`或`setTimestamp()`方法。本文将详细介绍这两种方法的使用方式及注意事项。 #### 一、`...

    简介JavaScript中的setDate()方法的使用

    javascript Date.setDate()方法按照本地时间设置月份的某一天,在指定的日期。 语法 Date.setDate( dayValue ) 下面是参数的详细信息: dayValue : 从1到31的整数,代表一个月份中某一天。 返回值: NA 例子: ...

    J2EE-JDBC学习笔记

    使用`DATE`, `TIME`, `TIMESTAMP`类型进行存储,通过`PreparedStatement`的`setDate()`, `setTime()`, `setTimestamp()`方法插入数据,以及`ResultSet`的相应`getDate()`, `getTime()`, `getTimestamp()`方法读取...

    js用Date对象的setDate()函数对日期进行加减操作

    但是涉及到每个月天数的判断,如果是2月份的话,还要涉及到闰年的判断,有些复杂,应用过程中总是出现问题,于是查了下资料,以在某个日期上加减天数来说,其实只要调用Date对象的setDate()函数就可以了,具体方法...

    HIBERNATE_QUERY

    ### HIBERNATE_QUERY知识点详解 #### 一、概述 Hibernate作为一款强大的对象关系映射(ORM)框架,为Java开发者提供了一套高效且简洁的方式来处理数据库操作。它支持多种查询方式,包括面向对象的查询语言(HQL)...

    软件开发中日期在数据库的处理

    pstmt.setTimestamp(2, new Timestamp(new Date().getTime())); // ... } ``` 在Oracle数据库中,处理日期的方式类似,但它有自己的函数来获取当前日期,如`SYSDATE`。Oracle同样有DATE、TIMESTAMP等类型。假设...

    MyCalendar.zip

    开发者可以通过ContentResolver与ContentProvider接口进行交互,读取和写入日历数据。了解这个框架是构建日历应用的基础。 2. **日历视图展示** 在"MyCalendar"项目中,核心在于创建一个自定义的日历视图控件。这...

    PreparedStatement 向数据库插入时间方法

    关键在于正确地将`java.util.Date`转换为`java.sql.Date`或`java.sql.Timestamp`,以便能够被`setDate()`和`setTimestamp()`方法所接受。这种方法不仅可以避免类型不匹配的错误,还可以确保数据被准确无误地插入到...

    JDBC说明文档

    - **常用日期格式**:在Java中处理日期时,经常使用的有`java.util.Date`、`java.sql.Date`和`java.sql.Timestamp`等。 - **转换**:从字符串到`java.util.Date`,再到`java.sql.Date`,最后可以通过`...

    java源代码公司管理系统

    import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import com.wsy.model.Back; import com.wsy.model.BookInfo; import com.wsy.model.BookType; import com.wsy.model.Borrow; ...

    mysql+jdbc+jsp+Hibernate3.2+tomcattomcat5.028成功测试

    tomcat中配jdbc就不用说了, &lt;br&gt; &lt;br&gt;4. Event.java 文件 &lt;br&gt;package events; &lt;br&gt;import java.util.Date; &lt;br&gt;public class Event { private Long id; &lt;br&gt; private String title;...

    java.util.Date与java.sql.Date互转及字符串转换为日期时间格式[文].pdf

    两者之间可以进行互转,但需要注意它们的区别和使用场景。 一、java.util.Date与java.sql.Date的互转 可以使用以下方式将java.util.Date转换为java.sql.Date: java.util.Date utilDate = new java.util.Date(); ...

    js获取本月的第一个星期一

    Date对象是JavaScript中的内置对象,用于处理日期和时间。我们可以通过构造函数创建一个Date对象,或者使用`new Date()`创建当前日期。例如: ```javascript let currentDate = new Date(); ``` 要获取本月的第一...

    Java Calendar手机上期选择-日历实现.rar

     Display.getDisplay(this).setCurrent(form); // 显示表单form  }  // 重载抽象类MIDlet的抽象方法destroyApp()  protected void destroyApp(boolean unconditional) { }  // 重载抽象类MIDlet的抽象方法...

    Java习题九.docx

    5. 答案:ABC,Statement 接口中常用的执行 SQL 语句的方法有 execute(String sql) 用于执行各种 SQL 语句、executeUpdate(String sql) 用于执行 SQL 中的 query、insert、update 和 delete 语句、executeQuery...

    Spring JDBC的使用方法详解

    Spring JDBC是Spring框架中的一部分,提供了对数据库的访问和操作。它提供了一个简单、灵活、可扩展的方式来访问数据库。下面将详细介绍Spring JDBC的使用方法详解。 为什么使用Spring提供的JDBC封装? Spring提供...

    Java中Date,Calendar,Timestamp的区别以及相互转换与使用

    - 在数据库操作中,如 `ResultSet` 的 `getTimestamp()` 和 `PreparedStatement` 的 `setTimestamp()` 方法通常使用 `Timestamp`,因为它能获取更多的日期时间信息。 5. **Oracle数据库的日期和时间类型** - ...

Global site tag (gtag.js) - Google Analytics