简体   繁体   中英

What's the advantage of finding an entity using the Hibernate @NaturalId

Is there any advantage to find an object using the Hibernate's @NaturalId?

I'm concerned about the fact that Hibernate performs two queries to get an object using @NaturalId. The first query just to get the id and the second query to load the real object.

The major advantage is that you can use the Cache to resolve the entity without hitting the database.

When the ResolveNaturalIdEvent event is thrown, Hibernate will try to:

  • load the associated entity id from the 1st level cache

  • load the associated entity id from the 2nd level cache (if enabled)

  • fall-back to a database query if the 1st level cache can't satisfy our request

     Serializable entityId = resolveFromCache( event ); if ( entityId != null ) { if ( traceEnabled ) LOG.tracev( "Resolved object in cache: {0}", MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory() ) ); return entityId; } return loadFromDatasource( event );

So, it's the same benefit as with using the entity loading through the Persistence Context API (eg EntityManager.find() ).

The only time when two queries are executed is when the entity is not already cached (1st or 2nd level cache).

At least one advantage is you will benefit from first level cache. So for example if you load User by email ( which is naturalid), you will get only the primary key id from db, and the user object from first level cache if it is already there. So faster load time as less network data transfer.

There is also another benefit of using @NaturalID and is related to the use of query cache (I'm basing this answer in Hibernate 4.3.8) If you configure a natural ID and then you query the entity by NaturalID Restrictions#naturalId or Session#byNaturalId you could be hitting query cache even if the table has been modified.

Hibernate keeps a cache of update timestamps of tables to say if a query cache entry is upToDate .

UpdateTimestampsCache : Tracks the timestamps of the most recent updates to particular tables. It is important that the cache timeout of the underlying cache implementation be set to a higher value than the timeouts of any of the query caches. In fact, we recommend that the the underlying cache not be configured for expiry at all. Note, in particular, that an LRU cache expiry policy is never appropriate.

In plain english, if Hibernate has cached the following

*----------------------------------------------------------   *
|                       Query Cache                           |                     
|----------------------------------------------------------   |
| ["from Team where trophies = ", ["10"] ] -> [1,2] ]           |
*----------------------------------------------------------   *

You'll want that if the Team with ID will win a new trophy the query "from Team where trophies=10" will no longer return it. To achieve this, Hibernate keeps records of when tables were last updated and then if the query cache entry is older than this timestamp it doesn't trust in it's results.

I say that it doesn't trust because the result can still be valid but Hibernate wouldn't know because the cache is not per entity, for obvious reasons. So, for example if the Team with ID=3 would be uptaded the entry for 1,2 will be invalidated even if those teams wasn't uptaded. Even more, if the Team 1 would be uptaded but his trophies count would remain the same as before, the query cache entry will be invalidated too although it would still be valid.

If we were querying via NaturalID

List results = s.createCriteria( Citizen.class )
                .add( Restrictions.naturalId().set( "ssn", "1234" ).set( "state", ste ) )
                .list()

we will be enabling to Hibernate to trust in the query cache entries even if they weren't upToDate. This will be like saying: "Yes Hibernate, I know that the table was uptaded but I assure you that the NaturalID of these entities weren't changed so you can trust in the entries that correspond to queries by NaturalID".

Hibernate has to check some a couple of things to see if the query can avoid the upToDate check. This can be seen in Loader#getResultFromQueryCache

boolean isImmutableNaturalKeyLookup =
                    queryParameters.isNaturalKeyLookup() && // If you are querying by NaturalID
                            resultTypes.length == 1 &&
                            resultTypes[0].isEntityType() && // and you are returning an Entity (no projections)
                            getEntityPersister( EntityType.class.cast( resultTypes[0] ) )
                                    .getEntityMetamodel()
                                    .hasImmutableNaturalId(); // and that Entity has an immutable NaturalID (Is the default and I can't imagine when we could use a mutable NaturalID)

The flag isImmutableNaturalKeyLookup is passed to Query#get . Let's see how StandardQueryCache uses it.

final List cacheable = getCachedResults( key, session );
final Long timestamp = (Long) cacheable.get( 0 );
if ( !isNaturalKeyLookup && !isUpToDate( spaces, timestamp, session ) ) {
    if ( DEBUGGING ) {
        LOG.debug( "Cached query results were not up-to-date" );
    }
    return null;
}

return cacheable; // more or less 

If isNaturalKeyLookup is true it doesn't check isUpToDate.

Hibernate: Cache Queries the Natural Id Way is an old but great post about query cache and NaturalID that will help to understand.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM