Jawaban:
Selamat, Anda telah memukul hewan peliharaan kesayangan saya dengan JDBC: Penanganan kelas tanggal.
Pada dasarnya database biasanya mendukung setidaknya tiga bentuk bidang datetime yaitu tanggal, waktu dan cap waktu. Masing-masing memiliki kelas yang sesuai di JDBC dan masing-masing dari mereka memperpanjangjava.util.Date
. Semantik cepat dari masing-masing dari ketiganya adalah sebagai berikut:
java.sql.Date
berkorespondensi dengan SQL DATE yang artinya menyimpan bertahun-tahun, bulan dan hari sementara jam, menit, detik dan milidetik diabaikan. Selain sql.Date
itu tidak terikat dengan zona waktu.java.sql.Time
sesuai dengan SQL TIME dan sebagaimana seharusnya jelas, hanya berisi informasi tentang jam, menit, detik, dan milidetik .java.sql.Timestamp
sesuai dengan SQL TIMESTAMP yang merupakan tanggal yang tepat untuk nanodetik ( perhatikan bahwa util.Date
hanya mendukung milidetik! ) dengan presisi yang dapat disesuaikan.Salah satu bug yang paling umum ketika menggunakan driver JDBC dalam kaitannya dengan ketiga jenis ini adalah bahwa jenis-jenisnya ditangani secara tidak benar. Ini berarti sql.Date
spesifik zona waktu, sql.Time
berisi tahun saat ini, bulan dan hari dan lain-lain.
Tergantung pada tipe SQL dari lapangan, sungguh. PreparedStatement
memiliki setter untuk ketiga nilai, #setDate()
menjadi satu untuk sql.Date
, #setTime()
untuk sql.Time
dan #setTimestamp()
untuk sql.Timestamp
.
Perhatikan bahwa jika Anda menggunakan, ps.setObject(fieldIndex, utilDateObject);
Anda benar-benar dapat memberikan yang normal util.Date
untuk sebagian besar driver JDBC yang dengan senang hati akan melahapnya seolah-olah itu adalah tipe yang benar tetapi ketika Anda meminta data setelah itu, Anda mungkin melihat bahwa Anda benar-benar kehilangan barang.
Apa yang saya katakan bahwa menyimpan milidetik / nanodetik sebagai panjang biasa dan mengubahnya menjadi benda apa pun yang Anda gunakan ( plug joda-waktu wajib ). Salah satu cara hacky yang dapat dilakukan adalah dengan menyimpan komponen tanggal sebagai satu komponen panjang dan waktu sebagai yang lain, misalnya saat ini adalah 20100221 dan 154536123. Angka ajaib ini dapat digunakan dalam query SQL dan akan portabel dari database ke yang lain dan akan membiarkan Anda menghindari bagian dari JDBC / Java Date API ini: sepenuhnya.
EDIT TERAKHIR: Dimulai dengan Java 8 Anda sebaiknya tidak menggunakan java.util.Date
atau java.sql.Date
jika Anda bisa menghindarinya, dan sebaliknya lebih memilih menggunakan java.time
paket (berdasarkan Joda) daripada yang lainnya. Jika Anda tidak menggunakan Java 8, inilah respons aslinya:
java.sql.Date
- ketika Anda memanggil metode / konstruktor perpustakaan yang menggunakannya (seperti JDBC). Tidak sebaliknya. Anda tidak ingin memperkenalkan dependensi ke pustaka database untuk aplikasi / modul yang tidak secara eksplisit berurusan dengan JDBC.
java.util.Date
- saat menggunakan perpustakaan yang menggunakannya. Kalau tidak, sesedikit mungkin, karena beberapa alasan:
Ini bisa berubah, yang berarti Anda harus membuat salinan defensif setiap kali Anda meneruskannya atau mengembalikannya dari suatu metode.
Ini tidak menangani kencan dengan sangat baik, yang membuat orang-orang seperti Anda mundur benar-benar berpikir kelas penanganan kencan seharusnya.
Sekarang, karena JuD tidak melakukan pekerjaannya dengan baik, Calendar
kelas-kelas yang mengerikan diperkenalkan. Mereka juga bisa berubah, dan mengerikan untuk bekerja dengan, dan harus dihindari jika Anda tidak punya pilihan.
Ada alternatif yang lebih baik, seperti Joda Time API ( yang bahkan mungkin membuatnya menjadi Java 7 dan menjadi tanggal resmi penanganan API baru - pencarian cepat mengatakan itu tidak akan).
Jika Anda merasa sulit untuk memperkenalkan ketergantungan baru seperti Joda, long
s tidak terlalu buruk untuk digunakan untuk bidang timestamp pada objek, meskipun saya sendiri biasanya membungkusnya dalam juD ketika melewati mereka di sekitar, untuk keamanan jenis dan sebagai dokumentasi.
java.sql.Date
dengan PreparedStatement
dll! Tetapi ketika Anda menyebarkannya, gunakan LocalDate
yang Anda konversi menggunakan java.sql.Date.valueOf
dan java.sql.Date.valueOf
ketika mengaturnya, dan mengubahnya kembali sedini mungkin menggunakan java.sql.Date.toLocalDate
- lagi, karena Anda ingin melibatkan java.sql sesedikit mungkin, dan karena itu bisa berubah.
Satu-satunya waktu untuk menggunakan java.sql.Date
adalah a PreparedStatement.setDate
. Kalau tidak, gunakan java.util.Date
. Ia memberi tahu bahwa ResultSet.getDate
mengembalikan a java.sql.Date
tetapi dapat ditugaskan langsung ke a java.util.Date
.
java.sql.Date
dapat ditugaskan ke suatu java.util.Date
ketika yang pertama memperpanjang yang terakhir? Apa yang ingin Anda sampaikan?
Saya memiliki masalah yang sama, cara termudah yang saya temukan untuk memasukkan tanggal saat ini ke dalam pernyataan yang disiapkan adalah yang ini:
preparedStatement.setDate(1, new java.sql.Date(new java.util.Date().getTime()));
Gunakan keduanya.
java.time.Instant
menggantikan java.util.Date
java.time.LocalDate
menggantikan java.sql.Date
java.util.Date vs java.sql.Date: kapan harus menggunakan yang mana dan mengapa?
Kedua kelas ini mengerikan, cacat dalam desain dan implementasi. Hindari seperti Wabah Coronavirus .
Alih-alih menggunakan kelas java.time , didefinisikan dalam JSR 310. Kelas-kelas ini adalah kerangka kerja industri terkemuka untuk bekerja dengan penanganan tanggal-waktu. Menggantikan ini seluruhnya kelas warisan mengerikan berdarah seperti Date
, Calendar
, SimpleDateFormat
, dan semacamnya.
java.util.Date
Yang pertama, java.util.Date
dimaksudkan untuk mewakili momen dalam UTC, yang berarti offset dari UTC dari nol jam-menit-detik.
java.time.Instant
Sekarang digantikan oleh java.time.Instant
.
Instant instant = Instant.now() ; // Capture the current moment as seen in UTC.
java.time.OffsetDateTime
Instant
adalah kelas dasar-blok bangunan java.time . Untuk fleksibilitas lebih, gunakan OffsetDateTime
set ke ZoneOffset.UTC
untuk tujuan yang sama: mewakili momen di UTC.
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
Anda dapat mengirim objek ini ke database dengan menggunakan PreparedStatement::setObject
dengan JDBC 4.2 atau yang lebih baru.
myPreparedStatement.setObject( … , odt ) ;
Ambil kembali.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
java.sql.Date
The java.sql.Date
kelas juga mengerikan dan usang.
Kelas ini dimaksudkan untuk mewakili tanggal saja, tanpa waktu-hari dan tanpa zona waktu. Sayangnya, dalam peretasan desain yang mengerikan, kelas ini mewarisi dari java.util.Date
mana yang mewakili momen (tanggal dengan waktu sehari di UTC). Jadi kelas ini hanya berpura-pura sebagai tanggal saja, sementara sebenarnya membawa waktu of-hari dan offset implisit UTC. Ini menyebabkan banyak kebingungan. Jangan pernah gunakan kelas ini.
java.time.LocalDate
Alih-alih, gunakan java.time.LocalDate
untuk melacak hanya tanggal (tahun, bulan, hari-bulan) tanpa batas waktu atau zona waktu atau offset apa pun.
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).
Kirim ke database.
myPreparedStatement.setObject( … , ld ) ;
Ambil kembali.
LocalDate ld = myResultSet.getObject( … , LocalDate.class ) ;
The java.time kerangka dibangun ke Jawa 8 dan kemudian. Kelas-kelas ini menggantikan tua merepotkan warisan kelas tanggal-waktu seperti java.util.Date
, Calendar
, & SimpleDateFormat
.
Untuk mempelajari lebih lanjut, lihat Tutorial Oracle . Dan cari Stack Overflow untuk banyak contoh dan penjelasan. Spesifikasi adalah JSR 310 .
Proyek Joda-Time , sekarang dalam mode pemeliharaan , menyarankan migrasi ke kelas java.time .
Anda dapat bertukar objek java.time secara langsung dengan database Anda. Gunakan driver JDBC yang sesuai dengan JDBC 4.2 atau yang lebih baru. Tidak perlu untuk string, tidak perlujava.sql.*
kelas.
Di mana mendapatkan kelas java.time?
Kelas java.util.Date di Java mewakili momen tertentu dalam waktu (e, .g., 2013 25 Nov 16:30:45 hingga milidetik), tetapi tipe data DATE dalam DB hanya mewakili tanggal (misalnya, 2013 25 November). Untuk mencegah Anda menyediakan objek java.util.Date ke DB karena kesalahan, Java tidak memungkinkan Anda untuk mengatur parameter SQL ke java.util.Date secara langsung:
PreparedStatement st = ...
java.util.Date d = ...
st.setDate(1, d); //will not work
Tetapi masih memungkinkan Anda untuk melakukannya dengan paksa / niat (maka jam dan menit akan diabaikan oleh driver DB). Ini dilakukan dengan kelas java.sql.Date:
PreparedStatement st = ...
java.util.Date d = ...
st.setDate(1, new java.sql.Date(d.getTime())); //will work
Objek java.sql.Date dapat menyimpan momen dalam waktu (sehingga mudah dibangun dari java.util.Date) tetapi akan melempar pengecualian jika Anda mencoba untuk meminta waktu (untuk menegakkan konsepnya menjadi hanya tanggal). Driver DB diharapkan untuk mengenali kelas ini dan hanya menggunakan 0 selama berjam-jam. Coba ini:
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
mewakili instan spesifik dalam waktu dengan presisi milidetik. Ini mewakili informasi tanggal dan waktu tanpa zona waktu. Kelas java.util.Date mengimplementasikan antarmuka Serializable, Cloneable dan Sebanding. Ini diwarisi oleh java.sql.Date
, java.sql.Time
dan java.sql.Timestamp
antarmuka.
java.sql.Date
memperluas kelas java.util.Date yang mewakili tanggal tanpa informasi waktu dan itu harus digunakan hanya ketika berhadapan dengan database. Untuk menyesuaikan dengan definisi SQL DATE, nilai milidetik yang dibungkus oleh sebuah java.sql.Date
instance harus 'dinormalisasi' dengan menetapkan jam, menit, detik, dan milidetik ke nol dalam zona waktu tertentu yang terkait dengan instance tersebut.
Itu mewarisi semua metode umum dari java.util.Date
seperti getHours()
, getMinutes()
, getSeconds()
, setHours()
, setMinutes()
, setSeconds()
. Karena java.sql.Date
tidak menyimpan informasi waktu, itu menimpa semua operasi waktu dari java.util.Date
dan semua metode ini membuang java.lang.IllegalArgumentException
jika dipanggil sebagai bukti dari rincian implementasi mereka.