论坛首页 Java企业应用论坛

=== Effective Hibernate ===!!!

浏览 38569 次
该帖已经被评为精华帖
作者 正文
   发表时间:2003-12-04  
由于hibernate过于灵活,以至于就算我能写出能运行的代码,还是很怕把它用到实际的项目中,毕竟做test的小例子,性能上的差异总是看不出来。而对于原理的了解又不多,我担心自己会写出很多“能运行的”垃圾代码。
不知道大家有没有我这样的担心。

希望大家跟贴,尤其是高手多跟贴,把自己的tips,来扩充这个effective hibernate,等帖子足够长了,robbin,整理一下,考虑出书,得到的钱,
可以用来支持这个网站的建设啊。
   发表时间:2003-12-04  
Item 1:使用ThreadLocal管理session。

目的:
在完成一个不需要人机交互的事情中,尽可能的只开关session一次。

例如完成事件A,需要判断条件A,条件B,得到资源A。假设整个动作不需要人机交互,那么我把完成事件1的动作,过程写在一个DoEvent.jsp文件中。
那么DoEvent.jsp的代码框架如下:
<%
Session sess = factory.openSession();;
 Transaction tx;
 try {
     tx = sess.beginTransaction();;
    if(conditionA(););
    ...
    if(conditionB(););
    ...
    getRresouceA();;
    ...
     tx.commit();;
 }
 catch (Exception e); {
     if (tx!=null); tx.rollback();;
     throw e;
 }
 finally {
     sess.close();;
 }
%>

而在condition()和getResource()中都要getSession,来处理持久对象。
目前,我看到很多网上的sample的做法是,直接在DAO类中sf.openSession();这样将在一个jsp页面中多次开关session ,完全没有这个必要。

我曾经的做法是,在页面的顶部,得到session,然后作为参数,传给每一个需要session的方法。这样固然使的一个页面开关session一次,但是感觉不爽,凭什么让我每个dao类中要定义一个session属性呢?或者要有这么一个参数输入呢?不符合OO原则。

servlet2.3提供的ServletFilter,今天才注意到,真是汗颜
Filter能够在servlet运作前截取请求,也能让你在servlet运作后修改请求。
也就是说,让一个url请求执行之前,需要预先执行的代码,和url返回应答之后需要执行的代码写在一个地方,真是太爽了。

应此定义个DoEventFilter.java类,来统一管理session的开关。而在整个页面,任何要得到session的地方都通过ThreadLocal.get()来得到。

具体的用法可以翻书或者在论坛里找,都有。这里只是告诉那些以前从网上学来的将session的开关放在dao类的兄弟们,和那些和我一样将session作为参数传入的兄弟们,那样做不effective。这样更好。

=----
抛砖头引玉。。。。
0 请登录后投票
   发表时间:2003-12-04  
对,我同意你的意见,其实Hibernate的灵活性也是一把双刃剑,用好了就特别舒服,用不好,就特别头疼。把我原来在jdon一个帖子转过来,谈到了这个问题:

一、Hibernate是JDBC的轻量级的对象封装,它是一个独立的对象持久层框架,和App Server,和EJB没有什么必然的联系。Hibernate可以用在任何JDBC可以使用的场合,例如Java应用程序的数据库访问代码,DAO接口的实现类,甚至可以是BMP里面的访问数据库的代码。从这个意义上来说,Hibernate和EB不是一个范畴的东西,也不存在非此即彼的关系。

二、Hibernate是一个和JDBC密切关联的框架,所以Hibernate的兼容性和JDBC驱动,和数据库都有一定的关系,但是和使用它的Java程序,和App Server没有任何关系,也不存在兼容性问题。

三、Hibernate不能用来直接和Entity Bean做对比,只有放在整个J2EE项目的框架中才能比较。并且即使是放在软件整体框架中来看,Hibernate也是做为JDBC的替代者出现的,而不是Entity Bean的替代者出现的,让我再列一次我已经列n次的框架结构:

传统的架构:
1) Session Bean &lt;-&gt; Entity Bean &lt;-&gt; DB
为了解决性能障碍的替代架构:
2) Session Bean &lt;-&gt; DAO &lt;-&gt; JDBC &lt;-&gt; DB
使用Hibernate来提高上面架构的开发效率的架构:
3) Session Bean &lt;-&gt; DAO &lt;-&gt; Hibernate &lt;-&gt; DB

就上面3个架构来分析:
1、内存消耗:采用JDBC的架构2无疑是最省内存的,Hibernate的架构3次之,EB的架构1最差。

2、运行效率:如果JDBC的代码写的非常优化,那么JDBC架构运行效率最高,但是实际项目中,这一点几乎做不到,这需要程序员非常精通JDBC,运用Batch语句,调整PreapredStatement的Batch Size和Fetch Size等参数,以及在必要的情况下采用结果集cache等等。而一般情况下程序员是做不到这一点的。因此Hibernate架构表现出最快的运行效率。EB的架构效率会差的很远。

3、开发效率:在有JBuilder的支持下以及简单的项目,EB架构开发效率最高,JDBC次之,Hibernate最差。但是在大的项目,特别是持久层关系映射很复杂的情况下,Hibernate效率高的惊人,JDBC次之,而EB架构很可能会失败。

4、分布式,安全检查,集群,负载均衡的支持
由于有SB做为Facade,3个架构没有区别。

四、EB和Hibernate学习难度在哪里?

EB的难度在哪里?不在复杂的XML配置文件上,而在于EB运用稍微不慎,就有严重的性能障碍。所以难在你需要学习很多EJB设计模式来避开性能问题,需要学习App Server和EB的配置来优化EB的运行效率。做EB的开发工作,程序员的大部分精力都被放到了EB的性能问题上了,反而没有更多的精力关注本身就主要投入精力去考虑的对象持久层的设计上来。

Hibernate难在哪里?不在Hibernate本身的复杂,实际上Hibernate非常的简单,难在Hibernate太灵活了。

当你用EB来实现持久层的时候,你会发现EB实在是太笨拙了,笨拙到你根本没有什么可以选择的余地,所以你根本就不用花费精力去设计方案,去平衡方案的好坏,去费脑筋考虑选择哪个方案,因为只有唯一的方案摆在你面前,你只能这么做,没得选择。

Hibernate相反,它太灵活了,相同的问题,你至少可以设计出十几种方案来解决,所以特别的犯难,究竟用这个,还是用那个呢?这些方案之间到底有什么区别呢?他们的运行原理有什么不同?运行效率哪个比较好?光是主键生成,就有七八种方案供你选择,你为难不为难?集合属性可以用Set,可以用List,还可以用Bag,到底哪个效率高,你为难不为难?查询可以用iterator,可以用list,哪个好,有什么区别?你为难不为难?复合主键你可以直接在hbm里面配置,也可以自定义CustomerType,哪种比较好些?你为难不为难?对于一个表,你可以选择单一映射一个对象,也可以映射成父子对象,还可以映射成两个1:1的对象,在什么情况下用哪种方案比较好,你为难不为难?

这个列表可以一直开列下去,直到你不想再看下去为止。当你面前摆着无数的眼花缭乱的方案的时候,你会觉得幸福呢?还是悲哀呢?如果你是一个负责的程序员,那么你一定会仔细研究每种方案的区别,每种方案的效率,每种方案的适用场合,你会觉得你已经陷入进去拔不出来了。如果是用EB,你第一秒种就已经做出了决定,根本没得选择,比如说集合属性,你只能用Collection,如果是Hibernate,你会在Bag,List和Set之间来回犹豫不决,甚至搞不清楚的话,程序都没有办法写。
0 请登录后投票
   发表时间:2003-12-04  
同意robbin的看法,EB真的让我没有精力放在持久对象的设计上,hibernate可以让我安心的对我的持久架构进行设计。

一些原子操作可以放在DAO里面,比如单条的retrive , update, save, remove, query方法都可以,而这些方法都可以包装成一个BaseDAOImpl以供继承,还可以在里面提供事务的开始结束方法,方法如下:
其中HibernateSession里面的方法见hibernate DOC的ThreadLocal模式

public class BaseDAOImpl {

  protected Session session;
  protected Transaction txc;

  public BaseDAOImpl(); throws HibernateException {
    initHibernateSession();;
  }

  /**
   * init Session
   * @throws HibernateException
   */
  protected void initHibernateSession(); throws HibernateException {
    session = HibernateSession.currentSession();;
  }

  /**
   *delete an object from database
   */
  protected void removeObject(Class clazz, Integer id); throws LogException {
    try {
      // Object must originate in this session
      Object obj = session.load(clazz, id);;
      session.delete(obj);;
      session.flush();;
    }
    catch (HibernateException e); {
      try {
        session.connection();.rollback();;
      }
      catch (Exception ex); {
        e.printStackTrace();;
      }

      throw new LogException(e);;
    }
  }

  /**
   * delete an Object from database
   */
  protected void removeObject(Class clazz, Long id); throws
      LogException {
    try {
      // Object must originate in this session
      Object obj = session.load(clazz, id);;
      session.delete(obj);;
      session.flush();;
    }
    catch (Exception e); {
      try {
        session.connection();.rollback();;
      }
      catch (Exception ex); {
        e.printStackTrace();;
      }
      throw new LogException(e);;
    }
  }

  /**
   * load an Object from Session
   */
  protected Object retrieveObject(Class clazz, Integer id); throws
      LogException {
    Object obj = null;

    try {
      obj = session.load(clazz, id);;
    }
    catch (Exception e); {
      throw new LogException(e);;
    }

    return obj;
  }

  /**
   * load an Object from Session
   */
  protected Object retrieveObject(Class clazz, Long id); throws
      LogException {
    Object obj = null;

    try {
      obj = session.load(clazz, id);;
    }
    catch (Exception e); {
      throw new LogException(e);;
    }

    return obj;
  }

  /**
   * store an object in the database
   *
   */
  protected void storeObject(Object obj); throws LogException {
    try {
      session.saveOrUpdate(obj);;
      session.flush();;
    }
    catch (Exception e); {
      try {
        txc.rollback();;
      }
      catch (Exception ex); {
        e.printStackTrace();;
      }

      ;
      throw new LogException(e);;
    }
  }

  /**
   *save an object to the database
   */
  protected void saveObject(Object obj); throws LogException {
    try {
      session.save(obj);;
      session.flush();;
    }
    catch (Exception e); {
      try {
        txc.rollback();;
      }
      catch (HibernateException ex); {
        e.printStackTrace();;
      }
      throw new LogException(e);;
    }
  }

  /**
   *update an object to the database
   */
  protected void updateObject(Object obj); throws LogException {
    try {
      session.update(obj);;
      session.flush();;
    }
    catch (Exception e); {
      try {
        txc.rollback();;
      }
      catch (Exception ex); {
        e.printStackTrace();;
      }
      throw new LogException(e);;
    }
  }

  /**
   * begin a Transaction
   * @throws HibernateException
   */
  protected  void beginTransaction(); throws HibernateException {
    txc = session.beginTransaction();;
  }

  /**
   * 如果commit为真 则执行事务 否则回滚
   * @param commit
   * @throws HibernateException
   */
  protected  void endTransaction(boolean commit); throws HibernateException {
    if (commit); {
      txc.commit();;
    }
    else {
      txc.rollback();;
    }
   // session.close();;
  }



}


不过就是有个问题 如果采用ThreadLocal模式session.close()之后查询得到的数据如果有带有Lazy的collection那么客户端就没有办法完整取到对象,所以ThreadLocal模式里面的session是否关闭和什么时候关闭一直困扰着我。
0 请登录后投票
   发表时间:2003-12-04  
还有一个微小的应用:
class Foo {
  Set bars = new HashSet();;
  public Set getBars(); {
    return bars;
  }

  public void setBars(Set bars); {
    this.bars = bars;
  }
...
}

可以加上add,remove方法,如下
class Foo {
  Set bars = new HashSet();;
  public Set getBars(); {
    return bars;
  }

  public void setBars(Set bars); {
    this.bars = bars;
  }

  public void addBar(Bar bar); {
    bar.setFoo(this);;
    bars.add(bar);;
  }

  public void removeBar(Bar bar); {
    bar.setFoo(null);;
    bars.remove(bar);;
  }
}
...

其实本来setBars(Set bar)方法都可以不要了,但是hibernate的property一定要setter方法。。希望以后hibernate可以支持property的setter,getter方法为空。
这样做一个是为了方便可以直接对对象里面的Set进行添加,不用再新建一个Collection。看的清晰。
还有一个方面为了不用调用SetBars(Set bar)方法以防止调用完SetBars(Set bar)方法之后Set又被改动..造成数据的不预期变化。。
0 请登录后投票
   发表时间:2003-12-05  
==???===
为什么还是在DAO中close session?
==================
protected  void endTransaction(boolean commit) throws HibernateException {
    if (commit) {
      txc.commit();
    }
    else {
      txc.rollback();
    }
   session.close();
  }
0 请登录后投票
   发表时间:2003-12-05  
呵呵 那句应该去掉。。
0 请登录后投票
   发表时间:2003-12-06  
楼上的兄弟,我觉得你那样写的DAO父类,代码重用性并不高。
举一个实际的例子书店和书,一个书店有很多本书。Book ,BookStore。

那么对由于书店和书是一对多的关系。模仿GavinKing的代码范例。添加一个书店的代码是:
public BookStore createBookStore(String name); throws HibernateException {
		BookStore bs = new BookStore();;
		bs.setBookStoreName(name);;
		bs.setBooks(new TreeSet(););;
		try {
			beginTransaction();;
			session.save(bs);;
			commit();;
		}catch (HibernateException he); {
			rollback();;
			throw he;
		}
		return bs;
	}


而为某个书店添加一本书的的代码是:
public Book addBooks(Long bsId,String bookName); throws HibernateException {
		Book b = new Book();;
		b.setBookName(bookName);;	
		try {
			beginTransaction();;
			BookStore bs = (BookStore);session.load(BookStore.class,bsId);;
			b.setBookStore(bs);;
			bs.getBooks();.add(b);;
			session.update(bs);;
			commit();;
		}catch (HibernateException he); {
			rollback();;
			throw he;
		}
		return b;
	}

如果情况更复杂,那么你写的DAO类中的,add delete upate之类的方法都很难复用。

我建议DAO的父类这样就够了:
public class DAO {
	protected Session session; 
	protected Transaction tx;
	
	protected DAO(); throws HibernateException{
		init();;
	}
	protected void init(); throws HibernateException { 
		session = ThreadLocalSession.currentSession();; 
 	} 
	protected void beginTransaction(); throws HibernateException { 
		tx = session.beginTransaction();; 
  	}
	protected void rollback(); throws HibernateException { 
		if(tx!=null);{
			tx.rollback();; 
		} 
	}
	protected void commit(); throws HibernateException { 
		if(tx!=null);{
			tx.commit();; 
		} 
	}
}


btw=============
从book的add方法,可以看出以往的DAO父类中的方法很明显的在操作数据库,而非持久对象,而通过hibernate,操作的是持久对象。而持久对象如何将自己和数据库保持一致,不是我们编码关心的问题--这也是hibernate的价值存在。我自己写函数的时候总是会自然的想,这个函数对数据库改变了什么。
我要让自己慢慢的只是思考,这个函数对持久对象做了什么。
0 请登录后投票
   发表时间:2003-12-06  
我觉得你上面的两个方法没什么必要

createBook(String name) ??

强烈反对这样添加一个对象
要是book有很多属性你怎么传
这个时候当然要传createBook(Book book)
而Book客户端就要构造好
同理updateBook(Book book) 就可以了

入口参数多的时候应该考虑以一个对象包装这些参数

而在这种情况下PO是最好的包装对象。。

所以我认为基本Object的这些方法还是有必要抽出来放在SuperClass里面的
0 请登录后投票
   发表时间:2003-12-06  
getdown 写道
我觉得你上面的两个方法没什么必要

你说的是commit(),rollback()。。那几个方法吗?
的确,可有可无,不过那是我目前觉得唯一应该放在父类中的方法。
但是毕竟还是复用了一些。10000块钱是攒,5块钱也是攒嘛:)

getdown 写道

createBook(String name) ??

强烈反对这样添加一个对象
要是book有很多属性你怎么传
这个时候当然要传createBook(Book book)
而Book客户端就要构造好
同理updateBook(Book book) 就可以了

入口参数多的时候应该考虑以一个对象包装这些参数

而在这种情况下PO是最好的包装对象。。

所以我认为基本Object的这些方法还是有必要抽出来放在SuperClass里面的

你说的对,不过我要拿名人来压你了:)
Gavinking 的sample代码就是这样写的:)
我自己觉得,重载几个同名方法就可以。这倒不是什么大的冲突。
一般来说
createBook(Book b);
createBook(String bookname);
都有的,也各有优势。

注意,是否能复用父类的代码,问题不在是否传入bookname还是传入Book对象,而是当一对多这中情况中,addBook(BookStore bs,,,)--还要传入Bookstore对象作为参数的。这样一来,add一个book和add一个bookStore就差别比较大了,当然你还是可以绕个弯来实现复用,但是有点得不偿失。

我印象中有不少得帖子不推荐使用DAO再次封装hibernate 对数据库得操作。
现在觉得似乎有点道理,不过我还是不喜欢把BO得代码写在PO里面,更愿意单独写一个Manager类。现在我也感觉要为Manager抽象出一个父类DAO来,发现代码重用得并不多。
--也许DAO典型写法在这里并不是特别适合hibernate。

另外,有一个hibernate成功案例,http://forum.hibernate.org.cn/viewtopic.php?t=1816
开发小组经过讨论得结果也是没有使用dAO,而是直接将BO写在PO中。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics