`

Hibernate 超实用解析(一)

阅读更多

解压缩从Hibernate网站http://www.hibernate.org下载的Hibernate发布包,并把/lib目录下所有需要的库文件拷到新建开发目录下的/lib目录下。

想使用Hibernate,以下是运行时所需要的最小库文件集合:
  antlr.jar
  cglib.jar
  asm.jar
  asm-attrs.jars
  commons-collections.jar
  commons-logging.jar
  ehcache.jar
  hibernate3.jar
  jta.jar
  dom4j.jar
  log4j.jar

配置Hibernate
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
        <property name="connection.url">jdbc:hsqldb:hsql://localhost</property>
        <property name="connection.username">sa</property>
        <property name="connection.password"></property>

<!-- 将显示的SQL排版,方便观看 -->  
<property name="format_sql">true</property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.HSQLDialect</property>

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

        <!-- Drop and re-create the database schema on startup -->
        <property name="hbm2ddl.auto">create</property>

        <mapping resource="events/Event.hbm.xml"/>

    </session-factory>

</hibernate-configuration>


Hibernate日志类别

类别        功能
org.hibernate.SQL   在所有SQL DML语句被执行时为它们记录日志
org.hibernate.type   为所有JDBC参数记录日志
org.hibernate.tool.hbm2ddl  在所有SQL DDL语句执行时为它们记录日志
org.hibernate.pretty   在session清洗(flush)时,为所有与其关联的实体(最多20个)的状态记录日志
org.hibernate.cache   为所有二级缓存的活动记录日志
org.hibernate.transaction  为事务相关的活动记录日志
org.hibernate.jdbc   为所有JDBC资源的获取记录日志
org.hibernate.hql.AST   在解析查询的时候,记录HQL和SQL的AST分析日志
org.hibernate.secure   为JAAS认证请求做日志
org.hibernate    为任何Hibernate相关信息做日志 (信息量较大, 但对查错非常有帮助)


class元素来定义一个持久化类:

<class
        name="ClassName"
        table="tableName"
        discriminator-value="discriminator_value"
        mutable="true|false"
        schema="owner"
        catalog="catalog"
        proxy="ProxyInterface"
        dynamic-update="true|false"
        dynamic-insert="true|false"
        select-before-update="true|false"
        polymorphism="implicit|explicit"
        where="arbitrary sql where condition"
        persister="PersisterClass"
        batch-size="N"
        optimistic-lock="none|version|dirty|all"
        lazy="true|false"
        entity-name="EntityName"
        check="arbitrary sql check condition"
        rowid="rowid"
        subselect="SQL expression_r"
        abstract="true|false"
        node="element-name"
/>
1. name (可选): 持久化类(或者接口)的Java全限定名。如果这个属性不存在,Hibernate将假定这是一个非POJO的实体映射。
2. table (可选 - 默认是类的非全限定名): 对应的数据库表名。

3. discriminator-value (可选 - 默认和类名一样): 一个用于区分不同的子类的值,在多态行为时使用。它可以接受的值包括 null 和 not null。

4. mutable (可选,默认值为true): 表明该类的实例是可变的或者不可变的。

5. schema (可选): 覆盖在根<hibernate-mapping>元素中指定的schema名字。

6. catalog (可选): 覆盖在根<hibernate-mapping>元素中指定的catalog名字。

7. proxy (可选): 指定一个接口,在延迟装载时作为代理使用。 可以在这里使用该类自己的名字。

8. dynamic-update (可选, 默认为 false): 指定用于UPDATE 的SQL将会在运行时动态生成,并且只更新那些改变过的字段。

9. dynamic-insert (可选, 默认为 false): 指定用于INSERT的 SQL 将会在运行时动态生成,并且只包含那些非空值字段。

10. select-before-update (可选, 默认为 false): 指定Hibernate除非确定对象真正被修改了(如果该值为true-译注),否则不会执行SQL UPDATE操作。在特定场合(实际上,它只在一个瞬时对象(transient object)关联到一个新的session中时执行的update()中生效),这说明Hibernate会在UPDATE 之前执行一次额外的SQL SELECT操作,来决定是否应该执行 UPDATE。

11. polymorphism(多态) (可选, 默认值为 implicit (隐式) ): 界定是隐式还是显式的使用多态查询(这只在Hibernate的具体表继承策略中用到-译注)。

12. where (可选) 指定一个附加的SQLWHERE 条件, 在抓取这个类的对象时会一直增加这个条件。

13. persister (可选): 指定一个定制的ClassPersister。

14. batch-size (可选,默认是1) 指定一个用于 根据标识符(identifier)抓取实例时使用的"batch size"(批次抓取数量)。

15. optimistic-lock(乐观锁定) (可选,默认是version): 决定乐观锁定的策略。

(16) lazy (可选): 通过设置lazy="false", 所有的延迟加载(Lazy fetching)功能将被全部禁用(disabled)。

(17) entity-name (可选,默认为类名): Hibernate3允许一个类进行多次映射( 前提是映射到不同的表),并且允许使用Maps或XML代替Java层次的实体映射 (也就是实现动态领域模型,不用写持久化类-译注)。

(18) check (可选): 这是一个SQL表达式,用于为自动生成的schema添加多行(multi-row)约束检查。

(19) rowid (可选): Hibernate可以使用数据库支持的所谓的ROWIDs,例如: Oracle数据库,如果设置这个可选的rowid, Hibernate可以使用额外的字段rowid实现快速更新。ROWID是这个功能实现的重点,它代表了一个存储元组(tuple)的物理位置。

(20) subselect (可选): 它将一个不可变(immutable)并且只读的实体映射到一个数据库的子查询中。当想用视图代替一张基本表的时候,这是有用的,但最好不要这样做。更多的介绍请看下面内容。

(21) abstract (可选): 用于在<union-subclass>的继承结构 (hierarchies)中标识抽象超类。


强烈建议在Hibernate中使用version/timestamp字段来进行乐观锁定。对性能来说,这是最好的选择,并且这也是唯一能够处理在session外进行操作的策略(例如:在使用Session.merge()的时候)。

UUID包含:IP地址,JVM的启动时间(精确到1/4秒),系统时间和一个计数器值(在JVM中唯一)。在Java代码中不可能获得MAC地址或者内存地址,所以这已经是在不使用JNI的前提下的能做的最好实现了。


Hibernate要求持久化集合值字段必须声明为接口

为了能在应用程序服务器(application server)中使用Hibernate, 应当总是将Hibernate 配置成从注册在JNDI中的Datasource处获得连接,至少需要设置下列属性中的一个.

这是一个使用应用程序服务器提供的JNDI数据源的hibernate.properties样例文件:

hibernate.connection.datasource = java:/comp/env/jdbc/test
hibernate.transaction.factory_class = \org.hibernate.transaction.JTATransactionFactory
hibernate.transaction.manager_lookup_class = \org.hibernate.transaction.JBossTransactionManagerLookup
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect


Hibernate自带的连接池算法相当不成熟. 并不适合用于产品系统或性能测试中。出于最佳性能和稳定性考虑应该使用第三方的连接池。只需要用特定连接池的设置替换 hibernate.connection.pool_size即可。可以使用C3P0连接池:
<!-- C3P0 连接池设定 -->       
<!-- 最小的Connection数目 -->      
<property name="c3p0.min_size">5</property>
<!-- 最大的Connection数目 -->      
<property name="c3p0.max_size">20</property>
<!-- 允许的idle时间 -->      
<property name="c3p0.timeout">300</property>
<!-- 最大的Statement数目 -->      
<property name="c3p0.max_statements">50</property>
<!-- idle的测试周期 -->      
<property name="c3p0.idle_test_period">3000</property>

使用Tomcat的话,也可以透过它提供的DBCP连接池来取得连接。

设定好容器提供的DBCP连接池之后,只要在配置文件中加入connection.datasource属性,例如在 hibernate.cfg.xml中加入:

<property name="connection.datasource">java:comp/env/jdbc/dbname</property>

如果是在hibernate.properties中的话,则加入:

hibernate.connection.datasource = java:comp/env/jdbc/dbname


如果有个属性,实际上并没有与之对应,例如使用COUNT('filed')来取得,则可以使用formula属性,例如:
...
    <property name="average" formula="(SELECT AVG(u.age) FROM T_USER u)"/>
...


设定< id>标签的unsaved-value来决定什么是新的值必需,什么是已有的值必须更新:

<id name="id" column="id" type="java.lang.Integer" unsaved-value="null">
    <generator class="native"/>
</id>

unsaved-value可以设定的值包括:

any:总是储存
none:总是更新
null:id为null时储存(预设)
valid:id为null或是指定值时储存

这样设定之后,可以使用Session的saveOrUpdate()方法来取代update()方法。

可编程的配置方式
一个org.hibernate.cfg.Configuration实例代表了一个应用程序中Java类型 到SQL数据库映射的完整集合. Configuration被用来构建一个(不可变的 (immutable))SessionFactory. 映射定义则由不同的XML映射定义文件编译而来.

可以直接实例化Configuration来获取一个实例,并为它指定XML映射定义 文件. 如果映射定义文件在类路径(classpath)中, 请使用addResource():

Configuration cfg = new Configuration()
    .addResource("Item.hbm.xml")
    .addResource("Bid.hbm.xml");
一个替代方法(有时是更好的选择)是,指定被映射的类,让Hibernate帮寻找映射定义文件:

Configuration cfg = new Configuration()
    .addClass(org.hibernate.auction.Item.class)
    .addClass(org.hibernate.auction.Bid.class);
Hibernate将会在类路径(classpath)中寻找名字为 /org/hibernate/auction/Item.hbm.xml和 /org/hibernate/auction/Bid.hbm.xml映射定义文件. 这种方式消除了任何对文件名的硬编码(hardcoded).


从数据库加载数据:
Session 有三种查询方法get(),load(),find(),前两种都是根据对象ID加载对象,后者类似HQL查询一个或多个对象.
Hibernate要求复合主键类别要实作Serializable介面,并定义equals()与hashCode()方法.
注意如果没有匹配的数据库记录,load()方法可能抛出无法恢复的异常(unrecoverable exception)。如果不确定是否有匹配的行存在,应该使用get()方法,它会立刻访问数据库,如果没有对应的记录,会返回null。如果不知道所要寻找的对象的持久化标识,那么需要使用查询。
一个查询通常在调用list()时被执行,执行结果会完全装载进内存中的一个集合(collection)。查询返回的对象处于持久(persistent)状态。如果知道的查询只会返回一个对象,可使用list()的快捷方式uniqueResult()。注意,使用集合预先抓取的查询往往会返回多次根对象(他们的集合类都被初始化了)。可以通过一个集合来过滤这些重复对象。 load()方法可以返回代理物件,并可充分利用缓冲机制。


以将HQL撰写在程式之外,以避免硬编码(Hard code)外置命名查询(Externalizing named queries)
可以在映射文件中定义命名查询(named queries)。 (如果的查询串中包含可能被解释为XML标记(markup)的字符,别忘了用CDATA包裹起来。)

<query name="ByNameAndMaximumWeight"><![CDATA[
    from eg.DomesticCat as cat
        where cat.name = ?
        and cat.weight > ?
] ]></query>
参数绑定及执行以编程方式(programatically)完成:

Query q = sess.getNamedQuery("ByNameAndMaximumWeight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();
请注意实际的程序代码与所用的查询语言无关,也可在元数据中定义原生SQL(native SQL)查询,或将原有的其他的查询语句放在配置文件中,这样就可以让Hibernate统一管理,达到迁移的目的。

也请注意在<hibernate-mapping>元素中声明的查询必须有一个全局唯一的名字,而在<class>元素中声明的查询自动具有全局名,是通过类的全名加以限定的。比如eg.Cat.ByNameAndMaximumWeight。



Configuration也允许指定配置属性:

Configuration cfg = new Configuration()
    .addClass(org.hibernate.auction.Item.class)
    .addClass(org.hibernate.auction.Bid.class)
    .setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect")
    .setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test")
    .setProperty("hibernate.order_updates", "true");


    SessionFactory可以创建并打开新的Session。一个Session代表一个单线程的单元操作,SessionFactory则是个线程安全的全局对象,只需要被实例化一次.创建一个HibernateUtil辅助类(helper class)来负责启动Hibernate和更方便地操作SessionFactory。
创建一个HibernateUtil辅助类(helper class)来负责启动Hibernate和更方便地操作SessionFactory。hibernate.cfg.xml把这个文件拷贝到源代码目录下面,这样它就位于classpath的根目录的最后。Hibernate在启动时会自动在classpath的根目录查找名为hibernate.cfg.xml的配置文件。
import org.hibernate.*;
import org.hibernate.cfg.*;

public class HibernateUtil {

    private static final SessionFactory sessionFactory;

    static {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

}
    Session在第一次被使用的时候,即第一次调用getCurrentSession()的时候,其生命周期就开始。然后它被Hibernate绑定到当前线程。当事务结束的时候,不管是提交还是回滚,Hibernate会自动把Session从当前线程剥离,并且关闭它。Hibernate Session的生命周期可以很灵活,但是绝不要把的应用程序设计成为每一次数据库操作都用一个新的Hibernate Session。

    持久化类(Persistent Classes)所有的持久化类都必须有一个默认的构造方法(可以不是public的),建议对持久化类声明命名一致的标识属性,建议使用一个可以为空(也就是说,不是原始类型)的类型如:Integer。 Hibernate可以持久化有 default、protected或private的get/set方法的属性。

    如果想把持久类的实例放入Set中(当表示多值关联时,推荐这么做)或希望Set有明确的语义,就必须实现equals() 和hashCode()。
建议使用业务键值相等(Business key equality)来实现equals() 和 hashCode()。业务键值相等的意思是,equals()方法仅仅比较形成业务键的属性,它能在现实世界里标识的实例(是一个自然的候选码)。


关联映射

   使用Java的集合类(collection):Set,因为set 不包含重复的元素及与无关的排序。
   1.多对多关联(或叫n:m实体关系), 需要一个关联表(association table)。表里面的每一行代表从person到event的一个关联。表名是由set元素的table属性配置的。关联里面的标识符字段名,对于person的一端,是由<key>元素定义,而event一端的字段名是由<many-to-many>元素的column属性定义。

使用Hibernate的多对多映射:

<class name="events.Person" table="PERSON">
    <id name="id" column="PERSON_ID">
        <generator class="native"/>
    </id>
    <property name="age"/>
    <property name="firstname"/>
    <property name="lastname"/>

    <set name="events" table="PERSON_EVENT">
        <key column="PERSON_ID"/>
        <many-to-many column="EVENT_ID" class="events.Event"/>
    </set>

</class>


关联工作把一些people和events 一起放到EventManager的新方法中:

private void addPersonToEvent(Long personId, Long eventId) {

    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    session.beginTransaction();

    Person aPerson = (Person) session.load(Person.class, personId);
    Event anEvent = (Event) session.load(Event.class, eventId);

    aPerson.getEvents().add(anEvent);

    session.getTransaction().commit();
}
set元素的inverse="true"属性。

这意味着在需要的时候,Hibernate能在关联的另一端的类得到两个实体间关联的信息。

使双向连起来首先请记住,Hibernate并不影响通常的Java语义。在单向关联的例子中,是怎样在Person和Event之间创建联系的?把Event实例添加到Person实例内的event引用集合里。因此很显然,如果要让这个关联可以双向地工作,需要在另外一端做同样的事情- 把Person实例加入Event类内的Person引用集合。这“在关联的两端设置联系”是完全必要的而且都得这么做。

许多开发人员防御式地编程,创建管理关联的方法来保证正确的设置了关联的两端,比如在Person里:

protected Set getEvents() {
    return events;
}

protected void setEvents(Set events) {
    this.events = events;
}

public void addToEvent(Event event) {
    this.getEvents().add(event);
    event.getParticipants().add(this);
}

public void removeFromEvent(Event event) {
    this.getEvents().remove(event);
    event.getParticipants().remove(this);
}
注意现在对于集合的get和set方法的访问级别是protected - 这允许在位于同一个包(package)中的类以及继承自这个类的子类可以访问这些方法,但禁止其他任何人的直接访问,避免了集合内容的混乱。应尽可能地在另一端也把集合的访问级别设成protected。

inverse映射属性究竟表示什么呢?对于和Java来说,一个双向关联仅仅是在两端简单地正确设置引用。然而,Hibernate并没有足够的信息去正确地执行INSERT和UPDATE语句(以避免违反数据库约束),所以它需要一些帮助来正确的处理双向关联。把关联的一端设置为inverse将告诉Hibernate忽略关联的这一端,把这端看成是另外一端的一个镜象(mirror)。这就是所需的全部信息,Hibernate利用这些信息来处理把一个有向导航模型转移到数据库schema时的所有问题。只需要记住这个直观的规则:所有的双向关联需要有一端被设置为inverse。在一对多关联中它必须是代表多(many)的那端。而在多对多(many-to-many)关联中,可以任意选取一端,因为两端之间并没有差别。

不要为每次数据库操作都使用一个新的Hibernate Session。将Hibernate Session的范围设置为整个请求。要用getCurrentSession(),这样它自动会绑定到当前Java线程。


Query上有list()与iterate()方法,两者的差别在于开启Query之后,list()方法在读取资料时,会利用到Query快取,
而iterate()则不会使用到Query快取功能,而是直接从资料库中再查询资料,执行两次数据库的查询。

批量处理:
Hibernate 把所有新插入实例在session级别的缓存区进行了缓存,如果要执行批量处理并且想要达到一个理想的性能,那么使用JDBC的批量(batching)功能是至关重要。将JDBC的批量抓取数量(batch size)参数设置到一个合适值 (比如,10-50之间):

hibernate.jdbc.batch_size 20
注意,假若使用了identiy标识符生成器,Hibernate在JDBC级别透明的关闭插入语句的批量执行。

使用延迟加载时,由于在需要数据时会向数据库进行查询,所以session不能关闭,如果关闭会丢出LazyInitializationException异常如果使用了延迟加载,而在某些时候仍有需要在session关闭之后取得相关对象,则可以使用Hibernate.initialize()来先行载入相关对象,

批量插入(Batch inserts)
如果要将很多对象持久化,必须通过经常的调用 flush() 以及稍后调用 clear() 来控制第一级缓存的大小。

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
 
for ( int i=0; i<100000; i++ ) {
    Customer customer = new Customer(.....);
    session.save(customer);
    if ( i % 20 == 0 ) { //20, same as the JDBC batch size //20,与JDBC批量设置相同
        //flush a batch of inserts and release memory:
        //将本批插入的对象立即写入数据库并释放内存
        session.flush();
        session.clear();
    }
}
 
tx.commit();
session.close();

批量更新(Batch updates)
此方法同样适用于检索和更新数据。此外,在进行会返回很多行数据的查询时, 需要使用 scroll() 方法以便充分利用服务器端游标所带来的好处。

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
 
ScrollableResults customers = session.getNamedQuery("GetCustomers")
    .setCacheMode(CacheMode.IGNORE)
    .scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while ( customers.next() ) {
    Customer customer = (Customer) customers.get(0);
    customer.updateStuff(...);
    if ( ++count % 20 == 0 ) {
        //flush a batch of updates and release memory:
        session.flush();
        session.clear();
    }
}
 
tx.commit();
session.close();


可以在映射文档中定义查询的名字,然后就可以象调用一个命名的HQL查询一样直接调用命名SQL查询.在这种情况下,不需要调用addEntity()方法.

<sql-query name="persons">
    <return alias="person" class="eg.Person"/>
    SELECT person.NAME AS {person.name},
           person.AGE AS {person.age},
           person.SEX AS {person.sex}
    FROM PERSON person
    WHERE person.NAME LIKE :namePattern
</sql-query>
List people = sess.getNamedQuery("persons")
    .setString("namePattern", namePattern)
    .setMaxResults(50)
    .list();

<idbag>映射定义了代理键,因此它总是可以很高效的被更新。事实上, <idbag>拥有着最好的性能表现。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics