实体污点检查的选项

发布者    |       Hibernate ORM

在刷新时,Hibernate需要知道哪些实体状态已变为污点(已更改),以便知道需要写入数据库的数据以及不需要写入的数据。历史上Hibernate仅定义了一种这样的污点检查方式,但后来增加了多种额外的方案,这些方案似乎并不那么为人所知。本博客的目的是开始改善关于污点检查和这些各种选项的文档。

刷新时状态比较策略

在这种方案中,实体的“加载状态”被保留为持久化上下文(Session/EntityManager)的一部分。这种加载状态是实体数据在数据库中的最后已知状态。这发生在实体被加载或从该持久化上下文更新实体时。但无论如何,我们在这次事务中都有实体在数据库中的“最后、已知状态”。

因此,在刷新时,该策略会在加载状态和实体的当前状态之间执行深度比较。这在每次刷新时都会对与持久化上下文关联的每个实体进行,这在长时间运行的持久化上下文和批量处理用例中可能会对性能产生一些影响。事实上,这就是为什么文档讨论了在批量处理用例中清除的重要性,但同样这也适用于长时间运行的持久化上下文。缓解这种策略性能影响的最佳方法之一是

  1. 最小化给定持久化上下文中发生的刷新次数(始终是一个好主意)
  2. 最小化在每次刷新期间与持久化上下文关联的实体数量

这样的策略属于我称之为计算的污点检查类别。最初,Hibernate只支持这种策略。然而,甚至在3.0版本(距今已有8-9年)时,Hibernate就开始转向“跟踪”污点检查策略。最知名的一般方法是通过字节码增强来实现。

跟踪方法:字节码增强

Hibernate首次尝试跟踪策略是通过字节码增强实现的,这是大多数其他JPA提供者进行跟踪脏检查的方式。这是一个过程,其中读取并增强(更改)您的Java类的字节码以引入新的字节码级别指令。Hibernate将其作为Ant任务提供,并通过运行时增强提供支持,尽管运行时增强目前需要JPA容器和容器管理的EntityManagerFactory引导。基本上,Hibernate不提供在类加载时执行增强的Java代理。我们并不是特别反对它,只是我们没有时间,也没有人为此做出贡献;尽管如果你感兴趣的话... ;)

增强会在实体本身中编织支持,以跟踪其实体状态何时发生变化。在刷新时间,我们就可以检查这个标志,而不是执行状态比较计算。

要使用Ant任务应用构建时增强,您需要指定Ant任务如下

<target name="instrument" depends="compile">
    <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask">
        <classpath ... >
    </taskdef>

    <instrument verbose="true">
        <fileset dir="${yourClassesDir}">
            <include name="*.class"/>
        </fileset>
    </instrument>
</target>

它会对您的类执行增强/仪器操作,正如嵌套的Ant文件集所定义的那样。重要的是,用于定义任务的类路径中包含Hibernate jar、所有依赖项以及您的类。

要使用运行时增强,只需启用设置hibernate.ejb.use_class_enhancertrue。同样,这也要求您的应用程序使用JPA容器管理的EMF引导。

还有第三方Maven插件支持在Maven构建中使用Hibernate字节码增强。

请注意,目前正在对字节码增强支持进行改进(实际上,其第一步已经在4.2版本中实现)。有关详细信息,请参阅HHH-7667HHH-7963

跟踪方法:代理

4.1版本中添加的一个新功能允许将所有脏检查委托给应用程序代码。请参阅HHH-3910HHH-6998以获取完整的设计讨论。但基本上,这个特性允许您的应用程序控制脏检查如何进行。想法是,您的应用程序模型类正在监控它们自己的脏状态。想象一个实体类如下:

@Entity
public class MyEntity {
    private Long id;
    private String name;

    @Id 
    public Long getId() { ... }
    public void setId(Long id) { ... }

    public Long getName() { ... }
    public void setName(String name) {
        if ( ! areEqual( this.name, name ) ) {
            trackChange( "name", this.name );
        }
        this.name = name;
    }

    private Map<String,?> tracker;

    private void trackChange(String attributeName, Object value) {
        if ( tracker == null ) {
            tracker = new HashMap<String,Object>();
        }
        else if ( tracker.containsKey( attributeName ) {
            // no need to re-put, we want to keep the original value
            return;
        }
        tracker.put( attributeName, value );
    }

    public boolean hadDirtyAttributes() {
        return tracker != null && ! tracker.isEmpty();
    }

    public Set<String> getDirtyAttributeNames() {
        return tracker.keySet();
    }

    public void resetDirtiness() {
        if ( tracker != null ) {
            tracker.clear();
        }
    }
}

使用4.1版本中作为HHH-3910一部分引入的org.hibernate.CustomEntityDirtinessStrategy我们可以轻松地将实体的内在脏检查与Hibernate的脏检查联系起来

public class CustomEntityDirtinessStrategyImpl implements CustomEntityDirtinessStrategy {
    @Override
    public boolean canDirtyCheck(Object entity, EntityPersister persister, Session session) {
        // we only manage dirty checking for MyEntity instances (for this example; a CustomEntityDirtinessStrategy
        // manage dirty checking for any number of entity types).
        return MyEntity.class.isInstance( entity );
    }

    @Override
    public boolean isDirty(Object entity, EntityPersister persister, Session session) {
        return ( (MyEntity) entity ).hadDirtyAttributes();
    }

    @Override
    public void findDirty(Object entity, EntityPersister persister, Session session, DirtyCheckContext dirtyCheckContext) {
        final MyEntity myEntity = (MyEntity) entity;
        final Set<String> dirtyAttributeNames = entity.getDirtyAttributeNames();

        dirtyCheckContext.doDirtyChecking(
                new AttributeChecker() {
                        @Override
                        public boolean isDirty(AttributeInformation attributeInformation) {
                            return dirtyAttributeNames.contains( attributeInformation.getName() );
                        }
                }
        );
    }

    @Override
    public void resetDirty(Object entity, EntityPersister persister, Session session) {
        return ( (MyEntity) entity ).resetDirtiness();
    }
}

这种代理甚至可以与您应用于领域模型的某些自定义字节码增强结合使用,以编织脏跟踪。这也非常适合动态模型(使用基于Map的代理等)。

目前可以有一个CustomEntityDirtinessStrategy与SessionFactory/EntityManagerFactory关联。要使用的是由CustomEntityDirtinessStrategyhibernate.entity_dirtiness_strategy设置定义的实现。

结论

希望这能更清楚地展示在Hibernate中管理脏检查的可能性。如果还有不清楚的地方,请在评论中讨论,我会尝试将所有建设性的批评和建议汇总到文档中。


返回顶部