Я написал две простые функции, чтобы получить значение моей даты в таблице MySQL. Оба столбца даты начала и даты окончания имеют тип данных Date
. Итак, в моих этих двух функциях это выглядит примерно так:
public Date get_startdate(long nodeid,String ts) {
try {
String sql = "Select STARTDT FROM urllink WHERE URL='f0 = "+nodeid+"&ts = "+ts + "'";
if (em == null) {
throw new Exception("could not found URL object.");
}
return (Date) em.createNativeQuery(sql).getSingleResult();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public Date get_enddate(long nodeid,String ts) {
try {
String sql = "Select ENDDT FROM urllink WHERE URL='f0 = "+nodeid+"&ts = "+ts + "'";
if (em == null) {
throw new Exception("could not found URL object.");
}
return (Date) em.createNativeQuery(sql).getSingleResult();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Теперь, когда я вызываю эти функции на своей главной странице, я хочу проверить условия даты: если URL-адрес находится между этими двумя датами, то он должен быть действительным и что-то делать. Я выделяю то, что я имею в виду ниже:
Date file_getstartdate=fileFacade1.get_startdate(fileID,hash);
Date file_getenddate=fileFacade1.get_enddate(fileID,hash);
String currentDate = CoreUtil.parseDate(new Date());
if ( file_getstartdate<= currentDate<= file_getendDate){
//URL is valid, do something
}else {
//do nothing
}
Моя дата, хранящаяся в моей таблице, имеет формат YYYY-MM-DD
, и проблема, с которой я сталкиваюсь, заключается в приведенном выше выражении if
для сравнения. Я не могу использовать эти операторы для проверки. Есть ли способ достичь того, чего я желаю?
Я знал об использовании localeDate
, поскольку он новее и более обновлен, но он доступен только в Java 8. Я помню, что в прошлый раз вы предоставили альтернативное решение относительно использования некоторой функции обратной переносимости, если я правильно помню.
Да, java.time был перенесен на Java 6 и 7 в версии Бэкпорт ThreeTen. К сожалению, он не интегрирован ни с Java Persistence, ни с JDBC, поэтому вам придется выполнить некоторое преобразование.
Если вы используете строки вместо объектов даты, оператор if будет работать для этого формата, если вы напишете его как (a <= b && b <= c)
В противном случае я не уверен, как getSingleResult()
можно преобразовать в объект Date, так как это может быть что угодно, и вам действительно придется анализировать значение, а затем использовать соответствующие методы Date для проверки isBefore и isAfter
Java: как проверить, находится ли дата в определенном диапазоне?
получить одиночные результаты, скажем, дату начала как 2019-04-08
Я не знаю, какую библиотеку sql вы используете, но я ожидаю, что объект ResultSet не может быть приведен к дате
Будет ли работать, если я преобразую их в строку и использую <
или >
для выполнения оператора if?
Должно, да.
Поэтому мне нужно разобрать мой метод на String
Но как String может проверить, что текущая дата больше или меньше конечной даты в yyyy-mm-dd
?
Подумай об этом. 2017 в виде строки меньше, чем 2019, посимвольно. Если у вас на компьютере есть список файлов с датами, как вы думаете, как он их сортирует?
Понимаю. Значит, он может проверить сам day
, например, 2019-04-08
больше, чем 2019-04-02
?
Кроме того, вам не нужны два метода. Просто запустите один запрос sql = "Select STARTDT, ENDDT FROM urllink...
Я также пытался использовать операторы <=
для String, это не позволяло мне сравнивать. я не думаю, что это сработает
Да, так как "8" > "2" верно. Он основан на значениях ASCII
другой вариант - передать Date String
в SQL и выполнить сравнение там
@ Страшно, для этого потребуется подзапрос, да?
Возможно, ответ PlayerOne кажется более разумным, если использовать String.compareTo()
для решения этой проблемы.
Не используйте конкатенацию строк для создания SQL-запросов.
Это уязвимо для SQL-инъекций и действительно опасно:
String sql = "Select STARTDT FROM urllink WHERE URL='f0 = "+nodeid+"&ts = "+ts + "'";
...
return (Date) em.createNativeQuery(sql).getSingleResult();
Предполагая, что em
относится к объекту EntityManager, вы можете построить запрос на основе Criteria
, или, если вам действительно нужно придерживаться собственного SQL, вместо этого вы должны использовать Подготовленное заявление.
Что касается вашего вопроса о сравнении, у вас есть три проблемы:
String
и Date
).<=
).a <= b <= c
не является допустимым оператором Java. Он должен быть a <= b && b <= c
).Вот способ сравнения с использованием класса Date
(обратите внимание, что, поскольку Java 8 LocalDate
— гораздо лучший класс для использования, если у вас есть возможность).
Date fileStartDate = fileFacade1.get_startdate(fileID, hash);
Date fileEndDate = fileFacade1.get_enddate(fileID, hash);
Date currentDate = new Date();
if (fileStartDate.before(currentDate) && fileEndDate.after(currentDate) {
...
В ответ на ваш комментарий
Okay but does using preparedStatement helps with this SQL inejction. Does entity manager prevent the injection? I was told not to use preparedstatement,are there any other alternatives?
Если кто-то сказал вам сделать собственный запрос, используя конкатенацию строк (части вашего кода +nodeid+
и +ts +
) вместо использования PreparedStatement, то они ошибаются. EntityManager не защитит вас от внедрения кода выше, но PreparedStatement вместе с изменением того, как построен ваш запрос, защитит.
PreparedStatement будет выглядеть примерно так
String url = "f0 = " + nodeid + "&ts = " + ts;
PreparedStatement preparedStatement = connection.prepareStatement("Select STARTDT FROM urllink WHERE URL= ?");
preparedStatement.setString(1, url);
ResultSet resultSet = preparedStatement.executeQuery();
Если вам сказали использовать EntityManager
вместо написания собственного SQL, то это действительно хороший совет. Вам нужно будет построить свой запрос, используя абстракцию Criteria
. Как это сделать, наверное, отдельный вопрос.
Но моя таблица хранится в формате YYYY-MM-DD
, и для сравнения не требуется отметка времени, возможно ли это?
ГГГГ-ММ-ДД имеет то преимущество, что сравнение строк даст тот же результат, что и преобразование его в дату с последующим сравнением. Так что либо используйте две строки и String.compareTo()
, либо две LocalDates и LocalDate.compareTo()
. В любом случае вы должны получить тот же результат.
Я не понимаю, вы можете дать ответ?
@Daredevil Я отредактировал пример. Кроме того, пожалуйста, не забывайте о том, что я сказал о SQL-инъекциях, это действительно важно.
Я хотел бы добавить, что localdate
вне моей досягаемости, так как я использую java 7. Как в вашем примере вы можете сравнить тип Date со строковым типом, который является currentDate
, я запутался?
Итак, вы используете .before
и .after
, это лучше, чем сравнивать с помощью String compareTo?
@ Daredevil Я отредактировал некоторую информацию о подготовленных заявлениях в своем ответе. Что касается .before
и .after
против String.compareTo
, оба варианта, вероятно, хороши. Ключ в том, что вам нужно убедиться, что оба объекта, которые вы сравниваете, имеют один и тот же тип. Вы можете сравнивать строки с другими строками и даты с другими датами, но не строки с датами.
Понимаю. Но в вашем ответе эта строка if (fileStartDate.before(currentDate) && fileEndDate.after(currentDate)
использует строку и дату, почему это должно работать?
В качестве примера SQL-инъекции: если злоумышленник передаст nodeId ' or 1 = 1 --
, произойдет сбой, возможно, с сообщением об ошибке, которое предоставит им детали реализации, которые они могут использовать, и определенно сообщит им, что другие, более чувствительные области кода вероятно, также уязвимы, поэтому они могут начать больше исследовать и найти способ получить доступ к конфиденциальным данным и / или получить доступ, которого у них не должно быть.
@ Daredevil «он использует String и Date, почему это должно работать?» Это была опечатка, извините :) Я исправил это
Произойдет ли SQL-инъекция в системе, используемой внутри страны?
Что касается вашего обновленного ответа, формат сравнения все еще YYYY-mm-dd
? Потому что DATE()
также включает временную метку.
Это может быть менее вероятно, чем в системе с внешней облицовкой, но может. Например, недовольный сотрудник может захотеть устроить хаос перед уходом. Несмотря на это, всегда хорошо писать надежный код, и вы никогда не знаете, когда только внутренняя система может быть раскрыта третьим лицам по вполне уважительным деловым причинам.
@ Daredevil При сравнении Date
s он не будет сравниваться ни в каком формате. Вы правы, что new Date()
будет иметь компонент времени, когда он был построен, а остальные даты будут в полночь. Если это проблема, вам нужно либо обрезать время из вашего new Date()
, либо, если вам удобнее сравнивать строки, отформатируйте их все как ГГГГ-ММ-ДД и используйте String.compare
. На SO есть и другие вопросы и ответы, которые смогут помочь с обеими этими задачами :)
Временная метка на самом деле не проблема, моя главная забота - просто убедиться, что текущая дата, сегодня находится в пределах даты начала и даты окончания, тогда URL-адрес действителен.
@ Сорвиголова, тогда ты должен быть в порядке с new Date()
. Любое время будет между полуночью startDate и полночью endDate.
Эй, позвольте мне пройти тест, и если он сработает, я отмечу ваш ответ.
Спасибо, это сработало. Но я удивлен отладкой, что currentdate
возвращает Mon April 2019
и может сравниваться с датой начала и датой окончания в формате yyyy-mm-dd
. Как их можно сравнивать, если они разного формата?
Если вы там, я разместил еще одну ветку, пытающуюся выполнить sql-инъекцию в моей программе: stackoverflow.com/questions/55571337/…, и я не смог внедрить ее в свою программу. Вы можете посоветовать?
@Daredevil Это сбивает с толку, но на самом деле это не разные форматы - это объекты одного и того же типа. Даты могут быть показано зрителю в разных форматах, но когда вы сравниваете их в Java, они будут сравниваться внутренней реализацией их метода compareTo
.
if ( file_getstartdate<= currentDate<= file_getendDate) { … }
LocalDateRange // Represent a span-of-time as a pair of `LocalDate` (date-only) objects.
.ofClosed( startLocalDate , stopLocalDate ) // Making the date range in fully-closed approach, against my advice of using half-open approach.
.contains( // Compares a specified `LocalDate` against the start and stop dates of the range.
LocalDate.now( // Use `java.time.LocalDate` to represent a date-only value without a time-of-day and without a time zone.
ZoneId.of( "Africa/Tunis" ) // Specify the time zone by which we want to perceive the calendar date for the current moment. "Tomorrow" arrives in Paris France while still "yesterday" in Montréal Québec.
) // Returns a `LocalDate` object.
) // Returns a `boolean` primitive.
Ответ первого игрока правильный, но его можно улучшить, используя современные классы Java.время, а не ужасные устаревшие классы (Date
и т. д.).
LocalDate
Для столбца стандартного для SQL типа DATE
используйте класс LocalDate
. Класс LocalDate
представляет значение только для даты без времени суток и без часовой пояс или смещение от UTC.
Часовой пояс имеет решающее значение при определении даты. В любой момент дата варьируется по всему земному шару в зависимости от зоны. Например, несколько минут после полуночи в Париж, Франция — это новый день, а в Монреаль, Квебек — все еще «вчера».
Если часовой пояс не указан, JVM неявно применяет свой текущий часовой пояс по умолчанию. Это значение по умолчанию может быть измениться в любой момент во время выполнения (!), поэтому ваши результаты могут отличаться. Лучше указать желаемый/ожидаемый часовой пояс явно в качестве аргумента. Если критично, подтвердите зону у своего пользователя.
ZoneID
Укажите правильное название часового пояса в формате Continent/Region
, например America/Montreal
, Africa/Casablanca
или Pacific/Auckland
. Никогда не используйте сокращения из 2-4 букв, такие как EST
или IST
, так как это нет истинные часовые пояса, не стандартизированные и даже не уникальные(!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
Если вы хотите использовать текущий часовой пояс JVM по умолчанию, запросите его и передайте в качестве аргумента. Если его опустить, код становится двусмысленным для чтения, поскольку мы не знаем наверняка, намеревались ли вы использовать значение по умолчанию или вы, как и многие программисты, не знали об этой проблеме.
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
if ( file_getstartdate<= currentDate<= file_getendDate){
Вам будет легче работать, если вы будете последовательно использовать полуоткрытый подход к определению промежутка времени. В этом подходе начало — включительно, а окончание — эксклюзивный. Таким образом, месяц начинается с первого числа и продолжается до первого месяца следующий, но не включает его.
Таким образом, логика вашего запроса будет выглядеть следующим образом: с <=
и <
.
if ( file_getstartdate <= currentDate < file_getendDate)
Это означает, что в SQL нет использует BETWEEN
для работы с датой и временем.
String sql = "SELECT * from tbl WHERE when >= ? AND when < ? ;" ;
…
myPreparedStatement.setObject( 1 , today ) ;
myPreparedStatement.setObject( 2 , today ) ;
Чтобы получить DATE
, значения:
LocalDate localDate = myResultSet.getObject( … , LocalDate.class ) ;
Такая же полуоткрытая логика в коде Java будет выглядеть следующим образом. Обратите внимание, что более короткий способ сказать «равно или позже, чем» — это «не раньше».
boolean isTodayWithinDateRange =
( ! today.isBefore( startDate ) ) // Is today same or later than start…
&& // AND
today.isBefore( stopDate ) // Is today before the stop (for Half-Open approach).
;
My date stored in my table is in the format YYYY-MM-DD
Нет это не так. Тип DATE
в MySQL хранит дату с помощью собственного внутреннего механизма, а не обычного текста.
Объект даты и времени, такой как столбец DATE
в базе данных или LocalDate
в Java, может анализировать входную строку в значение даты и может генерировать строку из этого значения даты. Но значение даты не является строкой. Не путайте их. Другими словами, значение даты не имеет «формата», формат имеют только их текстовые представления.
LocalDateRange
Если вы выполняете большую часть этой работы, добавьте в свой проект библиотеку ThreeTen-Extra. Это дает вам доступ к классу LocalDateRange
.
LocalDate start = … ;
LocalDate stop = … ;
LocalDateRange range = LocalDateRange.of( start , stop ) ;
boolean rangeContainsToday = range.contains( today ) ;
По умолчанию класс LocalDateRange
работает с полуоткрытым подходом. Но если вы настаиваете на своем полностью закрытом подходе, переопределите его поведение по умолчанию с помощью метода LocalDateRange.ofClosed
.
Платформа Java.время встроена в Java 8 и более поздние версии. Эти классы заменяют проблемные старые классы даты и времени наследие, такие как java.util.Date
, Calendar
и SimpleDateFormat
.
Чтобы узнать больше, см. Учебник по оракулу. И поищите множество примеров и пояснений в Stack Overflow. Спецификация JSR 310.
Проект Джода-Время, теперь именуемый Режим технического обслуживания, рекомендует перейти на классы Java.время.
Вы можете обмениваться объектами Java.время напрямую с вашей базой данных. Используйте JDBC-драйвер, совместимый с JDBC 4.2 или более поздней версии. Нет необходимости в строках, нет необходимости в классах java.sql.*
.
Где получить классы java.time?
Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является испытательным полигоном для возможных дополнений к java.time в будущем. Здесь вы можете найти несколько полезных классов, таких как Interval
, YearWeek
, YearQuarter
и более.
Я рекомендую вам не использовать
Date
, независимо от того, было ли этоjava.sql.Date
илиjava.util.Date
. Эти классы плохо разработаны и давно устарели. Вместо этого используйтеLocalDate
из java.time, современный API даты и времени Java. У него есть методыisAfter
,isBefore
иisEqual
, которые сделают то, что вы хотите.