Hibernate 超实用解析(二)

双向的一对多关系:
要实现一个简单的从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("[email protected]");
user1.addEmail("[email protected]");
             
User user2 = new User();
user2.setEmails(new HashSet());
user2.setName("name1");
user2.addEmail("[email protected]");

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自身提供的二级缓存实现,不过性能与功能上有限,只用于开发时期的测试之用。

猜你喜欢

转载自plasterdoll.iteye.com/blog/1097816