Câu trả lời:
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.Date
tươ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.Date
không bị ràng buộc với múi giờ.java.sql.Time
tươ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.Timestamp
tương ứng với SQL TIMESTAMP là ngày chính xác đến nano giây ( lưu ý rằng util.Date
chỉ 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.Date
là múi giờ cụ thể, sql.Time
chứa năm, tháng và ngày hiện tại et cetera et cetera.
Phụ thuộc vào loại SQL của trường, thực sự. PreparedStatement
có setters cho cả ba giá trị, #setDate()
là một cho sql.Date
, #setTime()
cho sql.Time
và #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.Date
cho 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ứ.
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.
LATE EDIT: Bắt đầu với Java 8, bạn không nên sử dụng java.util.Date
hoặc không java.sql.Date
nên tránh tất cả, và thay vào đó, thích sử dụng java.time
gó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 Calendar
lớ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, long
thì 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.
java.sql.Date
với PreparedStatement
v.v. Nhưng khi bạn chuyển nó xung quanh, hãy sử dụng cái LocalDate
mà bạn chuyển đổi bằng cách sử dụng java.sql.Date.valueOf
và java.sql.Date.valueOf
khi 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.
Thời gian duy nhất để sử dụng java.sql.Date
là trong a PreparedStatement.setDate
. Nếu không, sử dụng java.util.Date
. Nó nói rằng ResultSet.getDate
trả về một java.sql.Date
nhưng nó có thể được gán trực tiếp cho a java.util.Date
.
java.sql.Date
có thể được gán cho một java.util.Date
khi cái trước mở rộng cái sau? Điểm bạn đang cố gắng thực hiện là gì?
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()));
Sử dụng không.
java.time.Instant
thay thế java.util.Date
java.time.LocalDate
thay thế java.sql.Date
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.Date
có 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
Instant
là 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 OffsetDateTime
thiế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::setObject
vớ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 ) ;
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?
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());
}
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.Time
và java.sql.Timestamp
giao diện.
java.sql.Date
mở 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.Date
như getHours()
, getMinutes()
, getSeconds()
, setHours()
, setMinutes()
, setSeconds()
. Vì java.sql.Date
khô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.Date
và tất cả các phương thức này ném java.lang.IllegalArgumentException
nếu được gọi là hiển nhiên từ các chi tiết thực hiện của chúng.