JDBC和Ibatis中的Date,Time,Timestamp处理
JDBC和Ibatis中的Date,Time,Timestamp處理
November 25th, 2010西坪 Leave a commentGo to comments在此前,遇到過使用Ibatis操作Oracle時時間精度丟失的問題,昨天又遇到JDBC操作MySQL時間字段的問題,從網(wǎng)上看到各種式樣的解釋這些問題的博文/帖子,但多是霧里看花,不得要領(lǐng)。
理解JDBC中的時間類型
java.sql包中包括三個類,Date, Time, 和 Timestamp,分別用來表示日期(無時間信息,eg: YYYY-MM-DD),時間(只處理時間,無日期部分, eg: HH:MM:SS)和時間戳(精確到納秒級別)。在 它們都繼承自java.util.Date。java.sql.Date和java.sql.Time都存有一個時間域,該時間域是精確到秒級別的,但是,他們只處理日期或者時間部分。在MySQL Connector/J(v5.1.13)的實現(xiàn)中可以看到,PrepareStatement#setDate 時會將時間重新format成”yyyy-MM-dd”格式。
因為歷史遺留以及各種數(shù)據(jù)庫本身的不同,各種JDBC實現(xiàn)中留下了格式各樣的花招, 某些特殊場景下不遵照此情況可能也能工作,但不推薦這樣做。
你必須根據(jù)你的實際需要來選擇不同的類型,通常應(yīng)該選用精確度與相應(yīng)的數(shù)據(jù)庫字段類型相比相同或者更高的JDBC類型。
除此以外,還可以使用java.uitl.Date類型來處理時間。java.util.Date類型是上面各個類型的父類型,JDBC的API大都可以使用。
除此以外,在JDK1.6之前版本的構(gòu)造java.sql.{Date|Time|TimeStamp}對象時存在性能問題,尤其是在多線程環(huán)境下更嚴重。這個Bug在這里。其原因是java.util.Date的相關(guān)方法調(diào)用了TimeZone.getDefaultRef(),而此方法是同步方法注1。
MySQL的JDBC類型映射
| DATE | java.sql.Date |
| DATETIME | java.sql.Timestamp |
| TIMESTAMP[(M)] | java.sql.Timestamp |
| TIME | java.sql.Time |
| YEAR[(2|4)] | If yearIsDateType configuration property is set to false, then the returned object type is java.sql.Short. If set to true (the default) then an object of type java.sql.Date (with the date set to January 1st, at midnight). |
MySQL的DATETIME、TIMESTAMP兩種字段類型的顯著區(qū)別在于TIMESTAMP的取值在’1970-01-01 00:00:01′ UTC 和 ‘2038-01-19 03:14:07′ UTC之間。
MySQL在時間處理方面也有一個問題,當(dāng)Datetimes類型的字段的值為0000-00-00時取值方法會得到下面的異常:
這個問題的原因在于,MySQL中默認使用0000-00-00等來表示時間的特殊值(參見文檔)。而在Java中,并沒有一個合適的方式來表示這個時間(因為Java中時間軸上0是1970-01-01 00:00:00),早于這個時間的用負數(shù)表示,這個最小的負數(shù)在時間軸上是表示不出來的。Connector/J提供了一個屬性zeroDateTimeBehavior來解決此問題。
- exception (the default), which throws an SQLException with an SQLState of S1009.(默認行為)
- convertToNull, which returns NULL instead of the date.
- round, which rounds the date to the nearest closest value which is 0001-01-01.
如下所示的jdbc連接將指定該行為轉(zhuǎn)化為null值。
jdbc:mysql://localhost/myDatabase?zeroDateTimeBehavior=convertToNullOracle與JDBC之間的類型映射
| DATE | java.sql.Date |
| DATE | java.sql.Time |
| TIMESTAMP | java.sql.Timestamp |
Oracle數(shù)據(jù)庫字段類型主要有DATE、TIMESTAMP。
在9i以后、11g以前的Oracle JDBC驅(qū)動中存在一個會丟失DATE類型字段的時間信息的bug,原因是其JDBC驅(qū)動將Oracle的Date類型處理為java.sql.Date類型,這就丟失了時間部分(看來對java.sql包下三種時間類型認識不足的問題還很普遍的)。關(guān)于這個問題,這篇帖子給出了不錯的解釋(墻外),此文中的方法適用于11g JDBC以前的各版本驅(qū)動。在11g JDBC驅(qū)動中,此問題已經(jīng)解決了,Oracle 11g JDBC驅(qū)動的手冊中也給出了解釋注2。
事實上,如果是使用Ibatis,pojo屬性的類型設(shè)置為java.util.Date,確保 jdbcType不為 DATE或者TIME,則避免了這個bug。因為此時Ibatis會以java.sql.Timestamp來處理該字段。我專門對此做了驗證,點此看測試項目源碼。
Ibatis是怎么處理日期類型的
注意:本文皆基于ibatis 2.3.4.726源碼分析。不過根據(jù)我初略觀察,Ibatis3也適用,但請遇到問題時有所留意。
在此前工作中碰到Oracle中的Date字段會只剩下日期部分的數(shù)據(jù),丟失了,Google發(fā)現(xiàn)一些人的解決方法是將JDBCType指定為datetime。有人甚至自己編寫一個自定義的TypeHandler來解決這個問題。其實這完全是瞎貓撞上死耗子,那個datetime根本沒意義,卻歪打正著。一般的錯誤都是如下的配置(或者是pojo的屬性為java.sql.Date類型):
<sqlMap namespace="Info" ><resultMap id="Info" class="pojo.Info" ><result column="INFO_BEGINTIME" property="begin" jdbcType="DATE" /><result column="INFO_ENDTIME" property="end" jdbcType="DATE" /></resultMap>此時不論你pojo.Info中的字段類型(或者的javaType屬性)是java.util.Date還是java.sql.Date,最終都會丟失數(shù)據(jù)。實際上,在pojo.Info中的字段類型(或者javaType屬性)為java.util.Date時,如果指定jdbcType為DATE或TIME(區(qū)分大小寫),則將分別得到DateOnlyTypeHandler或TimeOnlyTypeHandler。如果你不指定jdbcType,或者指定一個錯誤的值,例如datetime,或者xxxx等,都會得到DateTypeHandler(日期時間都會處理)。
如果pojo.Info中的屬性類型(或者配置中的javaType屬性)是java.sql.Date,則選擇處理handler類時不會使用jdbcType,而是根據(jù)屬性類型得到SqlDateTypeHandler(只包含日期)。
在Ibatis的手冊中,指出來jdbcType一般情況下是不用于判斷處理方式的注3。大部分情況下是依據(jù)javaType或者pojo屬性類型來判斷處理方式,在少部分無法根據(jù)pojo屬性類型判斷的情況下才使用。如果你用java.util.Date對應(yīng)到MySQL,則就是這種少數(shù)情況之一。因為MySQL可以將多個數(shù)據(jù)庫字段類型映射到j(luò)ava.util.Date,所以需要指定是DATE還是TIME(必須是大寫),如果不指定則默認是完整的日期時間(此時按JDBC的timestamp操作)。
針對ibatis對時間的處理,我寫了個測試,點此看測試代碼。
對于Ibatis操作Date/Time/DateTime,總結(jié)如下:
- 將pojo的屬性類型設(shè)置為java.sql.Date(或java.sql.Time, java.sql.Timestamp),此時會嚴格遵循這三種類型的語義。但此方法因存在前文中提到的性能問題,在JDK1.6以前的JDK版本中能少使用就少使用。
- 如果你想在pojo中使用java.util.Date, 則要注意:
- 完整的日期時間,要確保jdbcType為空,或為DATE,TIME以外的值
- 只需要時間,要指定jdbcType=”TIME”
- 只需要日期,要指定jdbcType=”DATE”
注釋
注釋1:Use oracle.sql.DATE or oracle.sql.TIMESTAMP rather than
java.sql.Date or java.sql.Timestamp if you are using JDK 1.5 or earlier versions or require maximum performance. You may also use the oracle.sql data type if you want to read many date values or compute or display only a small percentage. Due to a bug in all versions of Java prior to JDK 1.6, construction of java.lang.Date and java.lang.Timestamp objects is slow, especially in multithreaded environments. This bug is fixed in JDK 1.6.
注釋2:Mapping SQL DATE Data type to Java Oracle Database 8i and earlier versions did not support TIMESTAMP data, but Oracle DATE data used to have a time component as an extension to the SQL standard. So, Oracle Database 8i and earlier versions of JDBC drivers mapped oracle.sql.DATE to java.sql.Timestamp to preserve the time component. Starting with Oracle Database 9.0.1, TIMESTAMP support was included and 9i JDBC drivers started mapping oracle.sql.DATE to java.sql.Date. This mapping was incorrect as it truncated the time component of Oracle DATE data. To overcome this problem, Oracle Database 11.1 introduces a new flag mapDateToTimestamp. The default value of this flag is true, which means that by default the drivers will correctly map oracle.sql.DATE to java.sql.Timestamp, retaining the time information. If you still want the incorrect but 10g compatible oracle.sql.DATE to java.sql.Date mapping, then you can get it by setting the value of mapDateToTimestamp flag to false.
注釋3:Ibatis 2的手冊中給出的jdbcType屬性的解釋:屬性jdbcType用于顯式地指定給本屬性(property)賦值的數(shù)據(jù)庫字段的數(shù)據(jù)類型。對于某些特定的操作,如果不指定字段的數(shù)據(jù)類型,某些JDBC Driver無法識別字段的數(shù)據(jù)類型。一個很好的例子是PreparedStatement.setNull(int parameterIndex, int sqlType)方法,要求指定數(shù)據(jù)類型。如果不指定數(shù)據(jù)類型,某些Driver可能指定為Types.Other或Types.Null。但是,不能保證所有的Driver都表現(xiàn)一致。對于這種情況,SQL Map API允許使用parameterMap子元素parameter的jdbcType屬性指定數(shù)據(jù)類型。
正常情況下,只有當(dāng)字段可以為NULL時才需要jdbcType屬性。另一需要指定jdbcType屬性的情況是字段類型為日期時間類型的情況。因為Java只有一個Date類型(java.util.Date),而大多數(shù)SQL數(shù)據(jù)庫有多個-通常至少有3種。因此,需要指定字段類型是DATE還是DATETIME。
屬性jdbcType可以是JDBC Types類中定義的任意參數(shù)的字符串值。雖然如此,還是有某些類型不支持(即BLOB)。本節(jié)的稍后部分會說明架構(gòu)支持的數(shù)據(jù)類型。
注意!大多數(shù)JDBC Driver只有在字段可以為NULL時需要指定jdbcType屬性。因此,對于這些Driver,只是在字段可以為NULL時才需要指定type屬性。
注意!當(dāng)使用Oracle Driver時,如果沒有給可以為NULL的字段指定jdbcType屬性,當(dāng)試圖給這些字段賦值NULL時,會出現(xiàn)“Invalid column type”錯誤。
參考資料
- JSR-000221 JDBC 4.0
- Ibatis Developer Guide
- MySQL Connector/J
總結(jié)
以上是生活随笔為你收集整理的JDBC和Ibatis中的Date,Time,Timestamp处理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Tortoise SVN 版本控制常用操
- 下一篇: 如何提高效率