-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 posts ] 
Author Message
 Post subject: one-to-zeroOrOne mapping ?
PostPosted: Sat Dec 10, 2005 3:11 pm 
Regular
Regular

Joined: Sun May 08, 2005 2:48 am
Posts: 118
Location: United Kingdom
I am trying to confirm my relationship configuration is correct.

It is like one-to-one but one side is optional and there is a bi-directional association which is NULL when the optional side does not exist. This is all contained within two tables.

I have been using many-to-one mapping from both sides (With no inverse=true set on either side) and I'm having a problem lazy loading from spouse to person. I have not used one-to-one as this seems to imply rows always exist in both tables and they share the same primary key.

So what would the correct way be to describe a one-to-zeroOrOne relationship ?


The long story:

The problem I am chasing (thinking I have a mapping error) simplified:

Spouse spouse = (Spouse) Session.get(Spouse.class, new Long(1001));
long dataInteger = spouse.getPerson().getDataInteger();

The result is ZERO, but the value from a different SQL session to the same table around the Hibernate select is not zero. The next saveOrUpdate() causes a zero value to be written to the table trashing the previous value.

The only odity with the arrangement is that I'm using MySQL TINYINT UNSIGNED (this has the range 0 to 255) and using a Java type 'short' to store the ranged data, and mapping type="short" in the HBM.

When I was using a MySQL 4.0 server there was no problem, I have upgraded to 4.1 and this single piece of code has this problem now.

The setting of the person.dataInteger column is done from JDBC code, however as I open the Session I do a clear(), the Java code snippet at the end of this post is more like what is really going on, I've commented where the problem occurs.


If there is any more info I can provide please say, at the moment I'm trying to produce a cut-down test project so I can see the error and start modifiying my configuration until it works, but I'd much rather understand the real cause thans fiddling until it works.

Thanks in advance.


Code:
class Person {
private long id;
private long spouseId;
private short dataInteger;
public void setPersionId(long id) { ... }
public void setSpouseId(long spouseId) { ... }
public void setDataInteger(short dataInteger) {
  if(dataInteger < 0 || dataInteger > 255)
    return; // SQL is MySQL TINYINT UNSIGNED which Java type short can hold its range
this.dataInteger = dataInteger;
}
// getters exist too ...
}

CREATE TABLE person (
personId BIGINT NOT NULL AUTO_INCREMENT,
spouseId BIGINT NULL, # FK spouse.spouseId
dataInteger TINYINT UNSIGNED NOT NULL,
PRUMARY KEY(personId)
);

CREATE TABLE spouse (
spouseId BIGINT NOT NULL AUTO_INCREMENT,
personId BIGINT NOT NULL,  # FK person.personId,
keyOne BIGINT NOT NULL,
PRIMARY KEY(spouseId),
KEY(keyOne)
);


Manual operation of bidi-assoc:

> INSERT INTO person SET personId=NULL, spouseId=NULL, dataInteger=1;
> SELECT LAST_INSERT_ID();
1001

Now person exists, then at some point later I want to give them a spouse:

> START TRANSACTION;
> INSERT INTO spouse SET spouseId=NULL, personId=1001;
> SELECT LAST_INSERT_ID();
9992
> UPDATE person SET spouseId=9992 WHERE personId=1001;
> COMMIT;

Then later I may want to remove the spouse:

> START TRANSACTION;
> UPDATE person SET spouseId=NULL WHERE personId=1001;
> DELETE FROM spouse WHERE spouseId=9992;
> COMMIT;

// Example of how my real Java code looks around the error
void func(long keyOne) {
   Session hsession = null;
   Transaction tx = null;
   try {
      hsession = HibernateSession.currentSession();
      hsession.clear();  // I use this to fix external JDBC interaction with this data
      tx = hsession.beginTransaction();

      Query q = hsession.createQuery("from Spouse s where s.keyOne=:keyOne");
      q.setLong("keyOne", keyOne);
      Iterator it = q.iterate();
      if(it.hasNext()) {
         Spouse spouse = (Spouse) it.next();
         Parent parent = spouse.getParent();
         long wrongValue = parent.getDataInteger();

         // THE ERROR CAN BE SEEN HERE wrongValue==0

         parent.setSomethingElse(42);
         hsession.saveOrUpdate(parent);
         // When this hits SQL server my parent.dataInteger column is trashed
      }
      hsession.flush();
      tx.commit();
      tx = null;
   } catch(Exception e) {
      // blah blah
   } finally {
      if(tx != null)
         tx.rollback();
      if(hsession != null)
         hsession.close();
   }
}


Top
 Profile  
 
 Post subject:
PostPosted: Sat Dec 10, 2005 4:43 pm 
Regular
Regular

Joined: Mon Sep 29, 2003 9:39 am
Posts: 67
You can still use one-to-one with one optional side. Constraining and using the foreign key generator is optional.

There are some notes in the lazy initialization wiki regarding this kind of relationship.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Dec 11, 2005 11:03 am 
Regular
Regular

Joined: Sun May 08, 2005 2:48 am
Posts: 118
Location: United Kingdom
Thanks for the suggestion.

I have managed to nail down the problem in that values in the range 128 to 255 are turned into 0. Even though the hibernate generated select when copied and run manually does return the value 128 thru 255.

The problem started since I have upgraded the SQL server from MySQL 4.0 to 4.1, I have been using the same MySQL JDBC driver 3.1.11 all along.

The column is stored in a MySQL TINYINT UNSIGNED type.

My mapping for that property is:
<property name="status" type="short" column="status" />

The range of a short is enough to hold the integral values 0 thru 255.


I have downgraded MySQL to 4.0 and confirmed the problem goes away.


Now I need to work out why the JDBC Driver and/or Hibernate is behaving differently based on the SQL server version I use.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Dec 11, 2005 12:19 pm 
Regular
Regular

Joined: Sun May 08, 2005 2:48 am
Posts: 118
Location: United Kingdom
I've a test case now.

I've nailed it down to the Hydration !?! process is loosing the value when talking to a MySQL 4.1 server, but doesn't for a 4.0 server, the server version is the only change between test runs:

I've been unable to find any Wiki of Documentation relating to the handling of unsigned SQL data types with hibernate, so again sadly I've no idea if this behaviour is intended Hibernate behaviour or a bug.

It would be nice to seek clarification on this matter and a potential fix to make behaviour consistant to the application programmer.

Code:
mysql> select * from person \G
*************************** 1. row ***************************
        person_id: 1
        spouse_id: 999
     when_created: 2005-12-11 16:17:06
   local_priority: 128
           status: 255  <<== TINYINT UNSIGNED value of 255
      when_status: 2005-12-11 16:17:06
    when_terminal: 2005-12-11 16:17:06
       status_ack: 0
  when_status_ack: NULL
when_terminal_ack: NULL
1 row in set (0.00 sec)

mysql> select * from spouse \G
*************************** 1. row ***************************
   spouse_id: 999
   person_id: 1
when_created: 2005-12-11 16:17:06
  message_id: 100
1 row in set (0.00 sec)



DEBUG 15:56:54,537 (AbstractBatcher.java:log:324)  -select person0_.person_id as person1_0_, person0_.when_created as when2_0_0_, person0_.local_priority as local3_0_0_, person0_.status as status0_0_, person0_.when_status as when5_0_0_, person0_.when_terminal as when6_0_0_, person0_.status_ack as status7_0_0_, person0_.when_status_ack as when8_0_0_, person0_.when_terminal_ack as when9_0_0_, person0_.spouse_id as spouse10_0_0_ from person person0_ where person0_.person_id=?
Hibernate: select person0_.person_id as person1_0_, person0_.when_created as when2_0_0_, person0_.local_priority as local3_0_0_, person0_.status as status0_0_, person0_.when_status as when5_0_0_, person0_.when_terminal as when6_0_0_, person0_.status_ack as status7_0_0_, person0_.when_status_ack as when8_0_0_, person0_.when_terminal_ack as when9_0_0_, person0_.spouse_id as spouse10_0_0_ from person person0_ where person0_.person_id=?
DEBUG 15:56:54,538 (AbstractBatcher.java:getPreparedStatement:378)  -preparing statement
DEBUG 15:56:54,541 (NullableType.java:nullSafeSet:59)  -binding '1' to parameter: 1
DEBUG 15:56:54,544 (AbstractBatcher.java:logOpenResults:306)  -about to open ResultSet (open ResultSets: 0, globally: 0)
DEBUG 15:56:54,545 (Loader.java:doQuery:405)  -processing result set
DEBUG 15:56:54,546 (Loader.java:doQuery:410)  -result set row: 0
DEBUG 15:56:54,551 (Loader.java:getRow:828)  -result row: EntityKey[data.Person#1]
DEBUG 15:56:54,559 (Loader.java:loadFromResultSet:978)  -Initializing object from ResultSet: [data.Person#1]
DEBUG 15:56:54,560 (BasicEntityPersister.java:hydrate:1651)  -Hydrating entity: [data.Person#1]
DEBUG 15:56:54,570 (NullableType.java:nullSafeGet:86)  -returning '2005-12-11 15:43:10' as column: when2_0_0_

# This is also a TINYINT UNSIGNED but its value is never lost, the value is really 128 not minus 128, minus 128 is valid 2s compliment value for a single byte of data with unsigned value 255.
DEBUG 15:56:54,575 (NullableType.java:nullSafeGet:86)  -returning '-128' as column: local3_0_0_

# This is the TINYINT UNSINGNED that has a value of 255 (which under signed 2s compliment would be -128) but it registers are zero here.
DEBUG 15:56:54,577 (NullableType.java:nullSafeGet:86)  -returning '0' as column: status0_0_

DEBUG 15:56:54,579 (NullableType.java:nullSafeGet:86)  -returning '2005-12-11 15:43:10' as column: when5_0_0_
DEBUG 15:56:54,580 (NullableType.java:nullSafeGet:86)  -returning '2005-12-11 15:43:10' as column: when6_0_0_
DEBUG 15:56:54,582 (NullableType.java:nullSafeGet:86)  -returning '0' as column: status7_0_0_
DEBUG 15:56:54,583 (NullableType.java:nullSafeGet:80)  -returning null as column: when8_0_0_
DEBUG 15:56:54,586 (NullableType.java:nullSafeGet:80)  -returning null as column: when9_0_0_
DEBUG 15:56:54,591 (NullableType.java:nullSafeGet:86)  -returning '999' as column: spouse10_0_0_

Not sure if its relevant but MySQL 4.1 caps the range to the highest value or lowest value if you try and set a column value out of range.  For for TINYINT UNSIGNED  SET data=-256 result in a value of data=0, and SET data=256 result in a value of data=255


Top
 Profile  
 
 Post subject:
PostPosted: Sun Dec 11, 2005 12:49 pm 
Regular
Regular

Joined: Sun May 08, 2005 2:48 am
Posts: 118
Location: United Kingdom
Testing with a recent CVS build of Hibernate yeilds a different value again for me.

Code:
DEBUG 16:30:10,986 (NullableType.java:nullSafeGet:86)  -returning '2005-12-11 16:29:58' as column: when2_0_0_
DEBUG 16:30:10,988 (NullableType.java:nullSafeGet:86)  -returning '-128' as column: local3_0_0_

# minus 1 now with Hibernate CVS HEAD a change from 0 in Hibernate 3.0.5 (in previous post)
DEBUG 16:30:10,989 (NullableType.java:nullSafeGet:86)  -returning '-1' as column: status0_0_

DEBUG 16:30:10,991 (NullableType.java:nullSafeGet:86)  -returning '2005-12-11 16:29:58' as column: when5_0_0_
DEBUG 16:30:10,996 (NullableType.java:nullSafeGet:86)  -returning '2005-12-11 16:29:58' as column: when6_0_0_
DEBUG 16:30:10,997 (NullableType.java:nullSafeGet:86)  -returning '0' as column: status7_0_0_
DEBUG 16:30:10,999 (NullableType.java:nullSafeGet:80)  -returning null as column: when8_0_0_
DEBUG 16:30:11,000 (NullableType.java:nullSafeGet:80)  -returning null as column: when9_0_0_
DEBUG 16:30:11,001 (NullableType.java:nullSafeGet:86)  -returning '999' as column: spouse10_0_0_


Top
 Profile  
 
 Post subject:
PostPosted: Sun Dec 11, 2005 1:36 pm 
Regular
Regular

Joined: Sun May 08, 2005 2:48 am
Posts: 118
Location: United Kingdom
Current inspection of class com.mysql.jdbc.ResultSet#getNativeShort() yeilds no special handling of UNSIGNED types by the MySQL JDBC 3.1.12 driver, there is special handling for other similar type conversions #getNativeInt().

But I still dont understand how 4.0 or 4.1 server makes a difference, since I've been testing with the same MySQL JDBC driver.

Nor why 3.0.5 and 3.1rc1 (CVS HEAD) sees a difference of 0 and -1.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 posts ] 

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.