天天看點

Hibernate 之單向多對一映射及其衍生問題

  由于在資料表之間可以通過外鍵進行關聯,在使用Hibernate操作映射到存在關聯關系的資料表的對象時,需要将對象的關聯關系與資料表的外鍵關聯進行映射。

  首先建立hibernate.cfg.xml和會話工廠類HibernateUtil,然後添加兩個待操作的實體類和相應的映射檔案。

HibernateUtil如下:

Hibernate 之單向多對一映射及其衍生問題
Hibernate 之單向多對一映射及其衍生問題

package com.zzh.util;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {
    private static SessionFactory sessionFactory;
    private static Session session;

    static {
        // 建立Configuration對象,讀取hibernate.cfg.xml檔案,完成初始化
        Configuration config = new Configuration().configure();
        StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder()
                .applySettings(config.getProperties());
        StandardServiceRegistry ssr=ssrb.build();
        sessionFactory=config.buildSessionFactory(ssr);
    }
    
    //擷取SessionFactory
    public static SessionFactory getSessionFactory(){
        return sessionFactory;
    }
    
    //擷取Session
    public static Session getSession(){
        session=sessionFactory.openSession();
        return session;
    }
    
    //關閉Session
    public static void closeSession(Session session){
        if(session!=null){
            session.close();
        }
    }
}      

View Code

兩個實體類班級Grade和學生Student

package com.zzh.entity;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

public class Grade implements Serializable {
    private int gid;
    private String gname;
    private String gdesc;

    public int getGid() {
        return gid;
    }

    public void setGid(int gid) {
        this.gid = gid;
    }

    public String getGname() {
        return gname;
    }

    public void setGname(String gname) {
        this.gname = gname;
    }

    public String getGdesc() {
        return gdesc;
    }

    public void setGdesc(String gdesc) {
        this.gdesc = gdesc;
    }

    public Grade() {
        super();
    }


    public Grade(int gid, String gname, String gdesc) {
        super();
        this.gid = gid;
        this.gname = gname;
        this.gdesc = gdesc;
    }

    public Grade(String gname, String gdesc) {
        super();
        this.gname = gname;
        this.gdesc = gdesc;
    }

}      
package com.zzh.entity;

import java.io.Serializable;

public class Student implements Serializable {
    private int sid;
    private String sname;
    private String sex;
    //在多方定義一個一方的引用
    private Grade grade;

    public Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }

    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Student() {
        super();
    }

    public Student(String sname, String sex) {
        super();
        this.sname = sname;
        this.sex = sex;
    }

}      

可以看出我在Student類中使用Grade類聲明了grade屬性,并添加了getter和setter,以展現實體類Student對Grade的關聯關系,在下面的映射表中隻需要在“多”的一方配置。注意,Student類中添加的grade屬性為Grade,它是一個持久化類Grade的對象屬性,不是一個基本類型屬性,是以不能用<property>元素來映射grade屬性,又因為是多對一關聯關系,要使用<many-to-one>元素。

Grade.hbm.xml:

Hibernate 之單向多對一映射及其衍生問題
Hibernate 之單向多對一映射及其衍生問題
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2016-8-31 11:19:40 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping>
    <class name="com.zzh.entity.Grade" table="GRADE">
        <id name="gid" type="int">
            <column name="GID" />
            <generator class="increment"/>
        </id>
        <property name="gname" type="java.lang.String">
            <column name="GNAME" />
        </property>
        <property name="gdesc" type="java.lang.String">
            <column name="GDESC" />
        </property>
    </class>
</hibernate-mapping>      

Student.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2016-8-31 11:19:40 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping>
    <class name="com.zzh.entity.Student" table="STUDENT">
        <id name="sid" type="int">
            <column name="SID" />
            <generator class="increment" />
        </id>
        <property name="sname" type="java.lang.String">
            <column name="SNAME" />
        </property>
        <property name="sex" type="java.lang.String">
            <column name="SEX" />
        </property>
        <many-to-one name="grade" class="com.zzh.entity.Grade" fetch="join">
            <column name="GRADE" />
        </many-to-one>
    </class>
</hibernate-mapping>      

<many-to-one>元素中的name指定Student類中關聯類的屬性名,column指定資料表關聯的外鍵。換句話說,實體類Student對Grade的多對一關聯在本質上是通過資料表student中的外鍵GRADE與資料表grade關聯實作的,但Hibernate将表之間的關聯通過<many-to-one>元素進行了封裝。

2.編寫與資料庫互動的UserDAO接口和其實作類UserDAOImpl,将資料的添加,查找,修改,删除等方法封裝其中。

UserDAO:

package com.zzh.dao;

public interface UserDAO {
    void save(Object obj);
    Object findById(int id,Object obj);
    void delete(Object obj);
    void update(Object obj);
}      

UserDAOImpl:

package com.zzh.dao;

import org.hibernate.Session;
import org.hibernate.Transaction;

import com.zzh.util.*;

public class UserDAOImpl implements UserDAO {

    @Override
    public void save(Object obj) {
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        try {
            session.save(obj);
            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
            tx.rollback();
        } finally {
            HibernateUtil.closeSession(session);
        }

    }

    @Override
    public Object findById(int id, Object obj) {

        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        try {
            obj = session.get(obj.getClass(), id);
            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            HibernateUtil.closeSession(session);
        }
        return obj;
    }

    @Override
    public void delete(Object obj) {
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        try {
            session.delete(obj);
            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            HibernateUtil.closeSession(session);
        }

    }

    @Override
    public void update(Object obj) {
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        try {
            session.update(obj);
            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            HibernateUtil.closeSession(session);
        }

    }

}      

注意:我的這個DAO使用的是Object對象,很多書上都是針對多個的對象建立多個DAO,也就是拆分為StudentDAOImpl和GradeDAOImpl,這樣裡面的Object就變為了相應的Student或者Grade。

如果你要改寫為我這樣的通用DAO,尤其要注意裡面的findById()方法,對于拆分寫的DAO,這個方法很簡單,隻需要傳入參數id然後Grade grade=(Grade) session.get(Grade.class, id);而我的方法因為是通用的,所有要傳入id和對應的實體對象,obj = session.get(obj.getClass(), id),這裡面運用了java的有關反射的知識,順便拓展一下。

1.利用對象調用getClass()方法擷取該對象的Class執行個體。比如 Students s = new Students();  Class c = s.getClass();2.使用Class的靜态方法forName(),用類的名字擷取一個Class執行個體;比如 Class c = Class.forName("Students");3.運用.class的方式擷取Class執行個體,對基本資料類型的封裝類,還可以采用.TYPE來擷取對應的基本資料類型的Class執行個體;Class c = Students.class; 是以對于我的例子而言,就是利用了obj.getClass()等價于Object.class這個特性來修改這個方法,大家可以自己試試。

3.添加測試案例JUnit

  請注意看我上面給出的hbm.xml檔案,主鍵生成機制是increment

Hibernate 之單向多對一映射及其衍生問題
Hibernate 之單向多對一映射及其衍生問題

現在問題又來了!!!當我用工具通過實體類生成hbm.xml檔案時,主鍵生成機制是

Hibernate 之單向多對一映射及其衍生問題

assigned表示對象辨別符由應用程式産生,如果不指定<generator>節點,則預設使用該政策。此時資料表還沒有建立,當我開始測試的時候,Hibernate根據cfg.xml中的資料庫資訊和相關映射資訊建立表,問題來了,Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '0' for key 'PRIMARY'。 資料表grade中出現 “0 Java一班 Java軟體開發一班 ”一條記錄,資料表student中出現 “ 0 慕女神  女 0 ”一條記錄,提示已經告訴我們主鍵重複,0主鍵已經存在,不能再進行添加了。assigned表示主鍵有程式代碼負責,你要保證這個主鍵資料是唯一的。這時我在想,那我用identity生成政策會怎麼樣,identity表示對象辨別符由底層資料庫的自增主鍵生成機制産生。這樣一搞 Caused by: java.sql.SQLException: Field 'SID' doesn't have a default value,馬上反應過來自己沒有在資料庫設定AUTO_increment,需要去設定兩個表的這個值。

Hibernate 之單向多對一映射及其衍生問題

這樣一搞,就可以正常添加了。

當我設定好自動遞增後,又想試試assigned,于是又改為了assigned,問題又來了Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 ;百度之後,有人這樣解釋:這個異常是由于主鍵設定為自增長,而在我們插入記錄的時候設定了ID的值導緻的。懵逼了,到現在我還在找怎樣解決,如果你有答案請告訴我。

之後我選用了increment生成政策,這個政策不需要在資料庫中設定自動遞增,感覺應付這種小例子很友善,不過他的局限性在于如果有多個應用執行個體向同一張表中插入資料時,則會出現重複的主鍵。需謹慎使用。

4.總結

  這篇文章感覺挺淩亂的,一會是DAO修改一會又是反射,最後還自己搞了一堆BUG,自己還是需要慢慢積累知識才行,如果你覺得這篇文章有點用,請幫忙點個贊,謝謝觀看。

  

作者:六月的餘晖

出處:http://www.cnblogs.com/zhaozihan/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連結,否則保留追究法律責任的權利。

繼續閱讀