1. 程式人生 > >Hibernate框架基礎——對映集合屬性

Hibernate框架基礎——對映集合屬性

集合對映

集合屬性大致有兩種

  • 單純的集合屬性,如像List、Set或陣列等集合屬性。
  • Map結構的集合屬性,每個屬性值都有對應的Key對映。

集合對映的元素大致有如下幾種

  • list:用於對映List集合屬性。
  • set:用於對映Set集合屬性。
  • map:用於對映Map集合性。
  • array:用於對映陣列集合屬性。
  • bag:用於對映無序集合。
  • idbag:用於對映無序集合,但為集合增加邏輯次序。

接下來我們就來一一詳解以上集合對映的元素。

Set

Set是無序,不可重複的集合
以案例的方式來介紹對映Set集合屬性,大概效果好點,並且在講解其他集合屬性時,我們都會遵循這點。試看這樣一個案例,在生活中,我們都已會網上購物,在填寫收貨地址時,我們大概都會寫多個收貨地址,想想看是不是這樣?我們可以剖析這個案例,作為介紹對映Set集合屬性的基石。
我們在Eclipse中新建一個普通的Java工程,如Hibernate_Test,接著我們搭建好Hibernate的開發環境,這個不說了。然後我們在cn.itcast.e_hbm_collection包下新建一個Java類——User.java,用於表示一個個使用者。

public class User {
    private Integer id; // 推薦使用包裝類
    private String name;

    private Set<String> addressSet; // 未初始化的Set集合

    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<String> getAddressSet() { return addressSet; } public void setAddressSet(Set<String> addressSet) { this.addressSet = addressSet; } }

接下來我們是該要創建出該類對應的對映配置檔案的,在做之前,我們總該要分析出資料庫中的表結構吧!只要分析出來,那建立相應的對映配置檔案還不是分分鐘的事情,我們可以用下圖來表示:
這裡寫圖片描述


這樣,我們就能依葫蘆畫瓢寫出對映配置檔案——User.hbm.xml了。記住,該對映配置檔案還得和User類待在一塊,即在同一個包下。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.e_hbm_collection">
    <class name="User" table="user">
        <id name="id">
            <generator class="native"></generator> 
        </id>
        <property name="name" />
        <!-- 
            addressSet屬性,Set集合
            table屬性:集合表的名字
            key子元素:集合外來鍵的列名
            element子元素:存放集合元素的列的資訊
        -->
        <set name="addressSet" table="user_addressSet">
            <key column="userId"></key>
            <element type="string" column="address"></element>
        </set>
    </class>
</hibernate-mapping>
  • table屬性:集合表的名字。
  • key子元素:集合外來鍵的列名。
  • element子元素:存放集合元素的列的資訊。

為了便於測試,我們總是會編寫一個單元測試類,所以我們還應在cn.itcast.e_hbm_collection包下創建出這樣一個類——App.java。

public class App {

    private static SessionFactory sessionFactory = new Configuration() //
            .configure() //
            .addClass(User.class) // 新增Hibernate實體類(載入對應的對映檔案)
            .buildSessionFactory();

    @Test
    public void testSave() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 構建物件
        Set<String> set = new HashSet<String>();
        set.add("御富科貿園xxx");
        set.add("棠東東路");

        User user = new User();
        user.setName("張天一");
        user.setAddressSet(set);

        // 儲存
        session.save(user);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    @Test
    public void testGet() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 獲取資料
        User user = (User) session.get(User.class, 1);
        // 顯示資訊
        System.out.println(user.getName());
        System.out.println(user.getAddressSet());

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

測試,會發現毫無問題,大發!
但有時候,我們會發現User類中的addressSet屬性已經初始化過了,如:

public class User {
    private Integer id; // 推薦使用包裝類
    private String name;

    private Set<String> addressSet = new HashSet<String>(); // Set集合

    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<String> getAddressSet() {
        return addressSet;
    }

    public void setAddressSet(Set<String> addressSet) {
        this.addressSet = addressSet;
    }
}

我都是初始化集合屬性的,因為這樣寫還是很有還好處的,那就是我們編寫測試程式碼的時候少寫一點程式碼,相應地單元測試類——App.java的程式碼就要改為:

public class App {

    private static SessionFactory sessionFactory = new Configuration() //
            .configure() //
            .addClass(User.class) // 新增Hibernate實體類(載入對應的對映檔案)
            .buildSessionFactory();

    @Test
    public void testSave() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 構建物件
        User user = new User();
        user.setName("張天二");
        user.getAddressSet().add("御富科貿園xxx");
        user.getAddressSet().add("棠東東路");

        // 儲存
        session.save(user);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    @Test
    public void testGet() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 獲取資料
        User user = (User) session.get(User.class, 2);
        // 顯示資訊
        System.out.println(user.getName());
        System.out.println(user.getAddressSet());

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

測試,一致通過,大發!
注意:對映Set集合屬性時,如果element元素包括not-null=”true”屬性,則集合屬性表以關聯持久化類的外來鍵和元素列作為聯合主鍵,否則該表沒有主鍵。但List集合屬性不會,List集合屬性總是以外來鍵列和元素次序列作為聯合主鍵

排序用的sort屬性

重申一遍,Set是無序,不可重複的集合。但有時不可避免地要讓Set集合中的元素具有一定順序,這時就可以用到sort屬性了,如我們要讓Set集合中的字串按照自然順序排序,我們可以修改對映配置檔案——User.hbm.xml為:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.e_hbm_collection">
    <class name="User" table="user">
        <id name="id">
            <generator class="native"></generator> 
        </id>
        <property name="name" />
        <!-- 
            addressSet屬性,Set集合
            table屬性:集合表的名字
            key子元素:集合外來鍵的列名
            element子元素:存放集合元素的列的資訊
            sort="unsorted|natural|comparatorClass"
                預設為:unsorted
        -->
        <set name="addressSet" table="user_addressSet" sort="natural">
            <key column="userId"></key>
            <element type="string" column="address"></element>
        </set>
    </class>
</hibernate-mapping>

sort屬性的取值有:unsorted、natural、comparatorClass,預設值為unsorted。
為了更加方便於我們看清Set集合中的字串是按照自然順序排序的,我們最好將App類的程式碼修改為:

public class App {

    private static SessionFactory sessionFactory = new Configuration() //
            .configure() //
            .addClass(User.class) // 新增Hibernate實體類(載入對應的對映檔案)
            .buildSessionFactory();

    @Test
    public void testSave() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 構建物件
        User user = new User();
        user.setName("張天一");
        user.getAddressSet().add("2御富科貿園");
        user.getAddressSet().add("1棠東東路");

        // 儲存
        session.save(user);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    @Test
    public void testGet() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 獲取資料
        User user = (User) session.get(User.class, 1);
        // 顯示資訊
        System.out.println(user.getName());
        System.out.println(user.getAddressSet());

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

當測試testSave()方法時,會發現報異常:java.lang.ClassCastException: java.util.HashSet cannot be cast to java.util.SortedSet。出現異常,我們就要解決嘛!解決辦法很簡單,我們只須修改App類的程式碼為:

public class App {

    private static SessionFactory sessionFactory = new Configuration() //
            .configure() //
            .addClass(User.class) // 新增Hibernate實體類(載入對應的對映檔案)
            .buildSessionFactory();

    @Test
    public void testSave() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 構建物件
        User user = new User();
        user.setName("張天一");
        user.setAddressSet(new TreeSet<String>()); // 當設定了sort屬性時,就要使用SortedSet型別
        user.getAddressSet().add("2御富科貿園");
        user.getAddressSet().add("1棠東東路");

        // 儲存
        session.save(user);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    @Test
    public void testGet() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 獲取資料
        User user = (User) session.get(User.class, 1);
        // 顯示資訊
        System.out.println(user.getName());
        System.out.println(user.getAddressSet());

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

測試,會發現Eclipse的控制檯列印:
這裡寫圖片描述

排序用的order by屬性

要讓Set集合中的元素具有一定順序,上面使用了sort屬性,但是該排序是在計算機記憶體中進行的,效率比較偏低,如果希望效率高點,我們可以使用order by屬性,它應該是就在資料庫中就排好序了。為了測試這點,我們將對映配置檔案——User.hbm.xml改為:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.e_hbm_collection">
    <class name="User" table="user">
        <id name="id">
            <generator class="native"></generator> 
        </id>
        <property name="name" />
        <!-- 
            addressSet屬性,Set集合
            table屬性:集合表的名字
            key子元素:集合外來鍵的列名
            element子元素:存放集合元素的列的資訊
            sort="unsorted|natural|comparatorClass"
                預設為:unsorted
            order-by屬性:寫的是order by子句,是SQL語句,是操作的集合表。
                這是在查詢資料時指定的order by子句。(排序推薦使用order-by屬性,效率高)
        -->
        <set name="addressSet" table="user_addressSet" order-by="address DESC">
            <key column="userId"></key>
            <element type="string" column="address"></element>
        </set>
    </class>
</hibernate-mapping>

我們又要修改App類的程式碼了。

public class App {

    private static SessionFactory sessionFactory = new Configuration() //
            .configure() //
            .addClass(User.class) // 新增Hibernate實體類(載入對應的對映檔案)
            .buildSessionFactory();

    @Test
    public void testSave() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 構建物件
        User user = new User();
        user.setName("張天一");
        // user.setAddressSet(new TreeSet<String>()); // 當設定了sort屬性時,就要使用SortedSet型別
        user.getAddressSet().add("2御富科貿園");
        user.getAddressSet().add("1棠東東路");

        // 儲存
        session.save(user);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    @Test
    public void testGet() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 獲取資料
        User user = (User) session.get(User.class, 2);
        // 顯示資訊
        System.out.println(user.getName());
        System.out.println(user.getAddressSet());

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

測試,我們會發現Eclipse控制檯列印:
這裡寫圖片描述

List

List是有序集合,因此持久化到資料庫時必須增加一列來表示集合元素的次序。
集合屬性只能以介面宣告(當持久化某個例項時,Hibernate會自動把程式中的集合實現類替換成Hibernate自己的集合實現類),因此下面程式碼中,addressList的型別只能是List,而不能是ArrayList。

private List<String> addressList = new ArrayList<String>(); // List集合

為了介紹對映List集合屬性,我們需要將User類的程式碼置為:

public class User {
    private Integer id; // 推薦使用包裝類
    private String name;

    private List<String> addressList = new ArrayList<String>(); // List集合

    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<String> getAddressList() {
        return addressList;
    }

    public void setAddressList(List<String> addressList) {
        this.addressList = addressList;
    }
}

同樣,在創建出該類所對應的對映配置檔案之前,我們還要分析出資料庫中的表結構!我們可以用下圖來表示:
這裡寫圖片描述
接下來我們就要修改對映配置檔案——User.hbm.xml的內容了。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.e_hbm_collection">
    <class name="User" table="user">
        <id name="id">
            <generator class="native"></generator> 
        </id>
        <property name="name" />
        <!-- 
            addressList屬性,List集合
            list-index:用於存放索引的列
        -->
        <list name="addressList" table="user_addressList">
            <key column="userId"></key>
            <list-index column="idx"></list-index>
            <element type="string" column="address"></element>
        </list>
    </class>
</hibernate-mapping>

對映說明

  • list元素要求用list-index的子元素來對映有序集合的次序列。
  • 集合的屬性的值會存放在另外的表中,須以外來鍵關聯,用Key元素來對映外來鍵列。
  • 當集合元素是基本資料型別及其包裝類,字串或日期型別時使用 element來對映集合屬性。

之後我們每講一個對映集合屬性,都遵循這樣一個原則——將資料庫hibernate_20160926中的user表和userXxx表刪乾淨,還給大家一片淨土,便於大家檢視測試後的結果。接下來,我們要將App類的程式碼修改為:

public class App {

    private static SessionFactory sessionFactory = new Configuration() //
            .configure() //
            .addClass(User.class) // 新增Hibernate實體類(載入對應的對映檔案)
            .buildSessionFactory();

    @Test
    public void testSave() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 構建物件
        User user = new User();
        user.setName("張天一");
        user.getAddressList().add("御富科貿園");
        user.getAddressList().add("棠東東路");
        user.getAddressList().add("棠東東路");

        // 儲存
        session.save(user);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    @Test
    public void testGet() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 獲取資料
        User user = (User) session.get(User.class, 1);
        // 顯示資訊
        System.out.println(user.getName());
        System.out.println(user.getAddressList());

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

測試,順利通過,大發!

陣列

陣列屬性的對映和List的處理方式基本一致。陣列使用<array>元素完成完成對映。
為了介紹對映陣列屬性,我們需要將User類的程式碼置為:

public class User {
    private Integer id; // 推薦使用包裝類
    private String name;

    private String[] addressArray; // 陣列

    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 String[] getAddressArray() {
        return addressArray;
    }

    public void setAddressArray(String[] addressArray) {
        this.addressArray = addressArray;
    }
}

接下來我們就要修改對映配置檔案——User.hbm.xml的內容了。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.e_hbm_collection">
    <class name="User" table="user">
        <id name="id">
            <generator class="native"></generator> 
        </id>
        <property name="name" />
        <!-- addressArray屬性,陣列。與List的對映基本一致  -->
        <array name="addressArray" table="user_addressArray">
            <key column="userId"></key>
            <list-index column="idx"></list-index>
            <element type="string" column="address"></element>
        </array>
    </class>
</hibernate-mapping>

最後,我們要將App類的程式碼修改為:

public class App {

    private static SessionFactory sessionFactory = new Configuration() //
            .configure() //
            .addClass(User.class) // 新增Hibernate實體類(載入對應的對映檔案)
            .buildSessionFactory();

    @Test
    public void testSave() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 構建物件
        User user = new User();
        user.setName("張天一");
        user.setAddressArray(new String[] { "御富科貿園", "棠東東路" });

        // 儲存
        session.save(user);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    @Test
    public void testGet() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 獲取資料
        User user = (User) session.get(User.class, 1);
        // 顯示資訊
        System.out.println(user.getName());
        System.out.println(Arrays.toString(user.getAddressArray()));

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

測試,順利通過,大發!

Map

  • Map不僅需要對映屬性值,還需要對映屬性Key。
  • Hibnernate將以外來鍵列和Key列作為聯合主鍵。
  • Map集合屬性使用map元素對映,該元素需要key和map-key兩個子元素。
    • key子元素用於對映外來鍵列。
    • map-key子元素則用於對映Map集合的Key。
    • map-key和element元素都必須確定type屬性。

為了介紹對映Map集合屬性,我們需要將User類的程式碼置為:

public class User {
    private Integer id; // 推薦使用包裝類
    private String name;

    private Map<String, String> addressMap = new HashMap<String, String>(); // Map集合

    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<String, String> getAddressMap() {
        return addressMap;
    }

    public void setAddressMap(Map<String, String> addressMap) {
        this.addressMap = addressMap;
    }
}

同樣,在創建出該類所對應的對映配置檔案之前,我們還要分析出資料庫中的表結構!我們可以用下圖來表示:
這裡寫圖片描述
接下來我們就要修改對映配置檔案——User.hbm.xml的內容了。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.e_hbm_collection">
    <class name="User" table="user">
        <id name="id">
            <generator class="native"></generator> 
        </id>
        <property name="name" />
        <!-- addressMap屬性,Map集合 -->
        <map name="addressMap" table="user_addressMap">
            <key column="userId"></key>
            <map-key type="string" column="key_"></map-key>
            <element type="string" column="address"></element>
        </map>
    </class>
</hibernate-mapping>

最後,我們要將App類的程式碼修改為:

public class App {

    private static SessionFactory sessionFactory = new Configuration() //
            .configure() //
            .addClass(User.class) // 新增Hibernate實體類(載入對應的對映檔案)
            .buildSessionFactory();

    @Test
    public void testSave() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 構建物件
        User user = new User();
        user.setName("張天一");
        user.getAddressMap().put("公司", "御富科貿園");
        user.getAddressMap().put("家庭", "棠東東路");

        // 儲存
        session.save(user);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    @Test
    public void testGet() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 獲取資料
        User user = (User) session.get(User.class, 1);
        // 顯示資訊
        System.out.println(user.getName());
        System.out.println(user.getAddressMap());

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

測試,順利通過,大發!

Bag

Bag的特性是:允許重複的元素,但無序。在Java的標準API中並沒有提供Bag容器,Hibernate提供自己的Bag實現,允許您將List對映為Bag。

  • bag元素既可以為List集合屬性對映,也可以為Collection集合屬性對映。
  • 不管是哪種集合屬性,使用bag元素都將被對映成無序集合,而集合屬性對應的表沒有主鍵。
  • bag元素只需要key元素來對映外來鍵列,使用element元素來對映集合屬性的每個元素。

為了介紹對映Bag集合屬性,我們需要將User類的程式碼置為:

public class User {
    private Integer id; // 推薦使用包裝類
    private String name;

    private List<String> addressBag = new ArrayList<String>();

    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<String> getAddressBag() {
        return addressBag;
    }

    public void setAddressBag(List<String> addressBag) {
        this.addressBag = addressBag;
    }
}

接下來我們就要修改對映配置檔案——User.hbm.xml的內容了。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.e_hbm_collection">
    <class name="User" table="user">
        <id name="id">
            <generator class="native"></generator> 
        </id>
        <property name="name" />
        <!-- addressBag屬性,Bag集合:無序,可重複。與Set集合的對映基本一致 -->
        <bag name="addressBag" table="user_addressBag">
            <key column="userId"></key>
            <element type="string" column="address"></element>
        </bag>
    </class>
</hibernate-mapping>

最後,我們要將App類的程式碼修改為:

public class App {

    private static SessionFactory sessionFactory = new Configuration() //
            .configure() //
            .addClass(User.class) // 新增Hibernate實體類(載入對應的對映檔案)
            .buildSessionFactory();

    @Test
    public void testSave() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 構建物件
        User user = new User();
        user.setName("張天一");
        user.getAddressBag().add("御富科貿園");
        user.getAddressBag().add("棠東東路");
        user.getAddressBag().add("棠東東路");

        // 儲存
        session.save(user);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    @Test
    public void testGet() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 獲取資料
        User user = (User) session.get(User.class, 1);
        // 顯示資訊
        System.out.println(user.getName());
        System.out.println(user.getAddressBag());

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

測試,順利通過,大發!