Hibernate Books

All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 40 posts ]  Go to page Previous  1, 2, 3  Next
Author Message
 Post subject:
PostPosted: Mon Mar 01, 2004 12:47 pm 
Proxool Developer
Proxool Developer

Joined: Tue Aug 26, 2003 10:42 am
Posts: 373
Location: Belgium
gavin wrote:
huh??? What, Java has no polymorphism?


Here is what you will find when looking at the sources of java.sql.Timestamp:
Code:
public boolean equals(java.lang.Object ts) {
   if (ts instanceof Timestamp) {
      return this.equals((Timestamp)ts);
   } else {
      return false;
   }
}


java.sql.Timestamp.equals(java.util.Date) will always fail!

Suppose the following persistent class:
Code:
public Class Foo {
   public java.util.Date utilDate;
  ...
}


You map this field using an Hibernate Timestamp type because you need date+time precision.

Now consider the following scenario:
Code:
Date now = new Date();

session = <new session>

Foo f = new Foo();
f.utilDate = now;

session.save(f);
session.close();

session = <new session>
f = (Foo) session.load(Foo.class, <foo id>);

assert(f.utilDate.equals(now)); // WILL FAIL !!


As you can notice, the latest statement will fail although it should succeed... This because the utilDate field has been loaded with a java.sql.Timestamp instance instead of a java.util.Date.

See the danger ?


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 01, 2004 12:55 pm 
Proxool Developer
Proxool Developer

Joined: Tue Aug 26, 2003 10:42 am
Posts: 373
Location: Belgium
tcollins wrote:
maybe try Date.equals(Timestamp) if your doing a compare against a regular Date the nanos won't be there anyway.


So I have to remember everywhere in my code that Hibernate may have replaced my Date with a Timestamp ?
Not very cool for a transparent ORM - and Hibernate can be much more transparent than this...

No, I believe the solution in this case is to write my own UserType. Gavin took the time to open the framework and provide various extension points to make us happy - so lets go this way. And I'm happy with that solution.

Just took the time to post my experience once more in this thread because it took me so long to troubleshoot our application because of this "surprising" java.sql.Timestamp.equals() implementation.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 01, 2004 1:01 pm 
Beginner
Beginner

Joined: Tue Jan 27, 2004 2:14 pm
Posts: 40
Location: Atlanta, GA, USA
That's cool... that sounds like a good solution. I was just trying to help. I would be interested in seeing that implemented UserType if you don't mind. Would be cool if you could post it here.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 01, 2004 1:18 pm 
Proxool Developer
Proxool Developer

Joined: Tue Aug 26, 2003 10:42 am
Posts: 373
Location: Belgium
tcollins wrote:
That's cool... that sounds like a good solution. I was just trying to help. I would be interested in seeing that implemented UserType if you don't mind. Would be cool if you could post it here.


Here is it...

Code:
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Date;

import net.sf.hibernate.Hibernate;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.UserType;

/**
* @author Bertrand Renuart
*
* Hibernate User Type to persist java.util.Date object as java.sql.Timestamp(s)
*
* The default behavior of Hibernate is to return the java.sql.Timestamp object
* retrieved from the underlying Resultset. Unfortunately, because of the extra
* precision (to the nanoseconds) of Timestamp, compare methods (including
* equals & compareTo) fail when used with java.util.Date instances.
*
* This user type will convert the java.sql.Timestamp back into java.util.Date
* instances (loosing the extra precision) before returning the data to the
* user.
*/
public class DateTimeType implements UserType
{
   private static final int[] SQL_TYPES = new int[] { Types.TIMESTAMP };
   

   //
   // ---- Constructors ---------------------------------------------------------
   //
   
   /**
    *
    */
   public DateTimeType()
   {
      super();
   }

   //
   // ---- UserType interface implementation ------------------------------------
   //

   /**
    * The class returned by this type factory.
    *
    * @see net.sf.hibernate.UserType#returnedClass()
    */
   public Class returnedClass() {
      return java.util.Date.class;
   }
   
   /**
    * Return the SQL type codes for the columns mapped by this type
    *
    * @see net.sf.hibernate.UserType#sqlTypes()
    */
   public int[] sqlTypes() {
      return SQL_TYPES;
   }

   /**
    * Compare two instances of the class mapped by this type for persistence "equality".
    *
    * @see net.sf.hibernate.UserType#equals(java.lang.Object, java.lang.Object)
    */
   public boolean equals(Object x, Object y) throws HibernateException {
      if( x==y ) return true;
      if( (x==null) || (y == null) ) return false;
      
      Date xDate = (Date) x;
      Date yDate = (Date) y;
      
      return x.equals(y);
   }


   /**
    * Retrieve an instance of the mapped class from a JDBC resultset.
    *
    * @see net.sf.hibernate.UserType#nullSafeGet(java.sql.ResultSet, java.lang.String[], java.lang.Object)
    */
   public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
      throws HibernateException, SQLException
   {
       // extract Timestamp from the result set
      Timestamp timestamp = (Timestamp) Hibernate.TIMESTAMP.nullSafeGet(rs, names[0]);      

      // return the value as a java.util.Date (dropping the nanoseconds)
      if( timestamp==null )
      {
         return null;
      }
      else
      {   
         return new Date(timestamp.getTime());
      }
   }


   /**
    * Write an instance of the mapped class to a prepared statement
    *
    * @see net.sf.hibernate.UserType#nullSafeSet(java.sql.PreparedStatement, java.lang.Object, int)
    */
   public void nullSafeSet(PreparedStatement st, Object value, int index)
      throws HibernateException, SQLException
   {
      // handle the NULL special case immediately
      if( value==null )
      {
         st.setTimestamp(index, null);
         return;
      }
      
      
      // make sure the received value is of the right type
      if( ! Date.class.isAssignableFrom(value.getClass()) ) {
         throw new IllegalArgumentException("Received value is not a [java.util.Date] but [" + value.getClass() + "]");
      }

      // set the value into the resultset
      Timestamp tstamp = null;
      if( (value instanceof Timestamp) )
      {
         tstamp = (Timestamp) value;
      }
      else
      {
         tstamp = new Timestamp( ((Date) value).getTime() );
      }
      
      st.setTimestamp(index, tstamp);
   }


   /**
    * Return a deep copy of the persistent state, stopping at entities and at collections
    *
    * @see net.sf.hibernate.UserType#deepCopy(java.lang.Object)
    */
   public Object deepCopy(Object value) throws HibernateException {
      if( value==null )
      {
         return null;
      }
      else
      {   
         return ((Date) value).clone();
      }
   }


   /**
    * Are objects of this type mutable?
    *
    * Date instances are mutable (although the date.set() methods are deprecated, there are
    * still accessible)
    *
    * @see net.sf.hibernate.UserType#isMutable()
    */
   public boolean isMutable() {
      return true;
   }

}


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 01, 2004 1:42 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
Quote:
java.sql.Timestamp.equals(java.util.Date) will always fail!


Not if the other instance of Date is actually a Timestamp!!

I mean, where on earth are these naked Dates coming from??? I am not creating them, so it must be you.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 01, 2004 5:23 pm 
Proxool Developer
Proxool Developer

Joined: Tue Aug 26, 2003 10:42 am
Posts: 373
Location: Belgium
gavin wrote:
Quote:
java.sql.Timestamp.equals(java.util.Date) will always fail!


Not if the other instance of Date is actually a Timestamp!!


Which means never try to compare (with equal) your Date Hibernate-persisted fields with a java.util.Date...

gavin wrote:
I mean, where on earth are these naked Dates coming from??? I am not creating them, so it must be you.


Of course! They are created from within my business code...

Stupid example: I have a persisted-collection of elements having a Date attribute. I iterate over these elements, comparing their date field with now (= new java.util.Date())

Several alternatives:
1/ dateField.compare(now)==0 - will work;
2/ now.compare(dateField)==0 - will work;

3/ dateField.equals(now)==true - will *not* work;
4/ now.equals(dateField)==true - will work;

5/ don't do now=new java.util.Date() but now=new java.sql.Timestamp() - but I'm against using java.sql stuff in my business (supposed to be independent of the underlying persistence layer).


=> Anyway, the transformation made by Hibernate when persisting fields with date+time precision is not a reflexive. This is so because:
- Hibernate replaces java.util.Date instances by java.sql.Timestamp
- AND because the Timestamp behavior described in 3/ & 4/ above.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 01, 2004 5:31 pm 
Proxool Developer
Proxool Developer

Joined: Tue Aug 26, 2003 10:42 am
Posts: 373
Location: Belgium
I don't understand why it is so hard to make this point obvious...
Could be my English is not good enough, or I'm trying to do something very stupid, or whatever...

Maybe you should read what Michael wrote in the original post (http://forum.hibernate.org/viewtopic.php?t=927602) - his words are probably more carefuly chosen than mine.

I just want to quote what you said in this previous thread:
Quote:
Note that no self-respecting code should EVER call equals on a Timestamp. Its almost as bad as for Float.


I agree with this statement - but how can you prevent this from happening if Hibernate replaces the java.util.Date with Timestamps ?


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 02, 2004 6:23 am 
Newbie

Joined: Tue Oct 14, 2003 11:32 am
Posts: 9
Location: Switzerland
I understand the positions of brenuart and Gavin, but my problem was mainly related to the code generation (hbm2java).

I just think it is not consistent to generate setter and getters with java.util.Date where there should be a java.sql.Timestamp. If I configure hibernate with a timestamp field I also would like to use it in my code, or should I always cast e.g. to set nanoseconds... (if I do not modify the generated code by myself)?

Regards
Massimo


Top
 Profile  
 
 Post subject:
PostPosted: Sat Nov 20, 2004 2:38 pm 
Regular
Regular

Joined: Tue Jan 06, 2004 3:32 pm
Posts: 80
Location: Munich, Germany
Just to complete the information in this thread:

Not only does equals() break, compareTo() does also.

compareTo() is used in the Collections framework, so you can't use TreeSet.add() and TreeMap.put()...

Regards,

Andreas


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jul 09, 2005 5:02 am 
Newbie

Joined: Tue Oct 21, 2003 2:12 am
Posts: 7
gavin wrote:
Dude, Timestamp inherits Date! Think about it. Hibernate has no problem assigning an instance of Timestamp to a property of type Date.

Although Timestamp inherits Date's implementation, it does NOT inherit its semantics. To quote from Sun's javadocs for Timestamp [1.5]:

Quote:
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.

So although it is legal Java to assign Timestamps to java.util.Date fields, it breaks the semantics and loses information. I just spent many hours dealing with a bug that was caused by this mismatch. It appears that many other users have met a similar fate.

As nearly as I can reckon, the following trivial Type precisely matches the semantics of the java.util.Date class:

Code:
public class TimeMillisType extends org.hibernate.type.TimestampType {

    public Date get(ResultSet rs, String name) throws SQLException {
        Timestamp timestamp = rs.getTimestamp(name);
        if (timestamp == null) return null;
        return
            new Date(timestamp.getTime()+timestamp.getNanos()/1000000);
   }

}


I don't understand why this isn't the default mapping for java.util.Date. It seems like a very large number of people have run afowl of this problem.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jul 25, 2005 6:36 pm 
Beginner
Beginner

Joined: Thu Dec 09, 2004 3:19 pm
Posts: 34
I'd vote for encapsulating it inside Hibernate. Comparing java.util.Date objects from one business subsystem against Timestamp objects retrieved from the database is extremely common. There's no way to know they aren't truly comparable. If you use compareTo() you'll even get a ClassCastException.

A real pain in the rear and there's no nice work around, barring Bertrand's user type.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jul 30, 2005 11:37 am 
Regular
Regular

Joined: Tue Jan 06, 2004 3:32 pm
Posts: 80
Location: Munich, Germany
ckessel wrote:
I'd vote for encapsulating it inside Hibernate.


I'd vote to fix it in the JDK (-:

Hibernate has got nothing to do with this problem.

The only thing Hibernate could do is include the usertype in its distribution, for convenience.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jul 30, 2005 10:26 pm 
Senior
Senior

Joined: Tue Jun 21, 2005 10:18 am
Posts: 135
Location: South Carolina, USA
Goonie wrote:
I'd vote to fix it in the JDK (-:

That's my take on it, too... date handling in Java is just plain weird, IMHO. The java.sql.Date/Time/Timestamp types just make things worse (though they are, admittedly, necessary for working with database date/time types).


Top
 Profile  
 
 Post subject: Sun patched Timestamp problem in JDK 1.5.0_07
PostPosted: Thu Sep 14, 2006 1:06 am 
Newbie

Joined: Fri Jun 10, 2005 12:22 am
Posts: 6
Location: Melbourne/Sydney
Thanks bertrand for your investigations and explaination.

I totally agree with your with you.

Looks like this issue has been resolved in JDK 1.5.0_07

http://bugs.sun.com/bugdatabase/view_bu ... id=5103041


Top
 Profile  
 
 Post subject:
PostPosted: Fri Oct 13, 2006 11:23 am 
Newbie

Joined: Fri Mar 03, 2006 6:03 am
Posts: 8
Bertrand's solution didn't work for me for some reason.
I really expected that it would work. Anybody has got that solution working?
I debugged the execution and observed that the cdate field of the date class gets different values for the application-created date object and hibernate-with-DateTimeType-created date object.

Actually I came across this by a unit test. For example there is an Event class having a 'java.util.Date date' field. Then when Event objects loaded from the database are checked against the objects created by the TestCase, the tests fail.


e.g.
// create Event object

// save in database

// close session

// load the event object by id

// close session

// compare > FAILS
...

And JDK doesn't seem to have been fixed on that issue either.

Any ideas?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 40 posts ]  Go to page Previous  1, 2, 3  Next

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.