Рекомендация: Как проверить Java для конкретной java.util.Calendar / Date в SQL.DATE - PullRequest
4 голосов
/ 13 марта 2009

Это то, с чем я борюсь со вчерашнего дня.

У меня есть встречи, чтобы сохранить в базе данных. Они состоят из даты и времени, например:

01.02.1970 14:00

(немецкий формат, по-американски, я думаю, что это будет что-то вроде 01.02.1970 14:00).

Первая идея: сохранить его как SQL.DATE!

Итак, я создал таблицу:

CREATE TABLE appointments (id NUMBER(10) NOT NULL, datum DATE NOT NULL, PRIMARY KEY id)

Пока все хорошо.

Теперь я написал DAO, сохранив мою встречу, введенную через веб-форму. После этого я хотел написать юнит-тест, чтобы проверить, правильно ли сохранена встреча.

Соответствующая тестовая часть выглядит следующим образом:

JdbcDao myDao = new JdbcDao();
myDao.setDataSource(jdbcTemplate.getDataSource());      
myDao.saveAppointment(appointmentModel);

// Not needed but I saw, the appointment is saved in the database
setComplete();

// And now for the (sorry for the harsh words) pain in the *** part

String sql = "SELECT id, datum FROM appointments WHERE datum ... // <--

<-: Это просто часть, в которую я не знаю, что вводить, чтобы узнать, есть ли в определенный день дата в базе данных. </p>

Я пытался:

datum = ?

следующий звонок

jdbcTemplate.query(sql, args, rowMapper);

имеет массив java.util.Date, java.util.Calendar или java.lang.String ('dd.MM.yyyy') в массиве args, который содержит аргументы, заменяющие? в подготовленном утверждении.

Конечно, это была плохая идея, потому что в базе данных есть что-то вроде

DD.MM.YYYY HH:MI 

в строке таблицы (ДД = день, ММ = месяц, ГГ = год, ЧЧ = час, МИ = минута).

Итак, я нашел команду BETWEEN sql, которая реорганизовала (и попробовала все виды форматов, входные данные, строки, объект для передачи в массив args) команду SELECT для:

String sql = "SELECT id, datum FROM appointments WHERE datum BETWEEN to_date( ?, 'DD.MM.YYYY HH24:MI:SS') AND to_date( ?, 'DD.MM.YYYY HH24:MI:SS')

, который работает, как и многие другие попытки, если я ввожу его через SQL-инструмент напрямую, например,

SELECT * FROM appointments WHERE datum BETWEEN to_date('01.02.1970 00:00:00', 'DD.MM.YYYY HH24:MI:SS') AND to_date('01.02.1970 23:59:59', 'DD.MM.YYYY HH24:MI:SS')

выводит например:

ID                      DATUM                   
----------------------  -------------------
70                      01.02.1970 11:11:11

но мой jdbc-вызов в java всегда приводит к пустому набору результатов.

Длинная история, короткий вопрос:

Как лучше всего запрашивать базу данных, если в базе данных sql.DATE в базе данных существует дата, независимая от заданного времени, в виде даты, представленной java-объектом?

Ответы [ 2 ]

5 голосов
/ 13 марта 2009

Примерно так:

PreparedStatement ps = conn.prepareCall("SELECT * FROM table WHERE someDate = ?");
ps.setDate(1, javaDate)

(из памяти, поэтому синтаксис может быть не совсем правильным)

Вы должны преобразовать объекты java.util.Date в объекты java.sql.Date.

Это довольно просто:

java.sql.Date myDate = new java.sql.Date(oldDate.getTime());

Где oldDate - это объект java.util.Date.

1 голос
/ 28 апреля 2017

Ответ Sacre правильный, но устаревший. Современный способ - с классами java.time.

ТЛ; др

StringBuild sql = new StringBuilder()
sql.append( "SELECT id_, datum_ " );
sql.append( "FROM appointments_ " );
sql.append( "WHERE datum_ >= ? " );  // Start is *inclusive*…
sql.append( "AND datum_ < ? ; " );   // …while end is *exclusive*. (Half-Open approach)

И передать пару Instant объектов.

pstmt.setObject( 1 , start ) ;  // Pass `java.time.Instant` object directly via `PreparedStatement::setObject` method.
pstmt.setObject( 2 , stop ) ;

Использовать объекты, а не строки

Избегайте использования строк для извлечения / хранения значений даты и времени в вашей базе данных. Используйте объекты даты и времени для значений даты и времени. Java и JDBC имеют богатую поддержку объектов даты и времени.

Использование java.time

Класс java.sql.Date предназначен для представления значения только для даты без времени суток и без часового пояса. На самом деле он несет в себе оба этих дополнения, но делает вид, что не слишком. Современная замена - LocalDate. Класс LocalDate представляет значение только для даты без времени суток и без часового пояса.

Но java.sql.DateLocalDate тоже) является неправильным классом для использования, поскольку ваш столбец, похоже, определяется не как только для даты , а как дата-время тип.

Критическая проблема здесь игнорируется вашим Вопросом: часовой пояс. Часовой пояс имеет решающее значение при определении даты . В любой момент времени дата меняется по всему земному шару в зависимости от зоны. Например, через несколько минут после полуночи в Париж Франция - это новый день, а еще «вчера» в Монреаль Квебек .

Укажите собственное имя часового пояса в формате continent/region, например America/Montreal, Africa/Casablanca или Pacific/Auckland. Никогда не используйте 3-4-буквенное сокращение, например EST или IST, поскольку они не истинных часовых поясов, не стандартизированы и даже не уникальны (!).

ZoneId z = ZoneId.of( "America/Montreal" );
LocalDate today = LocalDate.now( z );

Относительно часового пояса в вашей базе данных ...

  • Ваша база данных почти наверняка хранит значения даты и времени, переносящие информацию о часовом поясе или смещении от UTC, как значения UTC в типе, таком как стандарт SQL TIMESTAMP WITH TIME ZONE.
  • Если используется TIMESTAMP WITHOUT TIME ZONE, то значения не имеют информации о часовом поясе или смещении от UTC и не представляют фактических моментов на временной шкале, но могут подходить для встреч достаточно далеко в будущем, в которых они сохраняются как зонированные значения несут в себе риск столкновения политиков с изменением определения часового пояса, например с настройкой / принятием / отменой перехода на летнее время.

Если они хранятся в виде зонированных значений, вам необходимо определить начальный и конечный моменты вашей даты в желаемом часовом поясе, затем преобразовать эту пару точек в значения UTC и запросить в базе данных эту пару значений UTC.

Как правило, лучшая практика в работе с датой и временем для определения промежутков времени, таких как продолжительность дня, как это необходимо здесь, заключается в использовании подхода Half-Open, когда начало промежутка составляет включительно в то время как окончание исключительное . Таким образом, день начинается с первого момента дня и доходит, но не включает первый момент следующего дня.

Не думайте, что первый момент дня - 00:00:00. Аномалии, такие как летнее время (DST), означают, что день может начинаться в такое время, как 01:00:00. Пусть java.time определит первый момент дня.

ZonedDateTime startZdt = localDate.atStartOfDay( z );
ZonedDateTime stopZdt = localDate.plusDays( 1 ).atStartOfDay( z );

Также помните, что промежуток времени между этим стартом и остановкой не обязательно равен 24 часам. Аномалии, такие как летнее время, означают, что день может длиться 23 или 25 часов или какой-либо другой продолжительности.

Преобразуйте их в UTC, извлекая объект Instant. Класс Instant представляет момент на временной шкале в UTC с разрешением наносекунд (до девяти (9) цифр десятичной дроби).

Instant start = startZdt.toInstant();  // Convert to UTC value.
Instant stop = stopZdt.toInstant();

Чтобы выполнить запрос SQL, выполните , а не , используйте BETWEEN. Вместо подхода Half-Open эта команда использует метод Closed, в котором начало и конец включаются.

StringBuild sql = new StringBuilder()
sql.append( "SELECT id_, datum_ " );
sql.append( "FROM appointments_ " );
sql.append( "WHERE datum_ >= ? " );  // Start is *inclusive*…
sql.append( "AND datum_ < ? ; " );   // …while end is *exclusive*. (Half-Open approach)
PreparedStatement pstmt = conn.prepareStatement( sql.toString() );

Укажите пару объектов Instant в качестве аргументов, которые будут использоваться оператором SQL.

pstmt.setObject( 1 , start ) ;  // Pass java.time.Instant object directly via `setObject` method.
pstmt.setObject( 2 , stop ) ;

Боковая панель: добавьте завершающее подчеркивание ко всем вашим идентификаторам SQL, чтобы избежать любого конфликта имен с тысячей зарезервированных слов, согласно обещанию в спецификации SQL.

НетТо есть, как мы вызывали setObject, чтобы напрямую связываться с нашими типами java.time в JDBC.

Если ваш драйвер JDBC еще не поддерживает JDBC 4.2 или более поздней версии, вы должны использовать устаревшие типы java.sql. В этом случае нам понадобится java.sql.Timestamp. Но сделайте это ненадолго - постарайтесь как можно дольше оставаться в java.time.

pstmt.setTimestamp( 1 , java.sql.Timestamp.from( start ) ) ;
pstmt.setTimestamp( 2 , java.sql.Timestamp.from( stop ) ) ;

О java.time

Фреймворк java.time встроен в Java 8 и более поздние версии. Эти классы вытесняют проблемные старые устаревшие классы даты и времени, такие как java.util.Date, Calendar, & SimpleDateFormat.

Проект Joda-Time , в настоящее время находящийся в режиме обслуживания , рекомендует перейти на java.time классы.

Чтобы узнать больше, см. Oracle Tutorial . И поиск переполнения стека для многих примеров и объяснений. Спецификация JSR 310 .

Где получить классы java.time?

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является полигоном для возможных будущих дополнений к java.time. Здесь вы можете найти некоторые полезные классы, такие как Interval, YearWeek, YearQuarter и more .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...