其次,让我们来分析一下上面代码中的查询:“from Driver
driver”(假定使用了“select *”语法,但并不需要)。这个查询中没有涉及Car表格,但是却能返回可用的汽车。实际上,在这个查询中的单词“Driver”并不是指Driver表格—它指的是Driver
Java类,而这个类的确包括一个被一个司机所拥有和驾驶的汽车集合。因此,查询将直接返回你想使用的Java对象实例—根本不需要从结果集(ResultSet)转换到目标对象。而且,从一个Java对象类型(司机)到被引用的Java对象集合(汽车对象)的引用都是预先填充的。
六、深入讨论
对于上面的servlet,我们不妨做进一步讨论。首先请注意下列一行临近方法doQuery()顶部的代码:
Session session = HibernateUtil.getSessionFactory().openSession(); |
其实,我们也可以选择使用下列的语法(但是对于Tomcat的情况,我们却永远不能这样做):
Session session = HibernateUtil.getSessionFactory().getCurrentSession(); |
那么,为什么对于Tomcat时我们却不能这样做呢?在openSession()和getCurrentSession()之间的区别在于:在每次使用openSession()时,它都提供一个新的Hibernate会话。这正是我们想在我们的servlet中实现的。而相比之下,getCurrentSession则试图把一个Hibernate会话关联到一个特定的线程(Singleton-per-Thread模式)—Hibernate通过使用一个嵌入式(隐藏的)ThreadLocal实现了这一点。遗憾的是,Tomcat维持了一个线程池,并且在一个特定的Http请求使用完它以后再次重用这个给定的线程。因此,一个新的Http请求可以接收一个以前使用的线程—而它已经有一个Hibernate会话与之关联了(经由ThreadLocal);这样以来,当getCurrentSession()必须接收一个新的会话时有可能碰巧收到一个与之无关的Hibernate会话。从逻辑上讲,我们可以有一个新的Http会话和一个新的线程;但是,在物理上,我们却在重用一个现有的线程。这样以来,Tomcat
5.5.x和Hibernate 3.1都有可能会使对方感到“疑惑”。
通过使用SessionFactory.openSession,我们选择完全绕过这个问题,并且避免在一种Tomcat环境下使用SessionFactory.getCurrentSession。
【注意】上面我们所描述的冲突很容易进行试验。为此,我们可以建立一种Tomcat
5.5环境—仅允许极少数目的并发进程—比如说3或4个(你可以通过位于Tomcat
Root\conf\server.xml中的一个入口来实现这一目的。也就是,把这个文件中可用的
元素的maxThreads属性设置为3或4)。然后,创建几个不同的简单的业务事务(对话型事务或长期运行的事务)—它们将跨越Http请求进行通讯。试着把一些信息保存在一个ThreadLocal中(可以是任何信息,而且不需要与Hibernate有关;可以是进行一些跟踪/日志,用于显示线程ID)。你将会看到,Tomcat线程池最终会对另一个业务事务循环利用线程ID,从而使不恰当地存取ThreadLocal内容的情况发生。
最后,我希望你对ThreadLocal作些小研究。
七、小结
本篇向你展示了如何配置Tomcat 5.5和Hibernate 3.1以便于二者相互配合使用;同时,还讨论了如何在这种环境下实现面向对象的查询。
在接下来的最后一篇中,我们将向你展示如何在此环境下实现数据库更新;同时,还将通过一个购物车的示例来讨论有关多Http请求(Multi-Http-Request)的业务事务问题。