使用 Hibernate @NaturalId 查找实体有什么好处

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

使用 Hibernate 的 @NaturalId 查找对象有什么优势吗?

我担心 Hibernate 使用 @NaturalId 执行两个查询来获取对象这一事实。 第一个查询只是为了获取id,第二个查询是为了加载真实对象。

至少一个优势是您将受益于一级缓存。因此,例如,如果您通过电子邮件(这是 naturalid)加载用户,您将仅从数据库中获取主键 ID,如果已经存在,则从一级缓存中获取用户对象。加载时间更快,网络数据传输更少。

主要优点是您可以使用缓存来解析实体而无需访问数据库。

当抛出 ResolveNaturalIdEvent 事件时,Hibernate 将尝试:

  • 从一级缓存加载关联实体id

  • 从二级缓存中加载关联的实体 ID(如果启用)

  • 如果一级缓存不能满足我们的请求,则回退到数据库查询

      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 );
    

因此,它与通过持久性上下文使用实体加载的好处相同 API(例如 EntityManager.find())。

执行两个查询的唯一时间是实体尚未缓存(一级或二级缓存)时。

还有另一个使用@NaturalID 的好处,它与查询缓存的使用有关(我将这个答案基于 Hibernate 4.3.8) 如果您配置 natural ID and then you query the entity by NaturalID Restrictions#naturalId or Session#byNaturalId,即使 table 已被修改,您也可能会访问查询缓存。

Hibernate 保持 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.

简单来说,如果 Hibernate 缓存了以下内容

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

如果具有 ID 的团队将赢得新奖杯,则查询 "from Team where trophies=10" 将不再 return。为实现这一点,Hibernate 会保留 tables 上次更新时间的记录,然后如果查询缓存条目早于此时间戳,则它不信任它的结果。

我说它不信任,因为结果仍然有效但 Hibernate 不知道,因为缓存不是每个实体,原因很明显。因此,例如,如果 ID=3 的团队将更新,则 1,2 的条目将无效,即使这些团队未更新。 更重要的是,如果 Team 1 更新但他的奖杯数与以前相同,查询缓存条目也会失效,尽管它仍然有效。

如果我们通过 NaturalID 进行查询

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

我们将使 Hibernate 能够信任查询缓存条目,即使它们不是最新的。这就像在说:"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 必须检查一些事情以查看查询是否可以避免 upToDate 检查。这可以在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)

标志是 ImmutableNaturalKeyLookup 传递给 Query#get. Let's see how StandardQueryCache 使用它。

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 

如果 isNaturalKeyLookup 为真,则不会检查 isUpToDate。

Hibernate: Cache Queries the Natural Id Way 是关于查询缓存和 NaturalID 的古老但伟大的post,将有助于理解。