Hibernate学习笔记(1)

来源:互联网 发布:苹果打电话录音软件 编辑:程序博客网 时间:2024/09/21 09:01
[Hibernate参考文档]学习笔记
作者:ITRunner 时间:2005-7 来源:Buaa

  • Hibernate提供的默认数据库连接池,一般仅用于开发调试,在实际中一般推荐使用来自Apache的dbcp。
  • Configuration:      负责管理Hibernate的配置信息。如文件(hibernate.cfg.xml)
  • SessionFactory:    中保存了对应当前数据库配置的所有映射关系,同时也负责当前的二级数据缓存和Statement Pool。氢它的创建过程非常复杂、代价高昂。由于采取了线程安全的设计,可由多个线程并发调用,大多数情况下,一个应用针对一个数据库共享一个SessionFactory实例即可。
  • Session:                是Hibernate持久化操作的基础。

    在今日的企业环境中,把面向对象的软件和关系数据库一起使用可能是相当麻烦、浪费时间的。Hibernate是一个面向Java环境的对象/关系数据库映射工具。对象/关系数据库映射(object/relational mapping (ORM))这个术语表示一种技术,用来把对象模型表示的对象映射到基于SQL的关系模型数据结构中去。

    Hibernate不仅仅管理Java类到数据库表的映射(包括Java数据类型到SQL数据类型的映射),还提供数据查询和获取数据的方法,可以大幅度减少开发时人工使用SQL和JDBC处理数据的时间。

    Hibernate的目标是对于开发者通常的数据持久化相关的编程任务,解放其中的95%。对于以数据为中心的程序来说,它们往往只在数据库中使用存储过程来实现商业逻辑,Hibernate可能不是最好的解决方案;对于那些在基于Java的中间层应用中,它们实现面向对象的业务模型和商业逻辑的应用,Hibernate是最有用的。不管怎样,Hibernate一定可以帮助你消除或者包装那些针对特定厂商的SQL代码,并且帮你把结果集从表格式的表示形式转换到一系列的对象去。



  我们来配置在Tomcat和Hibernate中共用的数据库连接池。也就是说Tomcat会提供经过池处理的JDBC连接(用它内置的DBCP连接池),Hibernate通过JNDI方式来请求获得JDBC连接。作为替代方案,你也可以让Hibernate自行管理连接池。Tomcat把连接池绑定到JNDI,我们要在Tomcat的主配置文件(TOMCAT/conf/server.xml)中加一个资源声明:

     Tomcat现在通过JNDI的方式:java:comp/env/jdbc/quickstart来提供连接。配置Hibernate知道它如何获得JDBC连接,在这里我们使用基于XML格式的Hibernate配置文件,命名为hibernate.cfg.xml。

    SessionFactory是Hibernate的一个概念,表示对应一个数据存储源。通过创建多个XML配置文件并在你的程序中创建多个Configuration和SessionFactory对象,就可以支持多个数据库了。

    在hibernate.cfg.xml中的最后一个元素声明了Cat.hbm.xml,这是一个Hibernate XML映射文件,对应于持久化类Cat。这个文件包含了把Cat POJO类映射到数据库表(或多个数据库表)的元数据。我们稍后就回来看这个文件。下一步让我们先编写这个POJO类,然后在声明它的映射元数据。

    这个类不但在它的静态初始器中使用了SessionFactory,还使用了一个ThreadLocal变量来保存Session做为当前工作线程。在你使用这个辅助类之前,请确保你理解了thread-local变量这个Java概念。
    SessionFactory是安全线程,可以由很多线程并发访问并获取到Sessions。单个Session不是安全线程对象,它只代表与数据库之间的一次操作。Session通过SessionFactory获得并在所有的工作完成后关闭。

    在一个Session中,每个数据库操作都是在一个事务(transaction)中进行的,这样就可以隔离开不同的操作(甚至包括只读操作)。我们使用Hibernate的Transaction API来从底层的事务策略中(本例中是JDBC事务)脱身出来。这样,我们就不需要更改任何源代码,就可以把我们的程序部署到一个由容器管理事务的环境中去(使用JTA)。
    这样你就可以随心所欲的多次调用HibernateUtil.currentSession();,你每次都会得到同一个当前线程的Session。不管是在你的servlet代码中,或者在servlet filter中还是在HTTP结果返回之前,你都必须确保这个Session在你的数据库访问工作完成后关闭。这样做还有一个好处就是可以容易的使用延迟装载(lazy initialization)。

所有的持久类(persistent classes)都要求有无参的构造器(no-argument constructor); 因为Hibernate必须要使用Java反射机制(Reflection)来实例化对象。
映射文件(mapping file)应该被保存为Event.hbm.xml,和我们的EventJava 源文件放在同一个目录下。映射文件的名字可以是任意的,然而hbm.xml已经成为Hibernate开发者社区的习惯性约定。

不仅仅在它的静态初始化过程(仅当加载这个类的时候被JVM执行一次)中产生全局SessionFactory, 同时也有一个ThreadLocal变量来为当前线程保存Session。不论你何时 调用HibernateUtil.currentSession(),它总是返回同一个线程中的同一个Hibernate单元操作。 而一个HibernateUtil.closeSession()调用将终止当前线程相联系的那个单元操作。

在你使用这个帮助类之前,确定你明白Java关于本地线程变量(thread-local variable)的概念。一个功能更加强大的 HibernateUtil帮助类可以在CaveatEmptorhttp://caveatemptor.hibernate.org/找到 -它同时也出现在书:《Hibernate in Action》中。注意当你把Hibernate部署在一个J2EE应用服务器上的时候,这个类不是必须的: 一个Session会自动绑定到当前的JTA事物上,你可以通过JNDI来查找SessionFactory。 如果你使用JBoss AS,Hibernate可以被部署成一个受管理的系统服务(system service)并自动绑定SessionFactory到JNDI上。

最后我们需要配置一个日志系统 - Hibernate使用通用日志接口,这允许你在Log4j和 JDK 1.4 logging之间进行选择。多数开发者喜欢Log4j:从Hibernate的分发版(它在etc/目录下)拷贝 log4j.properties到你的src目录,与hibernate.cfg.xml.放在一起。 看一眼配置示例,你可以修改配置如果你希望看到更多的输出信息。缺省情况下,只有Hibernate的启动信息会显示在标准输出上。

SessionFactory (org.hibernate.SessionFactory)
针对单个数据库映射关系经过编译后的内存镜像,它也是线程安全的(不可变)。 它是生成Session的工厂,本身要用到ConnectionProvider。 该对象可以在进程或集群的级别上,为那些事务之间可以重用的数据提供可选的二级缓存。

Session (org.hibernate.Session)
表示应用程序与持久储存层之间交互操作的一个单线程对象,此对象生存期很短。 其隐藏了JDBC连接,也是Transaction的工厂。 其会持有一个针对持久化对象的必选(第一级)缓存,在遍历对象图或者根据持久化标识查找对象时会用到。

如果这些持久化类遵循一些简单的规则,Hibernate能够工作得最好,这些规则被称作, 简单传统Java对象(POJO:Plain Old Java Object)编程模型。但是这些规则没有一个是必需的。

被映射的类必须定义对应数据库表主键字段。大多数类有一个JavaBeans风格的属性, 为每一个实例包含唯一的标识。<id> 元素定义了该属性到数据库表主键字段的映射。

还有一个另外的<composite-id>定义可以访问旧式的多主键数据。 我们强烈不建议使用这种方式。

如果不知道所要寻找的对象的持久化标识,那么你需要使用查询。Hibernate支持强大且易于使用的面向对象查询语言(HQL)。 如果希望通过编程的方式创建查询,Hibernate提供了完善的按条件(Query By Criteria, QBC)以及按样例(Query By Example, QBE)进行查询的功能。 你也可以用原生SQL(native SQL)描述查询,Hibernate提供了将结果集(result set)转化为对象的部分支持。

设计细颗粒度的持久类并且使用<component>来实现映射。
使用一个Address持久类来封装 street, suburb, state, postcode. 这将有利于代码重用和简化代码重构(refactoring)的工作。

对持久类声明标识符属性。
Hibernate中标识符属性是可选的,不过有很多原因来说明你应该使用标识符属性。我们建议标识符应该是“人造”的(自动生成,不涉及业务含义)。虽然原生类型从语法上可能更易于使用,但使用long或java.lang.Long没有任何区别,。

为每个持久类写一个映射文件
不要把所有的持久类映射都写到一个大文件中。把 com.eg.Foo 映射到com/eg/Foo.hbm.xml中, 在团队开发环境中,这一点显得特别有意义。

把映射文件作为资源加载
把映射文件和他们的映射类放在一起进行部署。

考虑把查询字符串放在程序外面
如果你的查询中调用了非ANSI标准的SQL函数,那么这条实践经验对你适用。把查询字符串放在映射文件中可以让程序具有更好的可移植性。

使用绑定变量
就像在JDBC编程中一样,应该总是用占位符"?"来替换非常量值,不要在查询中用字符串值来构造非常量值!更好的办法是在查询中使用命名参数。

不要自己来管理JDBC connections
Hibernate允许应用程序自己来管理JDBC connections,但是应该作为最后没有办法的办法。如果你不能使用Hibernate内建的connections providers,那么考虑实现自己来实现org.hibernate.connection.ConnectionProvider

考虑使用用户自定义类型(custom type)
假设你有一个Java类型,来自某些类库,需要被持久化,但是该类没有提供映射操作需要的存取方法。那么你应该考虑实现net.sf.hibernate.UserType接口。这种办法使程序代码写起来更加自如,不再需要考虑类与Hibernate type之间的相互转换。

在性能瓶颈的地方使用硬编码的JDBC
在对性能要求很严格的一些系统中,一些操作(例如批量更新和批量删除)也许直接使用JDBC会更好,但是请先搞清楚这是否是一个瓶颈,并且不要想当然认为JDBC一定会更快。如果确实需要直接使用JDBC,那么最好打开一个 Hibernate Session 然后从 Session获得connection,按照这种办法你仍然可以使用同样的transaction策略和底层的connection provider。

理解Session清洗( flushing)
Session会不时的向数据库同步持久化状态,如果这种操作进行的过于频繁,性能会受到一定的影响。有时候你可以通过禁止自动flushing,尽量最小化非必要的flushing操作,或者更进一步,在一个特定的transaction中改变查询和其它操作的顺序。

在三层结构中,考虑使用 saveOrUpdate()
当使用一个servlet / session bean 类型的架构的时候, 你可以把已加载的持久对象在session bean层和servlet / JSP 层之间来回传递。使用新的session来为每个请求服务,使用 Session.update() 或者Session.saveOrUpdate()来更新对象的持久状态。

在两层结构中,考虑断开session.
为了得到最佳的可伸缩性,数据库事务(Database Transaction)应该尽可能的短。但是,程序常常需要实现长时间运行的“应用程序事务(Application Transaction)”,包含一个从用户的观点来看的原子操作。这个应用程序事务可能跨越多次从用户请求到得到反馈的循环。请使用脱管对象(与session脱离的对象),或者在两层结构中,把Hibernate Session从JDBC连接中脱离开,下次需要用的时候再连接上。绝不要把一个Session用在多个应用程序事务(Application Transaction)中,否则你的数据可能会过期失效。

不要把异常看成可恢复的
这一点甚至比“最佳实践”还要重要,这是“必备常识”。当异常发生的时候,必须要回滚 Transaction ,关闭Session。如果你不这样做的话,Hibernate无法保证内存状态精确的反应持久状态。尤其不要使用Session.load()来判断一个给定标识符的对象实例在数据库中是否存在,应该使用find()。

对于关联优先考虑lazy fetching
谨慎的使用主动外连接抓取(eager (outer-join) fetching)。对于大多数没有JVM级别缓存的持久对象的关联,应该使用代理(proxies)或者具有延迟加载属性的集合(lazy collections)。对于被缓存的对象的关联,尤其是缓存的命中率非常高的情况下,应该使用outer-join="false",显式的禁止掉eager fetching。如果那些特殊的确实适合使用outer-join fetch 的场合,请在查询中使用left join。

考虑把Hibernate代码从业务逻辑代码中抽象出来
把Hibernate的数据存取代码隐藏到接口(interface)的后面,组合使用DAO和Thread Local Session模式。通过Hibernate的UserType,你甚至可以用硬编码的JDBC来持久化那些本该被Hibernate持久化的类。 (该建议更适用于规模足够大应用软件中,对于那些只有5张表的应用程序并不适合。)

使用与业务有关的键值来实现equals()和 hashCode() .
如果你在Session外比较对象,你必须要实现equals()和 hashCode()。在Session内部,Java的对象识别机制是可以保证的。如果你实现了这些方法,不要再使用数据库(主键)辨识!瞬时对象不具有(数据库)标识值,Hibernate会在对象被保存的时候赋予它一个值。如果对象在被保存的时候位于Set内,hash code就会变化,要约就被违背。为了实现用与业务有关的键值编写equals()和 hashCode(),你应该使用类属性的唯一组合。记住,这个键值只是当对象位于Set内部时才需要保证稳定且唯一,并不是在其整个生命周期中都需要(不需要达到数据库主键这样的稳定性)。绝不要在equals()中比较集合(要考虑延迟装载),并且小心对待其他可能被代理过的类。

不要用怪异的连接映射
多对多连接用得好的例子实际上相当少见。大多数时候你在“连接表”中需要保存额外的信息。这种情况下,用两个指向中介类的一对多的连接比较好。实际上,我们认为绝大多数的连接是一对多和多对一的,你应该谨慎使用其它连接风格,用之前问自己一句,是否真的必须这么做。

 

session不用传递,随时可生成一个Session的引用来通过HibernateUtil.currentSession()得到此session.

原创粉丝点击