Hibernate Books

All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 10 posts ] 
Author Message
 Post subject: @NaturalId not recognized by Criteria NaturalIdentifier
PostPosted: Sun Jun 08, 2008 8:06 pm 
Newbie

Joined: Sun Jun 08, 2008 7:32 pm
Posts: 5
Howdy!

I'm cacheing some queries by natural-id, but it appears when configuring the natural id properties via annotations, they are not recognized as such. The unique key constraint is created in the database properly, but the ClassMetadata.hasNaturalIdentifier() returns false, and getNaturalIdentifierProperties() returns null. If I do an identical mapping via hbm xml, these methods return the expected values.

This only happens on a @ManyToOne @NaturalId property mapping. A simple property mapping (just a string) returns correct natural id state.

The end result is that the StandardQueryCache does not recognize the special-cased natural id query, and does not bypass the timestamp last-modified check on the table.

I am currently working around this by reverting to hbm xml mapping, but we're moving more and more away from xml config, preferring the annotations.

Some cursory debugging shows that the PropertyBinder does not have the setProperty(XProperty) called in this case, thus the @NaturalId annoation check fails on PropertyBinder.make(). It seems like (sorry, i haven't tried testing this myself -- yet) that adding binder.setProperty( inferredData.getProperty() ) to the correct location in AnnotationBinder.bindManyToOne() might just do the trick. However, it is entirely possible there are other side effects to consider that I am not aware of (and why i'm not attempting a patch just yet).

I've included some relevant test code below. It loads some basic mapping, duplicated both in xml and in annotations. Then, it dumps the class meta data natural id state. the output is:

Code:
SingleTableEntityPersister(oldag.PropertyNaturalId)
  hasNaturalIdentifier() returns true
  getNaturalIdentifierProperties() returns [0]
SingleTableEntityPersister(PropertyHbm)
  hasNaturalIdentifier() returns true
  getNaturalIdentifierProperties() returns [0]
SingleTableEntityPersister(oldag.ManyToOneNaturalId)
  hasNaturalIdentifier() returns false
  getNaturalIdentifierProperties() returns null
SingleTableEntityPersister(ManyToOneHbm)
  hasNaturalIdentifier() returns true
  getNaturalIdentifierProperties() returns [0]



Hibernate version:
3.2.6(ga) core
3.3.1(ga) annotations

Mapping documents:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping
  package="oldag"
  auto-import="true" default-access="field">

  <class name="PropertyNaturalId" table="property" entity-name="PropertyHbm">
    <id name="id">
      <generator class="native"/>
    </id>
    <natural-id>
      <property name="name"/>
    </natural-id>
  </class>

  <class name="ManyToOneNaturalId" table="manytoone" entity-name="ManyToOneHbm">
    <id name="id">
      <generator class="native"/>
    </id>
    <natural-id>
      <many-to-one name="naturalId" entity-name="PropertyHbm"/>
    </natural-id>
  </class>

</hibernate-mapping>



Test code:
Code:
package oldag;

import java.util.Arrays;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import org.hibernate.SessionFactory;
import org.hibernate.annotations.NaturalId;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.metadata.ClassMetadata;

public class NaturalIdTest {
    public static void main(final String[] args) {
        final AnnotationConfiguration cfg = new AnnotationConfiguration();
        cfg.configure("oldag/hib.cfg.xml");
        cfg.addAnnotatedClass(PropertyNaturalId.class);
        cfg.addAnnotatedClass(ManyToOneNaturalId.class);
        final SessionFactory sf = cfg.buildSessionFactory();

        dumpNaturalIds(sf.getClassMetadata(PropertyNaturalId.class));
        dumpNaturalIds(sf.getClassMetadata("PropertyHbm"));
        dumpNaturalIds(sf.getClassMetadata(ManyToOneNaturalId.class));
        dumpNaturalIds(sf.getClassMetadata("ManyToOneHbm"));

        System.out.flush();
        sf.close();
    }

    static void dumpNaturalIds(ClassMetadata cmd) {
        final int[] nids = cmd.getNaturalIdentifierProperties();
        final boolean nid = cmd.hasNaturalIdentifier();
        System.out.println(cmd);
        System.out.printf("  hasNaturalIdentifier() returns %s\n", nid);
        System.out.printf("  getNaturalIdentifierProperties() returns %s\n",
            Arrays.toString(nids));
    }
}

@Entity
@Table(name = "property_a")
class PropertyNaturalId {
    @Id
    @GeneratedValue
    int id;

    @NaturalId
    String name;
}

@Entity
@Table(name = "manytoone_a")
class ManyToOneNaturalId {

    @Id
    @GeneratedValue
    int id;

    @ManyToOne
    @NaturalId
    PropertyNaturalId naturalId;
}


Name and version of the database you are using:
MySQL 5.1.25


Last edited by oldag on Mon Jun 09, 2008 9:28 am, edited 1 time in total.

Top
 Profile  
 
 Post subject:
PostPosted: Mon Jun 09, 2008 4:42 am 
Hibernate Team
Hibernate Team

Joined: Fri Oct 05, 2007 4:47 pm
Posts: 2296
Location: Third rock from the Sun
Hello,
yes I think you have found something strange, but I am wondering
what the meaning is to have
Code:
    @ManyToOne
    @NaturalId
    PropertyNaturalId naturalId;

Does it make any sense?

_________________
Sanne
http://in.relation.to/


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jun 09, 2008 6:59 am 
Hibernate Team
Hibernate Team

Joined: Thu Apr 05, 2007 5:52 am
Posts: 1689
Location: Sweden
Hi,

Seems the mapping data is indeed wrong when there is just a *ToOne association annotated with @NaturalId. I created a Jira issue for it:http://opensource.atlassian.com/projects/hibernate/browse/ANN-750. However, the caching of the criteria query seems to work nevertheless. I used the the Hibernate Statistics class to assert the cache miss and hits and it seems to work. How did you verify the cache misses?

By the way - wouldn't making the ManyToOne association a natural id make the association effectively become a OneToOne? Placing @NaturalId on the association will make the foreign key unique which is somehow against ManyToOne.

--Hardy


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jun 09, 2008 9:31 am 
Newbie

Joined: Sun Jun 08, 2008 7:32 pm
Posts: 5
Sanne --

The example is completely contrived. So, it doesn't make a lot of sense as written. In our "real world" case, I have two business objects, or a business object and a string, that defined the natural key. I keep the surrogate primary key, however, instead of making a composite primary. Hibernate seems to like it this way, and then other tables join to this one via the surrogate primary.

thanks for keeping me honest. ;)

oldag


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jun 09, 2008 9:58 am 
Hibernate Team
Hibernate Team

Joined: Fri Oct 05, 2007 4:47 pm
Posts: 2296
Location: Third rock from the Sun
Hello,
thanks for explaining, I understand this is just an example;
What I meant to say is what Hardy expressed much better: I don't understand the meaning of a NaturalId+Many* combo.

_________________
Sanne
http://in.relation.to/


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jun 09, 2008 10:08 am 
Newbie

Joined: Sun Jun 08, 2008 7:32 pm
Posts: 5
Hardy --

Thanks for checking and getting that issue opened.

It's not that it won't cache it at all. It will. But it ignores the special treatment of (immutable) natural id lookups.

The special treatment is to ignore the last-modified timestamp of the table in the case of a natural id. That way, an insert, update, or delete from the table will not cause the query to be invalidated. As long as the natural key is immutable, the query results can be completely bypassed and the query cache goes directly to level 2 cache to hydrate the object, because the object with an immutable natural key could not have been affected by an insert, update, or delete (as long as it is still valid in the L2 cache). I found this by debugging thru the code. I did not utilize the statistics class to verify, but that is a good idea.

From StandardQueryCache.get()
Code:
      Long timestamp = (Long) cacheable.get(0);
      if ( !isNaturalKeyLookup && !isUpToDate(spaces, timestamp) ) {
         log.debug("cached query results were not up to date");
         return null;
      }


In this case, if one wanted to verify the behavior (according to what i see in the code), it might go something like this

select, miss (null result NOT cached in this case because of natural id)
select, miss
insert
select, miss (populate cache)
insert a new row
select, hit (hits because update timestamp should be ignored)

as for the one-one nature of my example, I bet you right. But, this was just a simplified example for illustrative purposes. In my real app, I have either two @ManyToOne with @NaturalId, or I have a @ManyToOne and a regular property with @NaturalId (see my reply to Sanne). So the unique key ends up (fk_id, propety) or (fk1_id, fk2_id), and the ManyToOne nature is not broken.

That also tells me maybe the jira issue topic should change, or perhaps i should comment it. In the case of my compound natural id, the simple property still shows up in getNaturalIdentifierProperties(), and the @ManyToOne does not. But this still means the mapping is essentially wrong, and the query cache still mistreats it.

Thanks again for the help.


Top
 Profile  
 
 Post subject: more details...
PostPosted: Wed Jun 18, 2008 10:04 pm 
Newbie

Joined: Sun Jun 08, 2008 7:32 pm
Posts: 5
I've posted a blog entry that gives more detail about how I came across this and how caching is effected.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jun 19, 2008 11:06 am 
Hibernate Team
Hibernate Team

Joined: Thu Apr 05, 2007 5:52 am
Posts: 1689
Location: Sweden
Hi,

thanks for the great blog. I just fixed a bug in Annotations so that now ClassMetadata..hasNaturalIdentifier() returns now properly true even if your @NaturalId is only placed on a *ToMany property.

Not sure whether this will fix your caching problems since I did not trace the code all the way into the core. If the caching algorithm relies on the meta data changes are that it will work now.

If you want you can try to build the latest annotations version from the source and give it a go. The next release is also not to far away.

--Hardy


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jun 19, 2008 11:31 pm 
Newbie

Joined: Sun Jun 08, 2008 7:32 pm
Posts: 5
i pulled the latest annotations src, and after realizing I had to update core too, I ran our app with the fix.

I am very pleased to say the StandardQueryCache is behaving as expected with the corrected metadata. isNaturalKeyLookup ends up being 'true' on the get() and put().

and just to reiterate -- in our real application, our natural id is a composite of a String and a @ManyToOne, but obviously your fix handled this case too as it worked perfectly.

Once again, thanks for the help. And thanks for keeping me up to date.


Top
 Profile  
 
 Post subject: Re: @NaturalId not recognized by Criteria NaturalIdentifier
PostPosted: Sun May 27, 2012 12:25 am 
Beginner
Beginner

Joined: Wed Jan 21, 2009 8:41 pm
Posts: 45
I am curious but if you have a HQL query does Hibernate today infer from any unique constraints of the entity and the criteria of the query whether the criteria is immutable or not and hence then ignores the updatetimestamps cache if the query hint was cacheable?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 10 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.