🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 19.1.3\. 单端关联代理(Single-ended association proxies) 在Hinerbate中,对集合的延迟抓取的采用了自己的实现方法。但是,对于单端关联的延迟抓取,则需要采用 其他不同的机制。单端关联的目标实体必须使用代理,Hihernate在运行期二进制级(通过优异的CGLIB库), 为持久对象实现了延迟载入代理。 默认的,Hibernate3将会为所有的持久对象产生代理(在启动阶段),然后使用他们实现 `多对一(many-to-one)`关联和`一对一(one-to-one)` 关联的延迟抓取。 在映射文件中,可以通过设置`proxy`属性为目标class声明一个接口供代理接口使用。 默认的,Hibernate将会使用该类的一个子类。 _注意:被代理的类必须实现一个至少包可见的默认构造函数,我们建议所有的持久类都应拥有这样的构造函数_ 在如此方式定义一个多态类的时候,有许多值得注意的常见性的问题,例如: ``` <class name="Cat" proxy="Cat"> ...... <subclass name="DomesticCat"> ..... </subclass> </class> ``` 首先,`Cat`实例永远不可以被强制转换为`DomesticCat`, 即使它本身就是`DomesticCat`实例。 ``` Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db) if ( cat.isDomesticCat() ) { // hit the db to initialize the proxy DomesticCat dc = (DomesticCat) cat; // Error! .... } ``` 其次,代理的“`==`”可能不再成立。 ``` Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy DomesticCat dc = (DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy! System.out.println(cat==dc); // false ``` 虽然如此,但实际情况并没有看上去那么糟糕。虽然我们现在有两个不同的引用,分别指向这两个不同的代理对象, 但实际上,其底层应该是同一个实例对象: ``` cat.setWeight(11.0); // hit the db to initialize the proxy System.out.println( dc.getWeight() ); // 11.0 ``` 第三,你不能对“final类”或“具有final方法的类”使用CGLIB代理。 最后,如果你的持久化对象在实例化时需要某些资源(例如,在实例化方法、默认构造方法中), 那么代理对象也同样需要使用这些资源。实际上,代理类是持久化类的子类。 这些问题都源于Java的单根继承模型的天生限制。如果你希望避免这些问题,那么你的每个持久化类必须实现一个接口, 在此接口中已经声明了其业务方法。然后,你需要在映射文档中再指定这些接口。例如: ``` <class name="CatImpl" proxy="Cat"> ...... <subclass name="DomesticCatImpl" proxy="DomesticCat"> ..... </subclass> </class> ``` 这里`CatImpl`实现了`Cat`接口, `DomesticCatImpl`实现`DomesticCat`接口。 在`load()`、`iterate()`方法中就会返回 `Cat`和`DomesticCat`的代理对象。 (注意`list()`并不会返回代理对象。) ``` Cat cat = (Cat) session.load(CatImpl.class, catid); Iterator iter = session.iterate("from CatImpl as cat where cat.name='fritz'"); Cat fritz = (Cat) iter.next(); ``` 这里,对象之间的关系也将被延迟载入。这就意味着,你应该将属性声明为`Cat`,而不是`CatImpl`。 但是,在有些方法中是_不需要_使用代理的。例如: * `equals()`方法,如果持久类没有重载`equals()`方法。 * `hashCode()`方法,如果持久类没有重载`hashCode()`方法。 * 标志符的getter方法。 Hibernate将会识别出那些重载了`equals()`、或`hashCode()`方法的持久化类。 若选择`lazy="no-proxy"`而非默认的`lazy="proxy"`,我们可以避免类型转换带来的问题。然而,这样我们就需要编译期字节码增强,并且所有的操作都会导致立刻进行代理初始化。