Hibernate 学习笔记(4)

Hibernate集合映射

<set>,<list>,<map>,<bag>,<array>,<primitive-array>

集合映射-Set

先介绍当Set中包括的对象为非实体(Entity)时的映射方式,简单的说,也就是所包括的对象没有对象识别(Identity),只是纯粹的值型态(Value type)对象)。

假设您有一个User类别,当中除了名称属性之外,另一个就是使用者的电子邮件地址,同一个使用者可能有多个不同的邮件地址,所以在User类别中使用 Set对象来加以记录,在这边使用String来记录每一笔邮件地址,为了不允许重复的邮件地址记录,所以使用Set对象,User类别如下: 

public class User {
    private Integer id;
    private String name;
    private Set emails; 

    // 必须要有一个预设的建构方法
    // 以使得Hibernate可以使用Constructor.newInstance()建立对象
    public User() {}
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    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容器中的数据,例如您可以分别建立user与email表格:

CREATE TABLE user (
    id INT(11) NOT NULL auto_increment PRIMARY KEY,
    name VARCHAR(100) NOT NULL default ''
 );

CREATE TABLE email (
    id INT(11) NOT NULL,
    address VARCHAR(100) NOT NULL
);

接着定义映像文件,使用<set>标签来定义Set映像:

User.hbm.xml 
<hibernate-mapping> 
    <class name=“com.cstp.User" table="user"> 
        <id name="id" column="id" type="java.lang.Integer"> 
            <generator class="native"/> 
        </id> 
        <property name="name" column="name" type="java.lang.String"/> 
        <set name="emails" table="email"> 
            <key column="id"/> 
            <element type="java.lang.String" 
                     column="address"/> 
        </set> 
    </class> 
</hibernate-mapping>

假设您如下储存对象:

User user1 = new User();
user1.setEmails(new HashSet());
user1.setName(“hhp");
user1.addEmail(“hhp@gmail.com");
user1.addEmail(“hhp@yahoo.com");
        
User user2 = new User();
user2.setEmails(new HashSet());
user2.setName(“tiantian");
user2.addEmail(“tian@gmail.com");

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();   

         
session.save(user1);
session.save(user2); 


tx.commit();
session.close();

集合映射-List

List是有序的结构,所以在储存List容器中的对象时,要一并储存其顺序信息,例如若您设计了以下的类别: 

User.java 


public class User {
    private Integer id;
    private String name;
    private List items; 

    // 必须要有一个预设的建构方法
    // 以使得Hibernate可以使用Constructor.newInstance()建立对象
    public User() {}

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

 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);
    }
}

在设计表格时,使用一个item表格来记录List容器信息,item表格必须包括索引信息例如您可以如下建立user与item表格:

CREATE TABLE user (
    id INT(11) NOT NULL auto_increment PRIMARY KEY,
    name VARCHAR(100) NOT NULL default ''
);

CREATE TABLE item (
    id INT(11) NOT NULL,
    position INT(11) NOT NULL,
    name VARCHAR(100) NOT NULL default ''
);

其中position字段要用来储存List的索引信息,可以使用<list>标签如下定义映像文件:

User.hbm.xml 

<hibernate-mapping> 

    <class name=“hhp.com.User" table="user"> 

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

        <property name="name" column="name" type="java.lang.String"/> 
 
        <list name="items" table="item"> 
            <key column="id"/> 
            <index column="position"/> 
            <element type="java.lang.String" column="name"/> 
        </list> 

    </class> 

</hibernate-mapping>

假设您如下储存对象:

User user1 = new User();
user1.setItems(new ArrayList());
user1.setName(“hhp");
user1.addItem("DC");
user1.addItem("CF Card");
        
User user2 = new User();
user2.setItems(new ArrayList());
user2.setName(“tiantian");
user2.addItem("comics");

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

集合映射-Map

Map的特性是key/value对,容器中的每一个对象都有一个key与之对应,所以将Map容器的数据储存至数据库时,必须一同储存它的key信息。

假设您设计了以下的类别: User.java 

import java.util.Map;

public class User {
    private Integer id;
    private String name;
    private Map items;

    // 必须要有一个预设的建构方法
    // 以使得Hibernate可以使用Constructor.newInstance()建立对象
    public User() {}

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    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);
    }
}

您可以建立以下的表格来分别储存user信息与当中的items信息:

CREATE TABLE user (
    id INT(11) NOT NULL auto_increment PRIMARY KEY,
    name VARCHAR(100) NOT NULL default ''
);

CREATE TABLE item (
    id INT(11) NOT NULL,
    name VARCHAR(100) NOT NULL default '',
    description VARCHAR(100) NOT NULL default ''
);

在映像文件方面,使用<map>标签来定义Map的映像,如下:User.hbm.xml 

<hibernate-mapping> 
    <class name=“com.cstp.User" table="user"> 

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

        <property name="name" 
                  column="name" 
                  type="java.lang.String"/> 
 
        <map name="items" table="item"> 
            <key column="id"/> 
            <index column="name" type="java.lang.String"/> 
            <element column="description" type="java.lang.String"/> 
        </map> 
 
    </class> 

</hibernate-mapping>

假设您使用以下的程序片段来储存User实例:

User user1 = new User();
user1.setItems(new HashMap());
user1.setName(“hhp");
user1.addItem("Book", "Java Gossip");
user1.addItem("DC", "Caxxx A80");
        
User user2 = new User();
user2.setItems(new HashMap());
user2.setName(“zhangy");
user2.addItem("Doll", "Snoppy world");     

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

session.save(user2);

tx.commit();
session.close();

集合映射-Bag

Bag是集合,与Set不同的是,Bag允许重复的元素,在Java的标准API中并没有提供Bag容器,Hibernate提供自己的Bag实现,允许您将List映射为Bag。

您可以如下定义User类别,其中的List成员将被用作Bag来使用,而不管对象在List容器中的顺序: User.java 

import java.util.List;

public class User {
    private Integer id;
    private String name;
    private List items;

    // 必须要有一个预设的建构方法
    // 以使得Hibernate可以使用Constructor.newInstance()建立对象
    public User() {}

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    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 name) {
        items.remove(name);
    }
}

最简单的Bag映像是使用<bag>标签,在这之前,假设您如下建立表格:

CREATE TABLE user (
    id INT(11) NOT NULL auto_increment PRIMARY KEY,
    name VARCHAR(100) NOT NULL default ''
);

CREATE TABLE item (
    id INT(11) NOT NULL,
    name VARCHAR(100) NOT NULL
);

接着定义映射文件,如下所示:User.hbm.xml

<hibernate-mapping> 
    <class name=“com.cstp.User" table="user"> 

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

        <property name="name" 
                  column="name" 
                  type="java.lang.String"/> 
 
        <bag name="items" table="item"> 
            <key column="id"/> 
            <element column="name" type="java.lang.String"/> 
        </bag> 
 
    </class> 

</hibernate-mapping>

假设您如下储存对象:

User user1 = new User();
user1.setItems(new ArrayList());
user1.setName(“hhp");
user1.addItem("Java Gossip");
user1.addItem("Java Gossip");
user1.addItem("Caxxx A80");
        
User user2 = new User();
user2.setItems(new ArrayList());
user2.setName(“zhangy");
user2.addItem("Snoppy world");        
        
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

session.save(user1);
session.save(user2);

tx.commit();
session.close();

您可以如下更新数据:

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

User user = (User) session.load(User.class, new Integer(1));
user.removeItem("Java Gossip");

tx.commit();
session.close();

然而注意观察在更新数据时所使用的SQL:

Hibernate: delete from item where id=?
Hibernate: insert into item (id, name) values (?, ?)
Hibernate: insert into item (id, name) values (?, ?)


由于Bag的数据允许重复,当必须更新数据时,无法确定要更新的是哪一笔数据,因而采取的方式是删除集合对象对应的所有数据,然后重新将集合对象中的数据写入数据库,显然的这种作法相当的没有效率。

作为Bag的一种扩充,Hibernate提供idbag,藉由在定义Bag映像时加上"collection-id",让Hibernate可以直接确定所要更新的数据,提高数据库操作的效率,

您可以先如下建立表格:

CREATE TABLE user (
    id INT(11) NOT NULL auto_increment PRIMARY KEY,
    name VARCHAR(100) NOT NULL default ''
);

CREATE TABLE item (
    cid CHAR(32) NOT NULL,
    id INT(11) NOT NULL,
    name VARCHAR(100) NOT NULL
);

其中item表格的cid就用于数据更新时定位之用,接着在映像文件中使用<idbag>标签加以定义:User.hbm.xml 

<hibernate-mapping> 
    <class name=“com.cstp.User" table="user"> 

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

        <property name="name" 
                  column="name" 
                  type="java.lang.String"/> 
 
        <idbag name="items" table="item"> 
            <collection-id column="cid" type="java.lang.String">
                <generator class="uuid.hex"/>
            </collection-id>
            <key column="id"/> 
            <element column="name" type="java.lang.String"/> 
        </idbag> 
 
    </class> 
</hibernate-mapping>

如果使用上面提到过的程序片段来更新对象的话,则实际上Hibernate会使用以下的SQL来进行更新:

Hibernate: delete from item where cid=?


这一次并不是整个删除集合中的数据,而是直接藉由cid来确定所要更新的数据,比起只使用Bag,idbag的效率好了许多

假设您建立了以下的表格:

CREATE TABLE user (
    id INT(11) NOT NULL auto_increment PRIMARY KEY,
    name VARCHAR(100) NOT NULL default ''
);

CREATE TABLE email (
    id INT(11) NOT NULL,
    address VARCHAR(100) NOT NULL
);

一个user可以有多个email,但不可重复,这可以使用 Set 来映像,在对应的对象方法,您可以如下设计对象:

package onlyfun.caterpillar;

import java.util.Set;

public class User {
    private Integer id;
    private Set emails;
    ....
}

假设您原先预定在Set中储存的是String型态,而后设计时考虑独立设计一个MailAddress类别,而Set中将储存MailAddress的实例,例如:

User.java 

import java.util.Set;

public class User {
    private Integer id;
    private String name;
    private Set emails; 

    // 必须要有一个预设的建构方法
    // 以使得Hibernate可以使用Constructor.newInstance()建立对象
    public User() {}

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

public void setName(String name) {
        this.name = name;
    }

    public Set getEmails() {
        return emails;
    }

    public void setEmails(Set emails) {
        this.emails = emails;
    }
 
    public void addEmail(MailAddress mailAddress) {
        this.emails.add(mailAddress);
    }
 
    public void removeEmail(MailAddress mailAddress) {
        this.emails.remove(mailAddress);
    }
}

MailAddress.java 

public class MailAddress {
    private String address;

    public MailAddress() {}
 
    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void sendMail() {
        System.out.println("Send mail to " + address);
    }
}

在映射文件方面,可以使用<composite-element>来为MailAddress作映射,如下:

User.hbm.xml 

<hibernate-mapping> 
    <class name=“com.cstp.User" table="user"> 
        <id name="id" column="id" type="java.lang.Integer"> 
            <generator class="native"/> 
        </id> 

        <property name="name" column="name" type="java.lang.String"/> 
 
        <set name="emails" table="email"> 
            <key column="id"/> 
            <composite-element class=“com.cstp.MailAddress"> 
                <property name="address" column="address"/> 
            </composite-element> 
        </set> 
    </class> 
</hibernate-mapping>

您可以如下储存对象:

User user = new User();
user.setName(“hhp");
        
user.setEmails(new HashSet());
MailAddress mailAddress = new MailAddress();
mailAddress.setAddress(“hhp@gmail.com");
user.addEmail(mailAddress);
        
mailAddress = new MailAddress();
mailAddress.setAddress(“hhp@yahoo.com");
user.addEmail(mailAddress);
        
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

session.save(user);

tx.commit();
session.close();

在查询时,address表格的数据会封装为MailAddress的实例,一个范例如下:

Session session = sessionFactory.openSession();
        
User user = (User) session.load(User.class, new Integer(1));

Iterator iterator = user.getEmails().iterator();
while(iterator.hasNext()) {
    MailAddress mailAddress = (MailAddress) iterator.next();
    mailAddress.sendMail();
}
session.close();

从数据库的观点来看,Set、Map、Bag是无序的,而List是有序的,这边所谓的无序或有序,是指将容器中对象储存至数据库时,是否依容器对象中的顺序来储存。

然而从数据库取得数据之后,您也许会希望Set、Map等容器中的对象可以依一定的顺序来排列,您可以从两个层次来容器中的对象排序,

一是在加载数据后于 JVM中排序,

另一是在数据库中直接使用order by子句来排序。

以 Set 这篇文章中的范例来作说明,要在JVM中就数据进行排序,您可以在映像文件中使用sort属性来定义容器的排序,这适用于Set与Map,例如: 

User.hbm.xml 

<hibernate-mapping> 
    <class name=“com.cstp.User" table="user"> 
        ....
        <set name="emails" table="email" sort="natural"
            <key column="id"/> 
            <element type="java.lang.String" 
                     column="address"/> 
        </set> 
    </class> 
</hibernate-mapping>

sort=“natural”表示使用对象的comparaTo()方法来进行排序,容器中的对象上必须有实例java.lang.Comparable 接口,例如String就有实例java.lang.Comparable接口,结果会使用字典顺序来排列容器中的对象。

您可以实现自己的排序方式,只要定义一个类别来实例java.util.Comparator接口,例如:

 

CustomComparator.java 

import java.util.Comparator;

public class CustomComparator implements Comparator {
    public int compare(Object o1, Object o2) {
        if (((String) o1).equals(o2)) 
            return 0;
        return ((Comparable) o1).compareTo(o2) * -1;
    }
}

在自定义的Comparator中,如果两个对象的顺序相同会传回0,而为了方便比较对象,要求传入的对象必须实例Comparable接口(例如 String对象就有实例Comparable接口),范例中只是简单的将原来compareTo()传回的值乘以负一,如此就可以简单的让排列顺序相反,接着可以在映射文件中指定自定的Comparator类别:

User.hbm.xml 

<hibernate-mapping> 

    <class name=“com.cstp.User" table="user"> 
        ....
        <set name="emails" table="email" 
             sort=“com.cstp.CustomComparator">
            <key column="id"/> 
            <element type="java.lang.String" 
                     column="address"/> 
        </set> 
    </class> 

</hibernate-mapping>

注意:Bag与List并不适用于这种方式。

另一个排序的方式则是在数据库中进行,直接使用order by子句来排序,这可以在映像文件中使用order-by属性来指定,例如: 

User.hbm.xml 

<hibernate-mapping> 

    <class name=“com.cstp.User" table="user"> 
        ....
        <set name="emails" table="email" order-by="address desc"
            <key column="id"/> 
            <element type="java.lang.String" 
                     column="address"/> 
        </set> 
    </class> 

</hibernate-mapping>

观察Hibernate所使用的SQL可以看到order by子句:

Hibernate: select emails0_.id as id0_, emails0_.address as address0_ from email emails0_ where emails0_.id=? order by emails0_.address desc

有时候您只是想要获得对象中某个属性的数据,如果您的对象中包括Set等容器对象,若从数据库中加载数据时全部加载所有的对象,却只是为了取得某个属性,显然的这样很没有效率。

以Set  中的范例来说,如果您只是想取得对象之后,显示对象的某些属性,例如name属性:

Session session = sessionFactory.openSession();
User user = (User) session.load(User.class, new Integer(1));
System.out.println(user.getName());
session.close();

在这个例子中,email的信息不必要从数据库中全部加载,在Hibernate中支持容器的延迟初始(Lazy onitialization),只有在真正需要容器对象中的数据时,才从数据库中取得数据,预设容器类会使用延迟加载的功能,例如上面的程序实际上会使用以下的SQL:

Hibernate: select user0_.id as id0_, user0_.name as name0_0_ from user user0_ where user0_.id=?

可以藉由映像文件中的lazy属性来设定是否使用延迟初始,例如在映射文件中如下设定:

User.hbm.xml 

<hibernate-mapping> 

    <class name=“com.cstp.User" table="user"> 
        ....
        <set name="emails" table="email" lazy="false"> 
            <key column="id"/> 
            <element type="java.lang.String" 
                     column="address"/> 
        </set> 
    </class> 

</hibernate-mapping>

由于lazy属性被设定为false,延迟初始的功能被关闭,所以上面的程序会使用以下的SQL来查询:

Hibernate: select user0_.id as id0_, user0_.name as name0_0_ from user user0_ where user0_.id=?
Hibernate: select emails0_.id as id0_, emails0_.address as address0_ from email emails0_ where emails0_.id=?

所有的容器对象之数据一并被查询了,即使程序中还不会使用到容器中的对象信息。

在启用延迟初始的情况下,如果如下查询数据:

Session session = sessionFactory.openSession();
User user = (User) session.load(User.class, new Integer(1));

System.out.println(user.getName());
Iterator iterator = user.getEmails().iterator();
while(iterator.hasNext()) {
    System.out.println(iterator.next());
}
session.close();

可以看到,只有在需要查询容器中对象时,才会向数据库索取数据。

使用延迟初始时,由于在需要数据时会向数据库进行查询,所以session不能关闭,如果关闭会丢出 LazyInitializationException 例外,例如下面的程序就会丢出例外:

Session session = sessionFactory.openSession();
User user = (User) session.load(User.class, new Integer(1));
System.out.println(user.getName());
session.close();

Iterator iterator = user.getEmails().iterator();
while(iterator.hasNext()) {
    System.out.println(iterator.next());
}

如果您使用了延迟初始,而在某些时候仍有需要在session关闭之后取得相关对象,则可以使用Hibernate.initialize()来先行加载相关对象,例如: 

Session session = sessionFactory.openSession();
User user = (User) session.load(User.class, new Integer(1));
System.out.println(user.getName());

Hibernate.initialize(user); // 先加载容器中的对象

session.close();

Iterator iterator = user.getEmails().iterator();
while(iterator.hasNext()) {
    System.out.println(iterator.next());
}

即使启用延迟初始,在Hibernate.initialize()该行,email容器中的对象已经被加载,所以即使关闭session也无所谓了,这种情况发生在某个情况下,您启用了延迟初始,而使用者操作过程中,会在稍后一阵子才用到email容器,您不能浪费session在这段时间的等待上,所以您先行加载容器对象,直接关闭session以节省资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值