java.util.Date vs java.sql.Date


Câu trả lời:


585

Xin chúc mừng, bạn đã đạt được peeve thú cưng yêu thích của tôi với JDBC: Xử lý lớp ngày.

Về cơ bản cơ sở dữ liệu thường hỗ trợ ít nhất ba dạng của trường thời gian là ngày, thời gian và dấu thời gian. Mỗi trong số chúng có một lớp tương ứng trong JDBC và mỗi lớp đều mở rộngjava.util.Date . Ngữ nghĩa nhanh của mỗi trong ba điều này là như sau:

  • java.sql.Datetương ứng với SQL DATE có nghĩa là nó lưu trữ năm, tháng và ngày trong khi giờ, phút, giây và mili giây bị bỏ qua. Ngoài ra, sql.Datekhông bị ràng buộc với múi giờ.
  • java.sql.Timetương ứng với SQL TIME và như một điều hiển nhiên, chỉ chứa thông tin về giờ, phút, giây và mili giây .
  • java.sql.Timestamptương ứng với SQL TIMESTAMP là ngày chính xác đến nano giây ( lưu ý rằng util.Datechỉ hỗ trợ mili giây! ) với độ chính xác tùy chỉnh.

Một trong những lỗi phổ biến nhất khi sử dụng trình điều khiển JDBC liên quan đến ba loại này là các loại được xử lý không chính xác. Điều này có nghĩa sql.Datelà múi giờ cụ thể, sql.Timechứa năm, tháng và ngày hiện tại et cetera et cetera.

Cuối cùng: Dùng cái nào?

Phụ thuộc vào loại SQL của trường, thực sự. PreparedStatementcó setters cho cả ba giá trị, #setDate()là một cho sql.Date, #setTime()cho sql.Time#setTimestamp()cho sql.Timestamp.

Xin lưu ý rằng nếu bạn sử dụng, ps.setObject(fieldIndex, utilDateObject);bạn thực sự có thể cung cấp bình thường util.Datecho hầu hết các trình điều khiển JDBC, thứ sẽ vui vẻ nuốt nó như thể nó là loại chính xác nhưng khi bạn yêu cầu dữ liệu sau đó, bạn có thể nhận thấy rằng bạn thực sự đang thiếu thứ.

Tôi thực sự nói rằng không nên sử dụng Ngày nào cả.

Những gì tôi đang nói là tiết kiệm mili giây / nano giây dưới dạng dài đơn giản và chuyển đổi chúng thành bất kỳ đối tượng nào bạn đang sử dụng ( phích cắm thời gian joda bắt buộc ). Một cách khó khăn có thể được thực hiện là lưu trữ thành phần ngày tháng như một thành phần dài và thời gian như một thành phần khác, ví dụ ngay bây giờ sẽ là 20100221 và 154536123. Những số ma thuật này có thể được sử dụng trong các truy vấn SQL và sẽ được chuyển từ cơ sở dữ liệu sang cơ sở dữ liệu khác và sẽ cho phép bạn tránh hoàn toàn phần này của API ngày JDBC / Java: s.


17
Câu trả lời tốt đẹp. Nhưng không lưu trữ ngày tháng vì một chút không thân thiện với DBA?
cherouvim

26
Có lẽ, tuy nhiên, các DBA thường có xu hướng lựa chọn RDBMS của họ và từ chối mọi thứ không liên quan đến RDBMS đó ( tôi đang nhìn vào bạn, những người hâm mộ Oracle ) trong khi các ứng dụng Java dự kiến ​​sẽ hoạt động với tất cả chúng. Cá nhân tôi không muốn đưa logic của mình vào DB.
Esko

2
Cột mysql của tôi là một datetime, nhưng thực hiện ps.setDate (java.sql.Date mới (myObject.getCreatedDate (). GetTime ())); Tôi đang mất phần mili giây, làm thế nào để khắc phục điều này?
Blankman

2
Để không mất mili giây của bạn: java.sql.Timestamp (producDate.getTime ())
Kieveli

3
Tôi đã đề cập rằng đó là một lỗi phổ biến mà nó là TZ cụ thể trong khi đó theo đặc điểm kỹ thuật thì không nên.
Esko

60

LATE EDIT: Bắt đầu với Java 8, bạn không nên sử dụng java.util.Datehoặc không java.sql.Datenên tránh tất cả, và thay vào đó, thích sử dụng java.timegói (dựa trên Joda) hơn là bất cứ thứ gì khác. Nếu bạn không dùng Java 8, đây là phản hồi ban đầu:


java.sql.Date- khi bạn gọi các phương thức / hàm tạo của các thư viện sử dụng nó (như JDBC). Không khác. Bạn không muốn giới thiệu các phụ thuộc vào các thư viện cơ sở dữ liệu cho các ứng dụng / mô-đun không xử lý rõ ràng với JDBC.

java.util.Date- khi sử dụng các thư viện sử dụng nó. Mặt khác, càng ít càng tốt, vì nhiều lý do:

  • Nó có thể thay đổi, có nghĩa là bạn phải tạo một bản sao phòng thủ của nó mỗi khi bạn chuyển nó đến hoặc trả lại từ một phương thức.

  • Nó không xử lý ngày rất tốt, điều mà những người lạc hậu như bạn thực sự nghĩ rằng các lớp xử lý ngày nên.

  • Bây giờ, vì juD không làm việc rất tốt, các Calendarlớp khủng khiếp đã được giới thiệu. Chúng cũng có thể thay đổi và rất tệ khi làm việc cùng, và nên tránh nếu bạn không có lựa chọn nào khác.

  • Có nhiều lựa chọn thay thế tốt hơn, như API thời gian Joda ( thậm chí có thể biến nó thành Java 7 và trở thành API xử lý ngày chính thức mới - một tìm kiếm nhanh cho biết nó sẽ không).

Nếu bạn cảm thấy quá mức cần thiết để giới thiệu một phụ thuộc mới như Joda, longthì sẽ không tệ lắm khi sử dụng cho các trường dấu thời gian trong các đối tượng, mặc dù bản thân tôi thường bọc chúng trong juD khi đi qua chúng, vì an toàn kiểu và làm tài liệu.


5
Tại sao chúng ta nên thích java.time hơn java.sql khi lưu trữ trong cơ sở dữ liệu? Tôi thực sự thú vị trong tuyên bố, nhưng tôi muốn hiểu tại sao :)
Jean-François Savard

@ Jean-FrançoisSavard Tôi hy vọng bạn đã tìm thấy câu trả lời cho câu hỏi của mình kể từ khi bạn đăng bình luận đó - nhưng đây là một câu trả lời, chỉ vì sự hoàn chỉnh: vì nó vẫn hoàn toàn ổn java.sql.Date với PreparedStatementv.v. Nhưng khi bạn chuyển nó xung quanh, hãy sử dụng cái LocalDatemà bạn chuyển đổi bằng cách sử dụng java.sql.Date.valueOfjava.sql.Date.valueOfkhi cài đặt nó, và chuyển đổi lại càng sớm càng tốt bằng cách sử dụng java.sql.Date.toLocalDate - bởi vì bạn muốn liên quan đến java.sql càng ít càng tốt và bởi vì nó có thể thay đổi được.
gustafc

19

Thời gian duy nhất để sử dụng java.sql.Datelà trong a PreparedStatement.setDate. Nếu không, sử dụng java.util.Date. Nó nói rằng ResultSet.getDatetrả về một java.sql.Datenhưng nó có thể được gán trực tiếp cho a java.util.Date.


3
Ehm, result Set # getDate () trả về sql.Date (mở rộng produc.Date).
Esko

3
@Esko - "Ehm", tôi đã sửa nó trước khi bạn nhận xét (và bị bỏ qua).
Paul Tomblin

1
Điều quan trọng cần lưu ý là lý do java.sql.Date có thể được gán cho java.util.Date là vì lớp đầu tiên là lớp con của lớp thứ hai.
dj18

2
Tại sao 'nói' rằng a java.sql.Datecó thể được gán cho một java.util.Datekhi cái trước mở rộng cái sau? Điểm bạn đang cố gắng thực hiện là gì?
Hầu tước Lorne

11

Tôi đã có cùng một vấn đề, cách dễ nhất tôi tìm thấy để chèn ngày hiện tại vào một tuyên bố đã chuẩn bị là đây:

preparedStatement.setDate(1, new java.sql.Date(new java.util.Date().getTime()));

2
Không thể có múi giờ với điều này.
erhanasikoglu

4

tl; dr

Sử dụng không.

Cũng không

java.util.Date vs java.sql.Date: khi nào nên sử dụng cái nào và tại sao?

Cả hai lớp này đều khủng khiếp, thiếu sót trong thiết kế và thực hiện. Tránh như Plague Coronavirus .

Thay vào đó sử dụng các lớp java.time , được định nghĩa trong JSR 310. Các lớp này là một khung công tác hàng đầu trong ngành để làm việc với việc xử lý thời gian. Những thay thế hoàn toàn các lớp học di sản khủng khiếp đẫm máu như Date, Calendar, SimpleDateFormat, và như vậy.

java.util.Date

Đầu tiên, java.util.Datecó nghĩa là đại diện cho một khoảnh khắc trong UTC, có nghĩa là phần bù từ UTC là 0 giờ-phút-giây.

java.time.Instant

Bây giờ được thay thế bởi java.time.Instant.

Instant instant = Instant.now() ;  // Capture the current moment as seen in UTC.

java.time.OffsetDateTime

Instantlà lớp khối xây dựng cơ bản của java.time . Để linh hoạt hơn, hãy sử dụng OffsetDateTimethiết lập thànhZoneOffset.UTC cho cùng một mục đích: thể hiện một khoảnh khắc trong UTC.

OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;

Bạn có thể gửi đối tượng này đến cơ sở dữ liệu bằng cách sử dụng PreparedStatement::setObjectvới JDBC 4.2 trở lên.

myPreparedStatement.setObject(  , odt ) ;

Lấy lại.

OffsetDateTime odt = myResultSet.getObject(  , OffsetDateTime.class ) ;

java.sql.Date

Các java.sql.Date lớp cũng là khủng khiếp và lỗi thời.

Lớp này có nghĩa là chỉ đại diện cho một ngày, không có thời gian trong ngày và không có múi giờ. Thật không may, trong một bản hack khủng khiếp của một thiết kế, lớp này kế thừa từ java.util.Dateđó đại diện cho một khoảnh khắc (một ngày với thời gian trong UTC). Vì vậy, lớp này chỉ đơn thuần là giả vờ chỉ là ngày, trong khi thực sự mang một sự bù đắp thời gian và ngầm của UTC. Điều này gây ra rất nhiều nhầm lẫn. Không bao giờ sử dụng lớp này.

java.time.LocalDate

Thay vào đó, sử dụng java.time.LocalDate để theo dõi chỉ một ngày (năm, tháng, ngày trong tháng) mà không có bất kỳ thời gian trong ngày cũng như bất kỳ múi giờ hoặc bù đắp.

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
LocalDate ld = LocalDate.now( z ) ;    // Capture the current date as seen in the wall-clock time used by the people of a particular region (a time zone).

Gửi đến cơ sở dữ liệu.

myPreparedStatement.setObject(  , ld ) ;

Lấy lại.

LocalDate ld = myResultSet.getObject(  , LocalDate.class ) ;

Bảng các loại thời gian ngày trong Java (cả di sản và hiện đại) và trong SQL tiêu chuẩn


Trong khoảng java.time

Các java.time khung được xây dựng vào Java 8 và sau đó. Những lớp học thay thế cái cũ phiền hà di sản lớp học ngày thời gian như java.util.Date, Calendar, &SimpleDateFormat .

Để tìm hiểu thêm, xem Hướng dẫn Oracle . Và tìm kiếm Stack Overflow cho nhiều ví dụ và giải thích. Đặc điểm kỹ thuật là JSR 310 .

Các Joda thời gian dự án, bây giờ trong chế độ bảo trì , khuyên chuyển đổi sang các java.time lớp.

Bạn có thể trao đổi các đối tượng java.time trực tiếp với cơ sở dữ liệu của bạn. Sử dụng trình điều khiển JDBC tương thích với JDBC 4.2 trở lên. Không cần chuỗi, không cần java.sql.*lớp.

Nơi để có được các lớp java.time?

Bảng mà thư viện java.time sẽ sử dụng với phiên bản Java hoặc Android nào


1
Upvote để thay thế tham chiếu Bệnh dịch hạch do vi rút Corona. Đáng tin cậy hơn bây giờ.
ankururag2

2

Lớp java.util.Date trong Java biểu thị một thời điểm cụ thể (e, .g., 2013 ngày 25 tháng 11 16:30:45 xuống còn mili giây), nhưng kiểu dữ liệu DATE trong DB chỉ biểu thị một ngày (ví dụ: Ngày 25 tháng 11 năm 2013). Để ngăn bạn cung cấp một đối tượng java.util.Date cho DB do nhầm lẫn, Java không cho phép bạn đặt tham số SQL thành java.util.Date trực tiếp:

PreparedStatement st = ...
java.util.Date d = ...
st.setDate(1, d); //will not work

Nhưng nó vẫn cho phép bạn thực hiện điều đó bằng vũ lực / ý định (sau đó giờ và phút sẽ bị trình điều khiển DB bỏ qua). Điều này được thực hiện với lớp java.sql.Date:

PreparedStatement st = ...
java.util.Date d = ...
st.setDate(1, new java.sql.Date(d.getTime())); //will work

Một đối tượng java.sql.Date có thể lưu trữ một khoảnh khắc trong thời gian (để dễ dàng xây dựng từ java.util.Date) nhưng sẽ đưa ra một ngoại lệ nếu bạn cố gắng yêu cầu nó trong nhiều giờ (để thực thi khái niệm trở thành một chỉ ngày). Trình điều khiển DB dự kiến ​​sẽ nhận ra lớp này và chỉ sử dụng 0 trong nhiều giờ. Thử cái này:

public static void main(String[] args) {
  java.util.Date d1 = new java.util.Date(12345);//ms since 1970 Jan 1 midnight
  java.sql.Date d2 = new java.sql.Date(12345);
  System.out.println(d1.getHours());
  System.out.println(d2.getHours());
}

0

java.util.Dateđại diện cho một thời điểm cụ thể trong thời gian với độ chính xác đến mili giây. Nó đại diện cho cả thông tin ngày và thời gian mà không có múi giờ. Lớp java.util.Date thực hiện giao diện Nối tiếp, Có thể sao chép và Có thể so sánh. Nó được kế thừa bởi java.sql.Date, java.sql.Timejava.sql.Timestampgiao diện.

java.sql.Datemở rộng lớp java.util.Date đại diện cho ngày không có thông tin về thời gian và nó chỉ được sử dụng khi làm việc với cơ sở dữ liệu. Để phù hợp với định nghĩa của NGÀY SQL, các giá trị mili giây được bao bọc bởi mộtjava.sql.Date thể hiện phải được 'chuẩn hóa' bằng cách đặt giờ, phút, giây và mili giây thành 0 trong múi giờ cụ thể mà trường hợp được liên kết.

Nó được thừa hưởng tất cả các phương pháp công khai java.util.Datenhư getHours(), getMinutes(), getSeconds(), setHours(), setMinutes(), setSeconds(). Vì java.sql.Datekhông lưu trữ thông tin thời gian, nó ghi đè tất cả các hoạt động thời gian từ java.util.Datevà tất cả các phương thức này ném java.lang.IllegalArgumentExceptionnếu được gọi là hiển nhiên từ các chi tiết thực hiện của chúng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.