1.2.6. 加载并存储对象

我们终于可以使用Hibernate来加载和存储对象了,编写一个带有main()方法的EventManager类:

  1. package events;
  2. import org.hibernate.Session;
  3. import java.util.Date;
  4. import util.HibernateUtil;
  5. public class EventManager {
  6. public static void main(String[] args) {
  7. EventManager mgr = new EventManager();
  8. if (args[0].equals("store")) {
  9. mgr.createAndStoreEvent("My Event", new Date());
  10. }
  11. HibernateUtil.getSessionFactory().close();
  12. }
  13. private void createAndStoreEvent(String title, Date theDate) {
  14. Session session = HibernateUtil.getSessionFactory().getCurrentSession();
  15. session.beginTransaction();
  16. Event theEvent = new Event();
  17. theEvent.setTitle(title);
  18. theEvent.setDate(theDate);
  19. session.save(theEvent);
  20. session.getTransaction().commit();
  21. }
  22. }

我们创建了个新的Event对象并把它传递给Hibernate。现在Hibernate负责与SQL打交道,并把INSERT命令传给数据库。在运行之前,让我们看一下处理SessionTransaction的代码。

一个Session就是个单一的工作单元。我们暂时让事情简单一些,并假设HibernateSession和数据库事务是一一对应的。为了让我们的代码从底层的事务系统中脱离出来(此例中是JDBC,但也可能是JTA),我们使用Hibernate Session中的Transaction API。

sessionFactory.getCurrentSession()是干什么的呢?首先,只要你持有SessionFactory(幸亏我们有HibernateUtil,可以随时获得),大可在任何时候、任何地点调用这个方法。getCurrentSession()方法总会返回“当前的”工作单元。记得我们在hibernate.cfg.xml中把这一配置选项调整为"thread"了吗?因此,因此,当前工作单元被绑定到当前执行我们应用程序的Java线程。但是,这并非是完全准确的,你还得考虑工作单元的生命周期范围 (scope),它何时开始,又何时结束.

Session在第一次被使用的时候,即第一次调用getCurrentSession()的时候,其生命周期就开始。然后它被Hibernate绑定到当前线程。当事务结束的时候,不管是提交还是回滚,Hibernate会自动把Session从当前线程剥离,并且关闭它。假若你再次调用getCurrentSession(),你会得到一个新的Session,并且开始一个新的工作单元。这种线程绑定(thread-bound)的编程模型(model)是使用Hibernate的最广泛的方式,因为它支持对你的代码灵活分层(事务划分可以和你的数据访问代码分离开来,在本教程的后面部分就会这么做)。

和工作单元的生命周期这个话题相关,Hibernate Session是否被应该用来执行多次数据库操作?上面的例子对每一次操作使用了一个Session,这完全是巧合,这个例子不是很复杂,无法展示其他方式。Hibernate Session的生命周期可以很灵活,但是你绝不要把你的应用程序设计成为每一次数据库操作都用一个新的Hibernate Session。因此就算下面的例子(它们都很简单)中你可以看到这种用法,记住每次操作一个session是一个反模式。在本教程的后面会展示一个真正的(web)程序。

关于事务处理及事务边界界定的详细信息,请参看第 11 章 事务和并发。在上面的例子中,我们也忽略了所有的错误与回滚的处理。

为第一次运行我们的程序,我们得在Ant的build文件中增加一个可以调用得到的target。

  1. <target name="run" depends="compile">
  2. <java fork="true" classname="events.EventManager" classpathref="libraries">
  3. <classpath path="${targetdir}"/>
  4. <arg value="${action}"/>
  5. </java>
  6. </target>

action参数(argument)的值是通过命令行调用这个target的时候设置的:

  1. C:\hibernateTutorial\>ant run -Daction=store

你应该会看到,编译以后,Hibernate根据你的配置启动,并产生一大堆的输出日志。在日志最后你会看到下面这行:

  1. [java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)

这是Hibernate执行的INSERT命令,问号代表JDBC的绑定参数。如果想要看到绑定参数的值或者减少日志的长度,就要调整你在log4j.properties文件里的设置。

我们想要列出所有已经被存储的events,就要增加一个条件分支选项到main方法中去。

  1. if (args[0].equals("store")) {
  2. mgr.createAndStoreEvent("My Event", new Date());
  3. }
  4. else if (args[0].equals("list")) {
  5. List events = mgr.listEvents();
  6. for (int i = 0; i < events.size(); i++) {
  7. Event theEvent = (Event) events.get(i);
  8. System.out.println("Event: " + theEvent.getTitle() +
  9. " Time: " + theEvent.getDate());
  10. }
  11. }

我们也增加一个新的listEvents()方法:

  1. private List listEvents() {
  2. Session session = HibernateUtil.getSessionFactory().getCurrentSession();
  3. session.beginTransaction();
  4. List result = session.createQuery("from Event").list();
  5. session.getTransaction().commit();
  6. return result;
  7. }

我们在这里是用一个HQL(Hibernate Query Language-Hibernate查询语言)查询语句来从数据库中加载所有存在的Event对象。Hibernate会生成适当的SQL,把它发送到数据库,并操作从查询得到数据的Event对象。当然,你可以使用HQL来创建更加复杂的查询。

现在,根据以下步骤来执行并测试以上各项:

  • 运行ant run -Daction=store来保存一些内容到数据库。当然,先得用hbm2ddl来生成数据库schema。

  • 现在把hibernate.cfg.xml文件中hbm2ddl属性注释掉,这样我们就取消了在启动时用hbm2ddl来生成数据库schema。通常只有在不断重复进行单元测试的时候才需要打开它,但再次运行hbm2ddl会把你保存的一切都删掉(drop)——create配置的真实含义是:“在创建SessionFactory的时候,从schema 中drop 掉所有的表,再重新创建它们”。

如果你现在使用命令行参数-Daction=list运行Ant,你会看到那些至今为止我们所储存的events。当然,你也可以多调用几次store以保存更多的envents。

注意,很多Hibernate新手在这一步会失败,我们不时看到关于Table not found错误信息的提问。但是,只要你根据上面描述的步骤来执行,就不会有这个问题,因为hbm2ddl会在第一次运行的时候创建数据库schema,后继的应用程序重起后还能继续使用这个schema。假若你修改了映射,或者修改了数据库schema,你必须把hbm2ddl重新打开一次。