11

I'm generating one date and saving in a database through hibernate, and when I get the value and I compare with the value before it was inserted. The result is not equal!

I created the date as following

Date rightnow = Calendar.getInstance().getTime();

Task t1 = new Task("My task", rightnow);
taskDao.saveOrUpdate(t1);

Task taskR1 = taskDao.get(t1.getIdTask());
assertEquals("They should have to be equal dates",taskR1.getDate(),t1.getDate());

I'm getting this error

<2014-04-11 23:13:13.0> is different to <Fri Apr 11 23:13:13 CEST 2014>

java.lang.AssertionError:  
They should have to be equal dates  
expected:<2014-04-11 23:13:13.0>  
but was:<Fri Apr 11 23:13:13 CEST 2014>

Extra info related with the problem

Class Task

@Entity
@Table(name = "t_task")
public class Task {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "idTask")
    private long idTask;
    ...
    @Column(name = "date")
    private Date date;
    ...

Mysql table t_task

CREATE TABLE IF NOT EXISTS `mytask`.`t_task` (
  `idTask` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `date` DATETIME NOT NULL
  ...

I created a new hashCode() and equals() functions in Task, with only date field and even so it is different.

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((date == null) ? 0 : date.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (!(obj instanceof Task))
        return false;
    Task other = (Task) obj;
    if (date == null) {
        if (other.date != null)
            return false;
    } else if (!date.equals(other.date))
        return false;
    return true;
}

Any idea?

Joe
  • 7,749
  • 19
  • 60
  • 110
  • 1
    :55 vs :54? Doesn't look the same to me - my initial hunch would be that the database is setting the value itself. – user2864740 Apr 11 '14 at 20:08
  • 2
    Are these the same `Date` type? They look like they have different `toString` formats. – Louis Wasserman Apr 11 '14 at 20:13
  • I added another execution and the hashCode and equal functions, I don't know what else to do ¿? – Joe Apr 11 '14 at 21:18
  • Here it what JB Nizet said, the difference between Date and Timestamp classes. http://docs.oracle.com/javase/7/docs/api/java/sql/Timestamp.html – Joe Apr 14 '14 at 07:03

5 Answers5

10

This is a complete mess caused by the java.sql.Timestamp messed up design, and by Hibernate returning instances of this class. Indeed, you're storing a java.util.Date instance into your entity. Hibernate transforms that to a java.sql.Timestamp to insert it in the database. But when it reads the data from the database, it doesn't trasform back the Timestamp into a java.util.Date. That works fine, because Timestamp extends Date.

But Timestamp should never have extended Date. Indeed, Date is precise up to the millisecond, whereas Timestamp is precise up to the nanosecond. To be able to compare the nanoseconds parts of two Timestamp, Timestamp overrides the equals() method, but breaks its general contract by doing so. The end result is that you can have date.equals(timestamp) being true, but timestamp.equals(date) being false.

My advice: never compare Date instances with equals(). Use compareTo() instead.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Just checked this with MongoDB and java.time.LocalDateTime, and the millisecond-nanosecond problem exists there as well. Trimming the date when setting with LocalDateTime solves the issue. – cst1992 May 19 '16 at 12:47
  • I have met the same problem, but I use mybatis. Do they have the same reason? please see http://stackoverflow.com/questions/38722942/why-mybatis-insert-java-util-date-value-to-mysql-datetime-column-then-fetch-it-t – zhuguowei Aug 03 '16 at 02:42
2

Sun's explanation, working with java client level (not with Hibernate), in the javadoc for java.sql.Timestamp, it states:

Quote: public class Timestamp extends Date

A thin wrapper around java.util.Date that allows the JDBC API to identify this as an SQL TIMESTAMP value. It adds the ability to hold the SQL TIMESTAMP nanos value and provides formatting and parsing operations to support the JDBC escape syntax for timestamp values.

Note: This type is a composite of a java.util.Date and a separate nanoseconds value. Only integral seconds are stored in the java.util.Date component. The fractional seconds - the nanos - are separate. The Timestamp.equals(Object) method never returns true when passed a value of type java.util.Date because the nanos component of a date is unknown. As a result, the Timestamp.equals(Object) method is not symmetric with respect to the java.util.Date.equals(Object) method. Also, the hashcode method uses the underlying java.util.Date implementation and therefore does not include nanos in its computation.

Due to the differences between the Timestamp class and the java.util.Date class mentioned above, it is recommended that code not view Timestamp values generically as an instance of java.util.Date. The inheritance relationship between Timestamp and java.util.Date really denotes implementation inheritance, and not type inheritance.

@Test
public void testTimestampVsDate() {
    java.util.Date date = new java.util.Date();
    java.util.Date stamp = new java.sql.Timestamp(date.getTime());
    assertTrue("date.equals(stamp)", date.equals(stamp));            //TRUE
    assertTrue("stamp.compareTo(date)", stamp.compareTo(date) == 0); //TRUE
    assertTrue("date.compareTo(stamp)", date.compareTo(stamp) == 0); //FALSE
    assertTrue("stamp.equals(date)", stamp.equals(date));            //FALSE
}

From javadoc we can figure out that:

Timestamp = java.util.Date + nanoseconds

and

The Timestamp.equals(Object) method never returns true when passed a value of type java.util.Date because the nanos component of a date is unknown.

Timestamp compareTo() function

public int compareTo(java.util.Date o) {
    if(o instanceof Timestamp) {
    // When Timestamp instance compare it with a Timestamp
    // Hence it is basically calling this.compareTo((Timestamp))o);
    // Note typecasting is safe because o is instance of Timestamp
    return compareTo((Timestamp)o);
    } else {
    // When Date doing a o.compareTo(this)
    // will give wrong results.
    Timestamp ts = new Timestamp(o.getTime());
    return this.compareTo(ts);
    }
}
Joe
  • 7,749
  • 19
  • 60
  • 110
1

I would suggest you look at what type you are using to store the date in the database. For instance, an Oracle DATE only has precision down to the second level while TIMESTAMP can have down to millisecond like you would with Java date.

http://docs.oracle.com/cd/B19306_01/server.102/b14220/datatype.htm#CNCPT413

Alex Rose
  • 111
  • 1
  • 3
  • In Java, I'm using Date and in MySQL a DATETIME. And I tried as well the annotation @Temporal(TemporalType.TIMESTAMP) but as well .DATE and TIME. None of them worked :( – Joe Apr 12 '14 at 07:56
1

For those who are looking for an easy unit testing answer to comparing dates, I have used a SimpleDateFormatter to compare dates as Strings. This allows you to specify the precision you are seeking in the comparison without a bunch of math.

SimpleDateFormatter formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
assertEquals(formatter.format(someExpectedDate), formatter.format(someActualDate));

You can modify the format to fit your needs.

Chuck Krutsinger
  • 2,830
  • 4
  • 28
  • 50
0

The two dates are of different classes (one is a java.util.Date, the other is java.sql.Timestamp), so they are not the same.

Try this to check the date values: assertEquals(new Date(taskR1.getDate().getTime()), t1.getDate());