Hibernate 闲谈杂记

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"
        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>拥有着最好的性能表现。


双向的一对多关系:
要实现一个简单的从Parent到Child的<one-to-many>关联
<many-to-one name="parent" column="parent_id" not-null="true"/>
(还需要为类Child添加parent属性)

现在实体Child在管理连接的状态,为了使collection不更新连接,使用inverse属性。

<set name="children" inverse="true">
    <key column="parent_id"/>
    <one-to-many class="Child"/>
</set>
下面的代码是用来添加一个新的Child

Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c);
session.save(c);
session.flush();
现在,只会有一条INSERT语句被执行!

为了让事情变得井井有条,可以为Parent加一个addChild()方法。

public void addChild(Child c) {
    c.setParent(this);
    children.add(c);
}
现在,添加Child的代码就是这样

Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.save(c);
session.flush();
需要显式调用save()仍然很麻烦,可以用级联来解决这个问题。

<set name="children" inverse="true" cascade="all">
    <key column="parent_id"/>
    <one-to-many class="Child"/>
</set>
这样上面的代码可以简化为:

Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.flush();
同样的,保存或删除Parent对象的时候并不需要遍历其子对象。 下面的代码会删除对象p及其所有子对象对应的数据库记录。

Parent p = (Parent) session.load(Parent.class, pid);
session.delete(p);
session.flush();


加载并存储对象
session.save(theEvent);
List result = session.createQuery("from Event").list();


spring与hibernate的整合原理:HibernateTemplate封装了session,StudentDAO需要一个HibernateTemplate,即可通过HibernateTemplate完成Session的功能。怎样创建出HibernateTemplate呢?HiberateTemplate需要SessionFactory,LocalSessionFactoryBean创建出hibernate的SessionFactory,即StudentDAO-->HibernateTemplate-->LocalSessionFactoryBean。如果StudentDAO直接得到了SessionFactory,那么,它也可以通过内部的程序代码创建出HibernateTemplate,HibernateDAOSupport就是基于这种考虑设计出来,它内封装了HibernateTemplate,并且需要给它传递SessionFactory。LocalSessionFactoryBean是如何创建出hibernate的SessionFactory的,hibernate.cfg.xml文件中的配置项都可以通过程序来设置,所以,在spring中可以不用hibernate.cfg.xml文件。


get与load都可以根据参数获取到指定的实体,有什么区别呢??
从Hibernate的参考手册中,基本可以总结出这样几条:
1、如果找不到符合条件的记录,get方法返回null,而load方法抛出异常
2、使用load方法,一般都假定要取得对象肯定是存在的,而get方法则尝试,如果不存在,就返回null
从这个角度看,似乎没什么大不了的。其实,仔细看看hibernate中关于get和load方法的源码,就不难发现,这背后的不同了。
get方法每次都要访问数据库,而load则不一定,如果使用了缓存机制,load就会从缓存中查找,所以,不一定每次都访问数据库。也就是,load可以更好的利用hibernate的缓存机制,从有效地降低地数据库的直接操作。经过比较可以发现:Session.load/get方法均可以根据指定的实体类和id从数据库读取记录,并返回与之对应的实体对象。其区别在于:

如果未能发现符合条件的记录,get方法返回null,而load方法会抛出一个ObjectNotFoundException。
Load方法可返回实体的代理类实例,而get方法永远直接返回实体类。
load方法可以充分利用内部缓存和二级缓存中的现有数据,而get方法则仅仅在内部缓存中进行数据查找,如没有发现对应数据,将越过二级缓存,直接调用SQL完成数据读取。

异常1:not-null property references a null or transient value
解决方法:将“一对多”关系中的“一”方,not-null设置为false
(参考资料:http://www.thearcmind.com/confluence/pages/viewpage.action?pageId=212)

异常2:org.hibernate.TransientObjectException: object references an unsaved transient instance
解决方法:cascade="save-update,persist"
(参考资料:http://www.laliluna.de/254.html)

异常3:org.hibernate.QueryException: could not resolve property
解决方法:"from Category category where category.userID = :userID"修改为"from Category category where userID = :userID"或者"from Category category where category.user.id = :userID"
(参考资料:http://www.laliluna.de/277.html)

异常4:could not initialize proxy - the owning Session was closed
解决方法:设置lazy为false
(参考资料:http://forum.springframework.org/showthread.php?t=27993)


复合主键:

主键即可以和其他字段定义在一个类中,也可以将复合主键单独声明为一个类,然后在类中声明一个复合主键类的对象。
在此介绍第一种方式:

CREATE TABLE user (
    name VARCHAR(100) NOT NULL,
    password VARCHAR(50) NOT NULL,
    age INT,
    PRIMARY KEY(name, password)
);

name和password为复合主键,Hibernate要求复合主键类别要实现Serializable,并定义equals()和hashCode()方法。
import java.io.Serializable;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

public class User implements Serializable {
private String name;   
private String password;   
private Integer age;    
public User() {}   
public Integer getAge() {
        return age;   
}   
public void setAge(Integer age) {
        this.age = age;   
}   
public String getName() {
        return name;   
}    public void setName(String name) {
        this.name = name;   
}    public String getPassword() {
        return password;   
}    public void setPassword(String password) {
        this.password = password;  
 }    
// 必须重新定义equals()与hashCode()
public boolean equals(Object obj) {
        if(obj == this) {
            return true;
       }        
if(!(obj instanceof User)) {
           return false;      
 }       
 User user = (User) obj;
        return new EqualsBuilder().append(this.name, user.getName()).append(this.password, user.getPassword()).isEquals();
    }    
public int hashCode() {
        return new HashCodeBuilder().append(this.name).append(this.password).toHashCode();  
}
}
<composite-id>在映射文件中定义复合组件与对象的属性对应。
<composite-id>          
    <key-property name="name" column="name" type="java.lang.String"/>
    <key-property name="password" column="password" type="java.lang.String"/>
</composite-id>

Hibernate 3中引入了动态模式,可以使用容器充当Java实体,在构造系统原型时灵活变化,而不必实际定义Java对象。
直接在映射文件的<class>标签上使用entity-name属性:
 <class entity-name="EntityName" table="T_NAME">

entity-name属性设定的名称将在储存或载入时使用,例如可以如下储存:

Map entity = new HashMap();
entity.put("name", "zxz");

Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx= session.beginTransaction();
session.save("EntityName", entity);
tx.commit();
session.close();
Map容器的key用来表示属性名称,而value用来表示值。
加载:
Map entity = (Map) session.load("EntityName", new Long(1));
System.out.println(entity.get("name")); 
使用HQL查询:EntityName为配置文件中定义的entity_name属性值。
List entitys = session.createQuery("from EntityName").list();
       
for(int i = 0; i < entitys.size(); i++) {
    Map entity = (Map) entitys.get(i);
    System.out.println(entity.get("name"));
}

为了设计上的弹性,动态模型也可以与静态的POJO模型混用,要混用静态模型与动态模型,可在配置文件中设定name与entity-name属性:
<class name="ClassName" entity-name="EntityName" table="T_NAME">
默认是使用pojo来操作,要使用动态模型必须:
Map entity = new HashMap();
entity.put("name", "zxz");
Session dynamicSession = session.getSession(EntityMode.MAP);
Transaction tx= dynamicSession.beginTransaction();
dynamicSession.save("EntityName", entity);

继承关系:Hibernate自动判断继承关系。
可以采取三种策略,
  第一种:
 (1)Table per concrete class,即抽象父类不建表,每一个子类建立一个表格。抽出相同属性部分定义为抽象父类,然后分别定义继承父类的子类,并分别定义配置文件。(HQL:from ParentClass 多表多次查询)。
分析:这种方式建议用于没有关联性,而且父类将来不会修改的情况,或者不需要多表查询的情况。
 (2)也可在一个配置文件中,将父类与子类的属性对应撰写在一起。只建立一个配置文件(多表一次查询)。

 <!-- abstract 表明ParentClass是抽象的,无需对应至任何表格 -->   
<class name="ParentClass" abstract="true">       
   <id name="id">
         <generator class="increment"/>       
   </id>        

 <!-- 从父类别继承下来的共同属性 -->  
  <property name="ParentProperty"/> 
    
  <!-- 子类别的新增属性 -->     
  <union-subclass name="subClass1" table="T_NAME1">
     <property name="someProperty"/>  
  </union-subclass>   

    <!-- 子类别的新增属性 -->
  <union-subclass name="subClass2" table="T_NAME2">
     <property name="otherProperty"/>
  </union-subclass>
</class>
分析:新增时要先查询出最大ID值再分别进行插入。利用子查询在同一个SQL语句中完成所有查询。Table per concrete class的继承映射方式是最简单,但没有效率。

   第二种:Table per subclass。父类与子类分别建表,而父类与子类对应的表通过外键来产生关联。只建立一个配置文件。
(多表一次查询,外键连接查询)
<class name="ParentClass" table="T_NAME">
<id name="id" column="id">
 <generator class="native"/>
</id>

<property name="ParentClassProperty"/>

<joined-subclass name="SubClass1" table="T_SUBNAME1">
 <key column="id" foreign-key="id"/>
 <property name="someProperty" column="someProperty" />
</joined-subclass>

<joined-subclass name="SubClass2" table="T_SUBNAME2">
            <key column="id" foreign-key="id"/>
            <property name="otherProperty" column="otherProperty" />        
</joined-subclass>  
</class>
###<joined-subclass>指明了子类所对应的表,<key column>指明子类对应表中,与父类的主键对应的外键一致。
分析:效率是这个映射类型需要考虑的,在复杂的类别继承下,新增资料必须对多个表格进行,而查询时,跨越多个表格的join也可能引发效率上的问题。如果需要多表查询,而子类相对来说有较多新增的属性,则可以使用这种映射方式。

第三种:
 (1)Table per class hierarchy的继承映射方式,这种方式使用一个表储存同一个继承级别的所有类,并使用额外的属性来表示所记录的是哪一个子类的资料。
create table T_NAME (
    id bigint not null auto_increment,
    subType varchar(255) not null,
    name varchar(255),
    someProperty varchar(255),
    otherProperty varchar(255),
    primary key (id)
)
如果要储存的资料是来自SubClass1,则在subType记下"sub1",如果储存的资料来subClass2,则在subType记下"sub2",由subType就可以判断是要使用subClass1或subClass2,在映射文件中使用<discriminator>等相关标签来定义。
<class name="User" table="T_USER">
         <id name="id" column="id">
             <generator class="native"/>
         </id>
         <discriminator column="subType"/>
          <property name="publicProperty"/>
         <subclass name="SubClass1" discriminator-value="sub1">
             <property name="someProperty" column="someProperty" />
         </subclass>
          <subclass name="SubClass2" discriminator-value="sub2">
             <property name="otherProperty" column="otherProperty"/>
         </subclass>
</class>
分析:因子类属性的不同,所以储存时会有许多字段没有值,但查询效率较好。

 (2)也可以不使用专门定义一个字段来记录子类的类型,这适用于在使用一个已有数据库的情况,无法新增字段来记录子类类型。
配置文件只需要修改 <discriminator column="subType"/>为<discriminator formula="case when someProperty is not null then 'sub1' else 'sub2' end"/> 即可。在<discriminator>上,设定foumula属性,根据回传值为sub1或sub2来判断是哪个子类。
分析:在需要多表查询,而子类属性相对比较少时,可以使用这种映射方式。这种方式会有大量的字段为NULL的情况,好处是使用一个表,查询时只需一次SQL。


Set:
(1)非实体(Entiy)时的映射方式,简单的说,也就是所包括的对象没有主键(Identity),只是纯綷的值类型(Value type)。
例:为了不允许重复的邮件位址记录,所以使用Set物件。在所包含的类中定义:
 private Set emails;
 public Set getEmails() {
        return emails;   
 }
 public void setEmails(Set emails) {
        this.emails = emails;
 }
 public void addEmail(String email) {
        this.emails.add(email);
 }
 public void removeEmail(String email) {
        this.emails.remove(email);   
 }
要映射Set集合,可以使用另一个表来储存Set集合中的资料.
create table email (
    id bigint not null,
    address varchar(255)
)

create table user (
    id bigint not null auto_increment,
    name varchar(255),
    primary key (id)
)

alter table email add index id (id),
add constraint id  foreign key (id) references user (id)
<class name="User" table="user">
         <id name="id" column="id">
             <generator class="native"/>
         </id>

         <property name="name" column="name"/>

          <set name="emails" table="email">
             <key column="id" foreign-key="id"/>
             <element type="string" column="address"/>
         </set>
</class>
User user1 = new User();
user1.setEmails(new HashSet());
user1.setName("Name");
user1.addEmail("name@gmail.com");
user1.addEmail("name@yahoo.com");
              
User user2 = new User();
user2.setEmails(new HashSet());
user2.setName("name1");
user2.addEmail("name1@gmail.com");

Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();          
session.save(user1);
session.save(user2);
tx.commit();
session.close();

List是有序的结构:
private List items;
public List getItems() {
        return items;   
}   
public void setItems(List items) {
        this.items = items;
}   
public void addItem(String item) {
        items.add(item);   
}    
public void removeItem(String item) {
        items.remove(item);   
}

create table item (
    id bigint not null,
    name varchar(255),
    position integer not null,
    primary key (id, position)
)

create table user (
    id bigint not null auto_increment,
    name varchar(255),
    primary key (id)
)

alter table item
    add index id (id),
    add constraint id
    foreign key (id)
    references user (id)

<class name="User" table="user">
         <id name="id" column="id">
             <generator class="native"/>
         </id>        
 
<property name="name" column="name"/>

          <list name="items" table="item">
             <key column="id"/>
          <list-index column="position"/>
             <element column="name" type="string"/>
          </list>
</class>
User user1 = new User();
user1.setItems(new ArrayList());
user1.setName("caterpillar");
user1.addItem("DC");
user1.addItem("CF Card");
       
User user2 = new User();
user2.setItems(new ArrayList());
user2.setName("momor");
user2.addItem("comics");

Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();           
session.save(user1);
session.save(user2);
tx.commit();
session.close();


Map的特性是key/value对,容器中的每一个对象都有一个key与之对应,所以将Map集合的资料储存至数据库时,必须一同储存它的key值。
private Map items;
public Map getItems() {
        return items;   
}   
public void setItems(Map items) {
        this.items = items;   
}   
public void addItem(String name, String description) {
        items.put(name, description);   
}    
public void removeItem(String name) {
        items.remove(name);   
}
create table item (
    id bigint not null,
    description varchar(255),
    name varchar(255) not null,
    primary key (id, name)
)

create table user (
    id bigint not null auto_increment,
    name varchar(255),
    primary key (id)
)

alter table item
    add index id (id),
    add constraint id
    foreign key (id)
    references user (id)

<class name="onlyfun.caterpillar.User" table="user">
         <id name="id" column="id">
             <generator class="native"/>
         </id>

         <property name="name" column="name"/>

          <map name="items" table="item">
             <key column="id" foreign-key="id"/>
             <map-key column="name" type="string"/>
             <element column="description" type="string"/>
         </map>     
</class>
User user1 = new User();
user1.setItems(new HashMap());
user1.setName("caterpillar");
user1.addItem("Book", "Java Gossip");
user1.addItem("DC", "Caxxx A80");
       
User user2 = new User();
user2.setItems(new HashMap());
user2.setName("momor");
user2.addItem("Doll", "Snoppy world");    

Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
session.save(user2);
tx.commit();
session.close();

如果希望Set、Map等集合可以依一定的顺序来排列,可以从两个方面排序,一是在载入资料后于JVM中排序,另一是在数据库中直接使用order by子句来排序。要在JVM中进行排序,可以在映射文件中使用sort属性来定义集合的排序,这适用于Set与Map。Bag与List并不适用于这种方式,Bag或List本身是根据索引值来排列的。
<set name="emails" table="email" sort="natural">
sort="natural"表示使用对象的comparaTo()方法来进行排序,集合中的对象必须实现java.lang.Comparable接口。

另一种排序的方式则是在数据库中进行,直接使用order by子句来排序,这可以在映射文件中使用order-by属性来指定。
<set name="emails" table="email" order-by="address desc">
Hibernate在内部会使用LinkedHashMap或LinkedHashSet来作为集合,如果是Bag的话,则会在内部使用ArrayList作为集合。

双向关联(inverse 的意义)
在一对多、多对一形成双向关联的情况下,可以将关联维持的控制权交给多的一方,这样会比较有效率.所以在一对多、多对一形成双向关联的情况下,可以在“一”的一方设定控制权反转,也就是当储存“一”的一方时,将关联维持的控制权交给“多”的一方.<set name="users" table="user" cascade="save-update" inverse="true">

session.flush();强制存储对象.
session.evict(Object);将对象从Cache中删除.
session.clear();删除所有Cache中对象.
在SQL Server、Oracle等数据库中,可以在Hibernate配置文件中设定属性hibernate.jdbc.batch_size来控制每多少条记录就存储至数据库.
<session-factory>
        <property name="hibernate.jdbc.batch_size">100</property>
</session-factory>
在MySQL中暂不支持该功能。


Hibernate本身并未提供二级缓存的实现,而是由第三方(Third-party)产品来实现,Hibernate预设使用EHCache作为其二级缓存的实现,在最简单的情况下,只需在Hibernate下撰写一个ehcache.xml作为EHCache的资源定义文件,可以在 Hibernate下载档案中的etc目录下找到一个已经撰写好的ehcache.xml

sessionFactory.evict(User.class, user.getId());消除二级缓存.

如果打算在Hibernate中使用其它第三方产品进行缓存,则可以在hibernate.cfg.xml中定义 hibernate.cache.provider_class属性<property name="hibernate.cache.provider_class"> org.hibernate.cache.HashtableCacheProvider </property>
HashtableCache是Hibernate自身提供的二级缓存实现,不过性能与功能上有限,只用于开发时期的测试之用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值