最后一个方法是否阻止Hibernate为这样的实体创建代理?

a8jjtwal  于 8个月前  发布在  其他
关注(0)|答案(4)|浏览(71)

Hibernate使用代理来支持集合的延迟加载,甚至是单端关联。根据Hibernate(3.6.5)的参考文档(* 第21.1.3节,单端关联代理 *),如果代理包含“any final methods",那么Hibernate就不能构造这样的代理。
我的问题是,这个限制只适用于持久化字段的getter/setter,还是真的适用于实体类中的任何方法?所以,像这样的一个方法:

public final String toString() {
   return this.getClass().getSimpleName() + id;
}

是否真的阻止为该实体创建(CGLIB或Javassist)代理?使用基于字段的访问还是使用属性访问有关系吗?由于CGLIB被Javassist取代,这是否在这个方向上提供了更多的功能?
我喜欢在实体层次结构中使用继承,因此需要定义一些最终方法,例如,在基类中,以防止子类重写这些方法。
先谢谢你!

umuewwlo

umuewwlo1#

在Hibernate邮件列表的帮助下(感谢Emmanuel Bernardt!我能回答我自己的问题,总结如下:
Final方法一般不会阻止Hibernate创建代理,但除非这些方法不使用实体的任何状态,否则这是非常不可取的。
一些背景资料:Hibernate既不使用cglib也不使用Javassist,所以为了让代理延迟初始化其目标实体,它必须拦截任何可能使用该目标实体状态的方法。现在有一个这样的最终方法是完全可以的

public final doSomething(String a, Integer b ) {
  // do complicated stuff using only a and b (no instance members accessed!)
}

但是一旦该方法直接或通过另一示例方法使用任何持久字段,这将绕过代理,从而导致意外行为。
作为旁注,这也是为什么你不应该直接访问其他示例的字段的原因,例如在实体equals方法中:

// XXX bad code!
public boolean equals(Object o) {
  if (this == o) return true;
  if (!(o instanceof Profile)) return false;
  Profile profile = (Profile) o;
  // XXX this bypasses a possible proxy, use profile.getName() instead!
  return (name == null ? profile.name == null : name.equals(profile.name));
}
fiei3ece

fiei3ece2#

我很确定,正如参考文献所说,它适用于任何方法。实际上,在我的理解中,代理只不过是实体的子类,除了最初的实体ID之外没有状态,并且一旦初始化,它就将每个方法调用委托给实体类的实际示例。因此,它必须重写所有方法,以便

  • 必要时初始化自身
  • 将调用委托给实际的示例方法
gkn4icbw

gkn4icbw3#

受这个问题的启发,我为Intellij IDEA创建了一个插件,解决了这个问题。这就是:
https://plugins.jetbrains.com/plugin/7866
简单的描述是这样的:
Hibernate在某些情况下会默默地失败,导致难以追踪的bug。这个插件可以帮助查找和修复其中的一些问题。在设置>检查>休眠检查下,添加以下检查:·持久化类是final;·持久化类的Final方法使用直接字段访问。

附说明:

正如@BartvanHeukelom在评论中所说的那样,最终方法不能被代理的事实可以被用作一种资产:然后,您可以从getter获取实体的id,而无需初始化代理并加载其字段。这是我用途:的代码

@SuppressWarnings ("AccessingFieldFromAFinalMethodOfPersistedClass")
public final Id getId() {
    if (this instanceof HibernateProxy) return (Id)((HibernateProxy)this).getHibernateLazyInitializer().getIdentifier();
    else return id;
    }

请注意@SuppressWarnings。这是必要的,只有当你使用IntelliJ IDEA与我的插件。

ie3xauqp

ie3xauqp4#

提出了一个问题:最后一个方法是否会阻止Hibernate创建代理-不,它不会。虽然在构建ProxtFactory时,当发现最终方法时,Entity Tuplizer会发出The HHH000112: Getters of lazy classes cannot be final错误消息,但Hibernate会继续执行,并且不会标记此有问题的方法。
这是因为代理机制,org.hibernate.proxy.ProxyFactory接口的实现,是开放的配置,开发人员可以自由选择它的任何实现。默认的Hibernate代理工厂,基于Javasist,确实不会拦截final方法,但是开发人员可以实现一个自定义的代理工厂,它不仅可以“覆盖”final方法,而且可以使用字节码库(如CGLib,ByteBuddy甚至ASM本身)做许多其他事情。
要覆盖默认的Hibernate ProxyFactory,从Hibernate 5.3.9开始,开发人员可以在META-INF/services/org.hibernate.integrator.spi.Integrator类路径的文件中注册org.hibernate.integrator.spi.Integrator的自定义实现:

com.foo.FactoryIntegrator

这个Itegrator将注册自定义EntityTuplizer

public static class FactoryIntegrator implements Integrator {

    @Override
    public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {
        sessionFactory.getSessionFactoryOptions().getEntityTuplizerFactory()
            .registerDefaultTuplizerClass(EntityMode.POJO, FactoryTuplizer.class);
    }

    @Override
    public void disintegrate(SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {
    }
    
}

反过来,它会注册一个定制的代理工厂,

public class FactoryTuplizer extends PojoEntityTuplizer {

    public FactoryTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) {
        super(entityMetamodel, mappedEntity);
    }

    @Override
    protected ProxyFactory buildProxyFactoryInternal(PersistentClass persistentClass,
            Getter idGetter, Setter idSetter) {
        return new FinalOverrdingProxyFactory();
    }
    
}

public class FinalOverrdingProxyFactory implements ProxyFactory, Serializable {
    ...
}

请注意,可以重用大多数复杂的Hibernate代理逻辑,因为ProxyFactory的实现可能(而且可能应该)非常简单,因为org.hibernate.proxy.LazyInitializer的所有实现负担都可以委托给BasicLazyInitializer的抽象类BasicLazyInitializer,您的LazyInitializer实现可能会从该抽象类派生,并且您必须实现的只是代理引擎依赖的逻辑。
如果JPA层构建在Hibernate之上,则上述所有内容都适用于JPA层。

相关问题