Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to compare java.util.Date value with MySQL datetime column values

I am trying to fetch the results from the table for a specific date. I am passing the Date in String format using the URL, then parsing the String date to java.util.Date and then using that to fetch the values from the table. Within the table, the values are stored in a column that has a data type of Datetime.

@Query(value = "" +
        "FROM " +
        "   MarketingForm mf " +
        "WHERE " +
        "   (:createdAt is null OR mf.createdAt = :createdAt)" +
        "   AND (:verticalId is null OR mf.verticalId = :verticalId)" +
        "   AND (:tag is null OR mf.tag = :tag)"
)
List<MarketingForm> findAllByCreatedAtAndVerticalIdAndTag(@Param("createdAt") Date createdAt, @Param("verticalId") Long verticalId, @Param("tag") String tag);

Below is the String Date and the corresponding java.util.Date

  • String Date = '2021-03-03'
  • java.util.Date converted Date = 'Wed Mar 03 00:00:00 IST 2021'

Method that I am using to parse the String Date to java.util.Date -

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
return formatter.parse(formCreatedDate);

Now I am not sure what is happening here. The java.util.Date is not getting parsed correctly or whether I am doing comparison incorrectly, but the query is not able to fetch the results even though the records are there in the table.

Why is the above SQL not able to fetch the records for the specified Date, even though there are records present in the table?

enter image description here

NOTE: The data in the table is timeformat, and I only need to compare the date component for the date.

like image 766
Gurucharan Sharma Avatar asked Mar 05 '21 06:03

Gurucharan Sharma


1 Answers

I am not sure exactly what you expected as your question is unclear, so I cannot give a precise answer. But I can discuss some of the problems in your approach and your code.

Firstly, never use java.util.Date. That terrible class is part of the legacy date-time classes that were supplanted years ago by the modern java.time classes defined in JSR 310. Likewise, never use Calendar, and never use SimpleDateFormat. Sun, Oracle, and the JCP community gave up on those legacy classes upon the adoption of JSR 310, and so should you. JPA and Hibernate have both been updated to support java.time classes, as has the JDBC 4.2 specification.

Another possible problem, perhaps you are mixing up java.sql.Date with java.util.Date. Be careful of your import statements there. And, again, never use either of the Date classes.

You mention a string of 2021-03-03, but don't really explain where that is. At any rate, that represents a date-only value. But you say you accessing a DATETIME column in MySQL. According the the MySQL 8 documentation, that type represents a date with a time-of-day.

The DATETIME type is used for values that contain both date and time parts. MySQL retrieves and displays DATETIME values in 'YYYY-MM-DD hh:mm:ss' format. The supported range is '1000-01-01 00:00:00' to '9999-12-31 23:59:59'.

So querying a date-with-time column by a date-only value makes no sense. The database cannot compare a date-only value to a date-with-time. You need to specify a span of time, a pair of date-with-time values for your start and end.

Another problem: You are trying to use java.util.Date which represents a moment, a specific date with time as seen in the context of UTC — but the DATETIME type in MySQL is a different animal. The DATETIME type in MySQL represents a date with a time-of-day but lacks the context of an offset or time zone. This type is akin to the SQL type TIMESTAMP WITHOUT TIME ZONE.

Beware: If you are trying to represent moments in your database, specific points on the timeline, then you are using the wrong data type for your column. For moments, you must use a data type akin to the SQL-standard TIMESTAMP WITH TIME ZONE. In MySQL 8, that would be the TIMESTAMP type. The TIMESTAMP type in MySQL tracks moments as seen in UTC, always in UTC. If your column in question is the one named createdAt, then you almost certainly are using the wrong data type, as tracking moments that passed as history must be done with SQL-standard TIMESTAMP WITH TIME ZONE. But I will ignore this crucial issue, and carry on with regard to the type you specified in your Question as written.

If you are not tracking moments, such as tracking future appointments alongside time zone saved in an additional column, then your use of DATETIME would be correct. If you are sure of this datatype for your purpose, then in Java the appropriate match would be LocalDateTime class.

Do your query for a day's worth of data over a DATETIME column by using the half-open approach to defining a span of time. The start is inclusive while the end is exclusive. So a day starts at first moment of the day and runs up to, but does not include, the start of the next day. With half-open, we do not use the SQL keyword BETWEEN.

Here is a simple code example to adapt to your situation. I use neither Spring nor Hibernate, so you will have to translate accordingly.

For your date-only input string, parse as a LocalDate.

Tip: A short way of asking "Is greater than or equal" is "Is not less than". We see this on the first case of the predicate in the SELECT statement below.

LocalDate ld = LocalDate.parse( "2021-03-03" ) ;
LocalDateTime startOfDay = ld.atStartOfDay() ;
LocalDateTime startOfNextDay = ld.plusDays( 1 ).atStartOfDay() ;
String sql = "SELECT * FROM my_table WHERE when !< ? AND when < ? ;" ;  // Performing query over a span-of-time defined as Half-Open. 
…
myPreparedStatement.setObject( 1 , startOfDay ) ;
myPreparedStatement.setObject( 2 , startOfNextDay ) ;

Retrieving DATETIME values.

LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;

All this has been discussed many many times already on Stack Overflow. Search to learn more.

Table of date-time types in Java (both legacy and modern) and in standard SQL

like image 115
Basil Bourque Avatar answered Oct 04 '22 01:10

Basil Bourque