Kava Pava Gava Tava Nava Zava Java

everything about Java
随笔 - 15, 文章 - 0, 评论 - 1, 引用 - 0
数据加载中……

Hibernate 的 Proxy 陷阱

对于使用框架,当然越是透明越好。在这一点上,Hibernate 做的相当不错,算是非常透明的了。但透明并不是免费的 (Freedom is not free) 。每个框架或多或少总得有些墙,而墙也变得透明的时候,我们通常只有在一头撞上去的时候才不得不付出代价。那个时候,我们才会揉着脑袋说 - 哦,原来这里还有一堵墙啊。。。

闲话少说。本文是阅读下面这篇文章的总结。

http://blog.xebia.com/2008/03/08/advanced-hibernate-proxy-pitfalls/

首先,什么是Proxy?这个可以参考一下GoF (著名的软件“四人帮”Gang of Four)提出的《设计模式》。简而言之,就是一个子类,在父类的基础上,实现一点附加功能,而将其他请求全都交给父类去实现,因而从用户角度看子类可以完成全部父类的功能以及子类自己附加的功能,所以可以代替父类使用。

Hibernate 使用 Proxy 实现 Lazy Loading 功能。 比如 Person 类有个成员,也是 Person 类,叫做 boss。如果使用 Lazy Loading,在加载一个 Person 的时候,不会自动加载其 boss 的数据,Hibernate 会自动给 Person 类生成一个 Proxy,将其 boss 的 ID 付给这个 Proxy。只有在访问 boss 的具体数据的时候,这个 Proxy 才会自动调取数据。

一个有趣的现象。如果我们使用图形化调试器(比如 Eclipse)。当我们点开调试器中的 boss 的实例查看其数据的时候,这个 Proxy 就会去调取数据。这可能会使得调试和实际执行的行为不一致。

陷阱1:直接访问成员变量

Proxy 会将函数访问转到其父类的同样函数处理。但是 Proxy 自己是不会持有数据的,其自己的成员变量(比如 boss 的姓名)都是 null。 比如,一个 Person 的 equals 函数可能会这样写:

  @Override
  
public boolean equals(Object obj) {
    
if (obj == null) {
      
return false;
    }
    
if (obj == this) {
      
return true;
    }
    
if (!(obj instanceof Person)) {
      
return false;
    }
    
return name.equals((Person)obj).name);
  }


上面高亮的那句话看起来没什么问题,也是常见写法。可是,当被传进来的 obj 可能是个 Proxy 的时候,直接访问 obj 的 name 成员变量就不对了 - 因为这永远是 null。因此,访问另一个 obj 的成员变量时要用 getter / setter

陷阱2:instanceOf

如果用 instanceOf 判断一个对象是否是 Person 类,那么就没有考虑到 Proxy 的情况。文章给出的建议是关掉 lazy loading,恐怕不是个很好的建议。可以考虑用 Class.isAssignableFrom 吧。

后记:

文章后面有些讨论,就不再详细讨论或者翻译了。有下面几点可能有帮助(很有些人说了些 Hibernate 的风凉话呢):

“@Peter: Actually, you don’t need to write equals/hashcode for proxies for that reason: Hibernate will enforce that every entity is represented by a single instance in a session, also for proxies. It will lookup a proxy instance if needed in an internal map, that is bound to the session.”


“To know the class of the proxy object, you can use the method
Class getClassWithoutInitializingProxy(Object object)
form org.hibernate.proxy.HibernateProxyHelper
/**
* Get the class of an instance or the underlying class
* of a proxy (without initializing the proxy!). It is
* almost always better to use the entity name!
*/”

“Actually there are some architectural problems resulting from pitfall 2.
There are some cases where you don’t want to expose a (visible) setter which could initialize the object…
Read here:
http://www.bb242.de/2008/04/02/how-to-live-with-hibernate-proxies/

A pretty neat workaround some things is to work with get() instead of load().”

“Regarding Proxies in general: The Hibernate approach is the we-can-implement-it-on-a-weekend approach. Whereas JDO has featured a smarter approach (available in many JPA implementations), i.e. use byte code enhancement to implement the proxy functionality in the class itself, which solves most of the problems. (But you can’t design and implement that on a weekend.)”


“Bytecode-enhancing the actual class would be great but in runtime is hard to do because they need to be enhanced before they are ever used which is complex.

A simpler alternative is to create a proxy for the actual object itself and *not* have the “target” kludge. Simply use the fields in the proxy directly. This still does not properly solve the inheritance scheme but that is impossible with lazy loading anyway.

The simpler alternative has way less problems than the current implementation and *can* be built in an afternoon (my replacement for Hibernate called SPF has it, and it took that long ;-)

Sadly, Hibernate sucks bigtime and is not a good persistence framework at all; it is just the one that sucks the least…

posted on 2010-01-11 12:15 bing 阅读(3530) 评论(0)  编辑  收藏 所属分类: Hibernate


只有注册用户登录后才能发表评论。


网站导航: