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.
Hibernate 5.5
, an entity is fetched by its natural identifier directly from the database i.e only 1 query – Gabrielegabriell