Hibernate(6)—— 一對多 和 多對多關聯關係映射(xml和註解)總結

俗話說,本身寫的代碼,6個月後也是別人的代碼……複習!複習!複習!涉及的知識點總結以下:java

  • One to Many 映射關係
    • 多對一單向外鍵關聯(XML/Annotation)
    • 一對多單向外鍵關聯(XML/Annotation)
    • 懶加載和積極加載
    • 一對多雙向外鍵關聯(XML/Annotation)
  • Many to Many 映射關係
    • 多對多單向外鍵關聯(XML/Annotation)
    • 多對多雙向外鍵關聯(XML/Annotation)
    • set的inverse元素詳解
  • 問題小結
  • 關聯關係的優缺點

  多對一單向外鍵關聯關係數據庫

  注意多對一關聯是多方持有一方的引用。看一個例子,去淘寶購物,那麼一個淘寶用戶能夠對應多個購物訂單,如圖所示:緩存

  多的一方是Orders,持有一方的引用,也就是Users,而在Users中無需做任何定義,從訂單到用戶的關係是單向多對一關聯。對應數據庫就是:session

  還有好比說學生和班級的關係,多個學生能夠屬於同一個班級,這就是從學生到班級也是典型的單向多對一關係,看代碼實現:架構

  

  基於註解的多對一單向外鍵關聯:併發

  單向多對一關聯中,多方須要持有一方的引用,那麼多方(學生類)須要額外配置,須要對持有的一方引用使用註解@ManyToOne (cascade={CascadeType.ALL}, fetch=FetchType.EAGER),設置爲級聯操做和飢渴的抓取策略,@JoinColumn(name="cid"),而一方(教室類)無需作任何多方的定義。app

  注意;多方必須保留一個不帶參數的構造器!框架

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

//班級類,在多對一關係中屬於一的方,不持有其餘多餘的配置,反而是被多方持有
@Entity
public class ClassRoom {
    private int cid;//班級編號

    private String cname;//班級名稱

//    自動增加的主鍵
    @Id
    @GeneratedValue
    public int getCid() {
        return cid;
    }

    public void setCid(int cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }
}
View Code

一方——班級類無需作多餘的定義,下面是多方——學生實體和配置:ide

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

//學生實體類,屬於多對一的多方,持有班級(一方)的引用
@Entity
public class Students {
    private int sid;  //編號

    private String sname; //姓名

    private ClassRoom classroom;//學生班級
    
    //注意:多方必定要顯式的定義不帶參數的構造方法
    public Students() {
    }

    public Students(String sname)
    {
        this.sname = sname;
    }
//    多方使用註解:@ManyToOne
//  fetch=FetchType.EAGER,急加載,加載一個實體時,定義急加載的屬性會當即從數據庫中加載。
//    所有級聯操做,referencedColumnName顯式設置數據庫字段名cid,不寫默認就是和name同樣的。
    @ManyToOne (cascade={CascadeType.ALL}, fetch=FetchType.EAGER)
    @JoinColumn(name="cid",referencedColumnName="cid")
    public ClassRoom getClassroom() {
        return classroom;
    }

    public void setClassroom(ClassRoom classroom) {
        this.classroom = classroom;
    }

    //    自動增加主鍵
    @Id
    @GeneratedValue
    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;
    }
}
View Code

下面測試:先生成數據庫腳本,再進行學生對象的插入性能

public class TestStudentsByAnno {
    private static SessionFactory sessionFactory;
    
    @Before
    public void setUp() throws Exception {
        System.out.println("setUp()...");
        sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
    }

    @After
    public void tearDown() throws Exception {
        System.out.println("tearDown()...");
        sessionFactory.close();
    }

    @Test
    public void testSave() {
        Session session = sessionFactory.getCurrentSession();
        Transaction tx = session.beginTransaction();

        try    {
            ClassRoom c = new ClassRoom();
            c.setCname("computer001");

            Students s = new Students("zhangsan");
            s.setClassroom(c);

            session.save(s);    
            tx.commit();
        } catch(Exception ex) {
            ex.printStackTrace();
            tx.rollback();
        }
    }

    @Test
    @Ignore
    public void testSchemaExport() {
        SchemaExport se = new SchemaExport(new AnnotationConfiguration().configure());
        se.create(true, true);
    }
}
View Code

反向建立表的數據庫腳本以下:

create table ClassRoom (cid integer not null auto_increment, cname varchar(255), primary key (cid))
create table Students (sid integer not null auto_increment, sname varchar(255), cid integer, primary key (sid))

插入一個學生對象,會自動生成以下語句:

            ClassRoom c = new ClassRoom();
            c.setCname("computer001");

            Students s = new Students("zhangsan");
            s.setClassroom(c);

            session.save(s);    
            tx.commit();
View Code

Hibernate: insert into ClassRoom (cname) values (?)

Hibernate: insert into Students (cid, sname) values (?, ?)

插入成功:

 

  基於xml配置實現多對一單向外鍵關聯

<hibernate-mapping>
  <class name="net.nw.vo.fk.mto.ClassRoom" table="classroom">
    <id name="cid" column="cid" type="int">
      <generator class="native"/>
    </id>
    
    <property name="cname" column="cname" type="string"/>
  </class>
</hibernate-mapping>
View Code

  一方(教室類)無需作任何多方的定義。只須要維護好本身的屬性配置便可。而多方只須要加上<many-to-one name="" column=「"/>就ok。

<hibernate-mapping>
  <class name="net.nw.vo.fk.mto.Students" table="students">
    <id name="sid" column="sid" type="int">
      <generator class="native"/>
    </id>
    
    <property name="sname" column="sname" type="string"/>
    
    <many-to-one name="classroom" column="cid"/>
  </class>
</hibernate-mapping>
View Code

  hibernate.cfg.xml里加上

        <mapping resource="net/nw/vo/fk/mto/ClassRoom.hbm.xml" />
        <mapping resource="net/nw/vo/fk/mto/Students.hbm.xml" />
View Code

注意:若是沒有設置級聯ALL,那麼須要在保存的時候先保存班級,在保存學生,不然出錯: object references an unsaved transient instance - save the transient instance before flushing: 

            ClassRoom classRoom = new ClassRoom();
            classRoom.setCname("CS");

            Students students = new Students("111");
            students.setClassroom(classRoom);

            session.save(classRoom);
            session.save(students);
            tx.commit();
View Code

  小結:使用<many-to-one>元素進行多對一關聯關係配置,name屬性  指定類的屬性名,column屬性 指定庫表字段名,class屬性  指定類屬性類型(加上姓,即包名),not-null屬性 指定屬性是否容許爲空,cascade屬性 指定是否級聯保存和更新:save-update、delete、all、none。

  

  一對多單向外鍵關聯

  當類與類創建了關聯,程序能很方便的從一個對象導航到另外一個或一組與之關聯的對象,有了student對象,就能夠經過student對象獲得這個學生所屬的班級的信息——students.getClassroom();,對於班級對象,若是想要獲得某個學生的信息,怎麼辦呢?這時候能夠反過來控制,一方控制多方,下面進行一對多單向外鍵關聯。

  簡單說就是和以前多對一相反,以前是多方持有一方的引用,而一對多關聯關係是一方持有多方的集合的引用,注意區別:這裏是持有多方的集合

 

  基於註解的配置:

   @OneToMany(cascade={CascadeType.ALL},fetch=FetchType.LAZY),@JoinColumn(name=""),除了級聯以外,還要設置一方爲懶加載模式。且外鍵仍是加在了多方學生表裏,只不過控制權變了,以前多對一關聯是多方學生持有班級外鍵,控制班級,如今一對多關聯,表裏仍是多方學生持有班級一方的外鍵,只不過控制權交給了班級,讓班級控制學生。不要混淆。

import javax.persistence.*;
import java.util.Set;

//班級類是一方,一方持有多方的引用
@Entity
public class ClassRoom {
    private int cid;//班級編號

    private String cname;//班級名稱

    private Set<Students> stus ;//班級的學生集合是多方

//    如今是一方維護多方了,主控權交給了一方,設置級聯,一方要設置懶加載,推薦!
    @OneToMany(cascade={CascadeType.ALL},fetch=FetchType.LAZY)
    @JoinColumn(name="cid") // 設置一方的外鍵,這裏是cid,由於實際上這個外鍵仍是加在多方,只不過控制權變了。
    public Set<Students> getStus() {
        return stus;
    }

    public void setStus(Set<Students> stus) {
        this.stus = stus;
    }

    @Id
    @GeneratedValue
    public int getCid() {
        return cid;
    }

    public void setCid(int cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }
}
View Code

  注意,不論多對一仍是一對多,多方都要顯式保留無參構造器。

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

//學生實體類
@Entity
public class Students {
    private int sid;  //編號

    private String sname; //姓名
    
    //注意:必定要保留這個默認不帶參數的構造方法
    public Students(){
        
    }

    public Students(String sname)
    {
        this.sname = sname;
    }

    @Id
    @GeneratedValue
    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;
    }
}
View Code

  執行數據庫腳本,發現一方(主控方)仍是和以前多對一的表結構同樣,多方也是如此。

create table ClassRoom (cid integer not null auto_increment, cname varchar(255), primary key (cid))
create table Students (sid integer not null auto_increment, sname varchar(255), cid integer, primary key (sid))

  執行測試,保存學生,由於如今關係是一方維護,控制多方。確定保存主控方——班級(和以前相反,以前多對一保存的是多方學生對象),可是本質上仍是先保存的學生班級,再自動保存學生,這點和多對一本質同樣。

            Set<Students> stus = new HashSet<>();
            stus.add(new Students("zhangsan"));
            stus.add(new Students("lisi"));
            stus.add(new Students("wangwu"));
            stus.add(new Students("zhaoliu"));
            stus.add(new Students("sunqi"));

            ClassRoom c = new ClassRoom();
            c.setCname("cs001");
            c.setStus(stus);

            session.save(c);
            tx.commit();
View Code

  生成的腳本以下:先插入外鍵的班級對象,在執行五個學生的插入操做,最後執行五個更新,爲sid=1。。。5的學生,更新cid爲2

Hibernate: insert into ClassRoom (cname) values (?)
Hibernate: insert into Students (sname) values (?)
Hibernate: insert into Students (sname) values (?)
Hibernate: insert into Students (sname) values (?)
Hibernate: insert into Students (sname) values (?)
Hibernate: insert into Students (sname) values (?)
Hibernate: update Students set cid=? where sid=?
Hibernate: update Students set cid=? where sid=?
Hibernate: update Students set cid=? where sid=?
Hibernate: update Students set cid=? where sid=?
Hibernate: update Students set cid=? where sid=?
View Code

  

  總結:多對一時候,多方設置EAGER,一方設置LAZY,也就是說,若是是多對一,多方控制一方,那麼多方設置積極加載,一方無需多餘配置,反過來,若是是一對多關係,一方控制多方,那麼一方設置懶加載,多方無需多餘配置,可是不論哪一種,多方都顯式加上一個不帶參數的構造器。

  

  一對多裏的懶加載

  記得以前總結,get和load的查詢方式源碼的時候,就總結了一下懶加載load裏的應用,以前說Hibernate中,當訪問的數據量過大時,用緩存也不太合適, 由於內存容量有限 ,爲了減小併發量,減小系統資源的消耗,Hibernate用懶加載機制來彌補這種缺陷,可是這只是彌補而不是用了懶加載整體性能就提升了。懶加載也被稱爲延遲加載,它在查詢的時候不會馬上訪問數據庫,而是返回代理對象,好比以前總結的load方式查詢,當真正去使用對象的時候纔會訪問數據庫。除了load查詢默認使用懶加載,如今咱們的一對多關聯映射也使用了lazy加載,下面進行實際驗證測試:

    public void testQuery()    {
        Session session = sessionFactory.getCurrentSession();
        Transaction tx = session.beginTransaction();

        try    {
            // 首先查詢班級,cid=1的班級
            ClassRoom c =(ClassRoom) session.get(ClassRoom.class, 1);

            // 經過班級導航到學生,遍歷學生獲得名字
            for(Students s : c.getStus()) {
                System.out.println("姓名 :" + s.getSname());
            }
            
            tx.commit();
        } catch(Exception ex) {
            ex.printStackTrace();
            tx.rollback();
        }
    }
View Code

  執行以後,debug發現:在沒有使用班級對象的時候,只有這樣一條SQL語句:從classroom表查詢,cid=1的班級,使用使用AS賦給列一個別名。

Hibernate: select classroom0_.cid as cid0_0_, classroom0_.cname as cname0_0_ from ClassRoom classroom0_ where classroom0_.cid=?
View Code

  等執行到for了,纔打印這一語句:

Hibernate: select stus0_.cid as cid0_1_, stus0_.sid as sid1_, stus0_.sid as sid1_0_, stus0_.sname as sname1_0_ from Students stus0_ where stus0_.cid=?
View Code

  充分說明這是執行的懶加載模式。一方控制多方,一方設置懶加載,若是什麼都不設置,會是什麼狀況?通過驗證,發現和顯式設置懶加載效果同樣,也就是說,one-to-many(元素)的懶加載是默認的,這是必須的,是經常使用的策略。一對多的時候,查詢主對象時默認是懶加載。即:查詢主對象的時候不會把從對象查詢出來,使用從對象的時候才加載從對象。

  若是人爲設置爲積極加載,則直接所有查詢,@OneToMany(cascade={CascadeType.ALL},fetch=FetchType.EAGER),SQL語句爲以下,進行了外鏈接的查詢。一次性查了主對象和從對象出來。

Hibernate: select classroom0_.cid as cid0_1_, classroom0_.cname as cname0_1_, stus1_.cid as cid0_3_, stus1_.sid as sid3_, stus1_.sid as sid1_0_, stus1_.sname as sname1_0_ from ClassRoom classroom0_ left outer join Students stus1_ on classroom0_.cid=stus1_.cid where classroom0_.cid=?
View Code

 

  基於xml文件配置

  一方做爲主控方:

<set name="" >

   <key column=""/>

   <one-to-many class= "" />

</set>
View Code

  一方是班級,持有多方的集合,以下配置:

<hibernate-mapping>
  <class name="net.nw.vo.fk.otm.ClassRoom" table="classroom">
    <id name="cid" column="cid" type="int">
      <generator class="native"/>
    </id>

    <property name="cname" column="cname" type="string"/>

    <set name="stus" >
      <!-- 外鍵仍是班級cid -->
      <key column="cid"/>
      <one-to-many class="net.nw.vo.fk.otm.Students" />
    </set>
  </class>
</hibernate-mapping>
View Code

  多方是學生,做爲從對象,別忘了,保留無參構造器,以下配置:

<hibernate-mapping>
  <class name="net.nw.vo.fk.otm.Students" table="students">
    <id name="sid" column="sid" type="int">
      <generator class="native"/>
    </id>

    <property name="sname" column="sname" type="string"/>
  </class>
</hibernate-mapping>
View Code

  

  一對多雙向外鍵關聯

  其實相似以前的一對一雙向外鍵關聯,也是互相持有對方的引用,故也叫雙向一對多自身關聯。多方持有一方的引用,@ManyToOne(cascade={CascadeType.ALL}),@JoinColumn(name="")。反過來,一方也持有多方的集合,@OneToMany(cascade={CascadeType.ALL}),@JoinColumn(name="")。代碼以下:

  基於註解的配置:

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

//學生實體類,屬於多方,持有一方的引用
@Entity
public class Students {
    private int sid;  //編號

    private String sname; //姓名

    private ClassRoom classroom;//學生班級屬於一方

    //注意:必定要在多方保留這個默認不帶參數的構造方法
    public Students() {

    }

    public Students(String sname) {
        this.sname = sname;
    }

//    多方是設置積極加載,所有級聯
    @ManyToOne (cascade={CascadeType.ALL}, fetch=FetchType.EAGER)
    @JoinColumn(name="cid",referencedColumnName="cid") // 外鍵設置爲班級id,cid
    public ClassRoom getClassroom() {
        return classroom;
    }

    public void setClassroom(ClassRoom classroom) {
        this.classroom = classroom;
    }

    @Id
    @GeneratedValue
    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;
    }
}
View Code

  關鍵是一方,也必須持有多方的集合,造成你中有我,我中有你的局面,互相控制。可是仍是注意,本質上,數據庫表裏外鍵cid仍是加在了學生表——多方的表裏。

import javax.persistence.*;
import java.util.Set;

//班級類
@Entity
public class ClassRoom {
    private int cid;//班級編號

    private String cname;//班級名稱

    private Set<Students> stus; // 一方也持有了多方:學生的集合引用

//    一方也要控制多方,一方設置懶加載,外鍵仍是cid,也就是外鍵仍是加在多方——學生表。
    @OneToMany(cascade={CascadeType.ALL},fetch=FetchType.LAZY)
    @JoinColumn(name="cid")
    public Set<Students> getStus() {
        return stus;
    }

    public void setStus(Set<Students> stus) {
        this.stus = stus;
    }

    @Id
    @GeneratedValue
    public int getCid() {
        return cid;
    }

    public void setCid(int cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }
}
View Code

  測試腳本生成。和以前表同樣,只不過控制權雙方都有了:

alter table Students drop foreign key FK73AC29B8559B6D03
drop table if exists ClassRoom
drop table if exists Students
create table ClassRoom (cid integer not null auto_increment, cname varchar(255), primary key (cid))
create table Students (sid integer not null auto_increment, sname varchar(255), cid integer, primary key (sid))
alter table Students add index FK73AC29B8559B6D03 (cid), add constraint FK73AC29B8559B6D03 foreign key (cid) references ClassRoom (cid)
View Code

   此時先保存誰均可以!控制權是雙方都有。

 

  基於xml配置

  多方:<many-to-one name="group" column="gid"></many-to-one>,一方:記住,一方是持有集合
<set name="" >

   <key column=""></key>

   <one-to-many class=""/>

</set>
View Code

  本例代碼以下:

<hibernate-mapping>
  <class name="net.nw.vo.bfk.mto.Students" table="students">
    <id name="sid" column="sid" type="int">
      <generator class="native"/>
    </id>
    
    <property name="sname" column="sname" type="string"/>
    
    <many-to-one name="classroom" column="cid"/>
  </class>
</hibernate-mapping>

----------------------------------------------------------------------------------

<hibernate-mapping>
  <class name="net.nw.vo.bfk.mto.ClassRoom" table="classroom">
    <id name="cid" column="cid" type="int">
      <generator class="native"/>
    </id>

    <property name="cname" column="cname" type="string"/>

    <set name="stus" >
      <key column="cid"/>
      <one-to-many class="net.nw.vo.bfk.mto.Students" />
    </set>
  </class>
</hibernate-mapping>
View Code

 

  小結:在關係模型中,只存在外鍵參照關係,並且是many方參照one方。

 

  多對多的關聯關係映射

  如今有一個角色類,和一個特權類,前者保存了都有哪些人(角色)擁有哪些特權,後者保存的是一些特權,好比能夠作什麼,不能夠作什麼等讓哪些人擁有。如圖類關係:

  這就是一個多對多的例子,他們之間在數據庫如何實現的關聯呢?顯然不能互相持有對方主鍵作外鍵,那麼就須要用到一個新的表——映射表做爲中間表:

  再舉一個最熟悉的學生的例子,現實中,學生和教師就構成了多對多的關聯關係。一個教師能夠教不少學生,同時一個學生能夠師從不少老師,拿這個例子說明。

 

  多對多單向外鍵關聯

  其中一個多方持有另外一個多方的集合對象,並且前面也分析了,還須要建立一箇中間表,先看兩個實體對象配置
  
   基於註解的多對多單向外鍵關係配置:
  一個多方持有另外一個多方的集合對象,我讓學生持有老師的集合對象引用,一樣的另外一個多方——老師不作多餘配置,且多方要顯式設置一個無參構造器,那麼學生須要使用註解@ManyToMany和@JoinTable,設置級聯所有 cascade=CascadeType.ALL,代碼以下:
import javax.persistence.*;
import java.util.Set;

//學生實體類
@Entity
public class Students {
    private int sid;  //編號

    private String sname; //姓名

    private Set<Teachers> teachers ;  // 我設置學生這個多方去持有老師這個多方的集合,去控制老師

    //注意:必定要保留這個默認不帶參數的構造方法
    public Students() {
        
    }

    public Students(String sname)
    {
        this.sname = sname;
    }

//    先設置多對多的關聯,以後必須生成一箇中間表,使用JoinTable註解
    @ManyToMany(cascade=CascadeType.ALL)
    @JoinTable(
//      設置中間表名
      name="teachers_students",
//      指定當前對象的外鍵,本表在中間表的外鍵名稱
      joinColumns={@JoinColumn(name="sid")},
//      指定關聯對象的外鍵,另外一個表在中間表的外鍵名稱。
      inverseJoinColumns={@JoinColumn(name="tid")}
     )
    public Set<Teachers> getTeachers() {
        return teachers;
    }

    public void setTeachers(Set<Teachers> teachers) {
        this.teachers = teachers;
    }
    
    @Id
    @GeneratedValue
    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;
    }
}
View Code

  另外一個多方,老師不作多餘配置:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Teachers {
    private int tid;//教師的編號

    private String tname;//教師姓名
    
    public Teachers() {
        
    }

    public Teachers(String tname)
    {
      this.tname = tname; 
    }
    
    @Id
    @GeneratedValue
    public int getTid() {
        return tid;
    }

    public void setTid(int tid) {
        this.tid = tid;
    }

    public String getTname() {
        return tname;
    }

    public void setTname(String tname) {
        this.tname = tname;
    }
}
View Code

  生成的數據庫腳本以下:

create table Students (sid integer not null auto_increment, sname varchar(255), primary key (sid))
create table Teachers (tid integer not null auto_increment, tname varchar(255), primary key (tid))
create table teachers_students (sid integer not null, tid integer not null, primary key (sid, tid))

  主要關注中間表,sid和tid都是做爲了中間表的聯合主鍵,他們同時也是外鍵:

  下面進行插入數據的測試,由於學生持有老師集合引用,且設置了級聯,故直接保存學生就ok:
            // 由於學生持有教師的集合,先設置教師
            Set<Teachers> teachers = new HashSet<>();
            
            teachers.add(new Teachers("Wang"));
            teachers.add(new Teachers("Li"));
            teachers.add(new Teachers("Song"));
            teachers.add(new Teachers("Zhang"));

            Students s = new Students();
            s.setSname("zhangsan");
            s.setTeachers(teachers);

            session.save(s);
            tx.commit();
View Code

 

  基於xml的多對多單向外鍵關係配置:

  學生這個多方持有老師的集合,那麼持有對方集合的學生映射文件配置以下: 

<hibernate-mapping>
  <class name="net.nw.vo.fk.mtm.Students" table="students">
    <id name="sid" column="sid" type="int">
      <generator class="native"/>
    </id>

    <property name="sname" column="sname" type="string"/>

    <!-- 學生表持有老師的集合,以下進行配置 -->
    <set name="teachers" table="students_teachers" cascade="all">
      <!-- table設置中間表,級聯是all -->
      <!-- key設置本對象在中間表的外鍵sid -->
      <key column="sid"/>
      <!-- many-to-many 標籤設置對方的表(老師)在中間表的外鍵tid -->
      <many-to-many class= "net.nw.vo.fk.mtm.Teachers" column="tid"/>
    </set>
  </class>
</hibernate-mapping>
View Code

  老師表配置就簡單了:

<hibernate-mapping>
  <class name="net.nw.vo.fk.mtm.Teachers" table="teachers">
    <id name="tid" column="tid" type="int">
      <generator class="native"/>
    </id>

    <property name="tname" column="tname" type="string"/>
  </class>
</hibernate-mapping>
View Code

  進行測試(刪除以前的表,先刪除中間表,在刪除老師表,最後刪除學生表):

            Set<Teachers> teachers = new HashSet<>();
            
            teachers.add(new Teachers("Teacher Wang"));
            teachers.add(new Teachers("Teacher Li"));
            teachers.add(new Teachers("Teacher Song"));
            teachers.add(new Teachers("Teacher Zhang"));

            Students s = new Students();
            s.setSname("zhangsan");
            s.setTeachers(teachers);

            session.save(s);
            tx.commit();
View Code

 

  多對多雙向外鍵關聯

  和以前的相似,是互相持有對方的集合,雙方持有對方的集合對象,其中一方設置@ManyToMany(mappedBy=""),另外一方:

@ManyToMany

  @JoinTable(

  name="",

  joinColumns={@JoinColumn(name="")},

  inverseJoinColumns={@JoinColumn(name="")}

)
View Code

  基於註解的配置,看具體代碼:

import javax.persistence.*;
import java.util.Set;

//學生實體類
@Entity
public class Students {
    private int sid;  //編號

    private String sname; //姓名

    private Set<Teachers> teachers ;

    //注意:必定要保留這個默認不帶參數的構造方法
    public Students() {
    }

    public Students(String sname)
    {
        this.sname = sname;
    }
     
    @ManyToMany(cascade=CascadeType.ALL)
    @JoinTable(
      name="teachers_students",
      joinColumns={@JoinColumn(name="sid")},
      inverseJoinColumns={@JoinColumn(name="tid")}
     )
    public Set<Teachers> getTeachers() {
        return teachers;
    }

    public void setTeachers(Set<Teachers> teachers) {
        this.teachers = teachers;
    }
    
    @Id
    @GeneratedValue
    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;
    }
}
View Code

  關鍵是另外一方的配置,前面總結了,雙向關聯不會真的是互相維持,只能交給一方去維護:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import java.util.Set;

@Entity
public class Teachers {
    private int tid;//教師的編號

    private String tname;//教師姓名

    private Set<Students> stus ;

    public Teachers() {
    }

    public Teachers(String tname)
    {
      this.tname = tname; 
    }

//    把控制權交給student類——teachers集合引用
    @ManyToMany(mappedBy="teachers") 
    public Set<Students> getStus() {
        return stus;
    }

    public void setStus(Set<Students> stus) {
        this.stus = stus;
    }
    
    @Id
    @GeneratedValue
    public int getTid() {
        return tid;
    }

    public void setTid(int tid) {
        this.tid = tid;
    }

    public String getTname() {
        return tname;
    }

    public void setTname(String tname) {
        this.tname = tname;
    }
}
View Code

  生成數據庫腳本:(和以前的單向多對多同樣的表結構,關鍵看實體中控制權的變化)

create table Students (sid integer not null auto_increment, sname varchar(255), primary key (sid))
create table Teachers (tid integer not null auto_increment, tname varchar(255), primary key (tid))
create table teachers_students (sid integer not null, tid integer not null, primary key (sid, tid))

  基於xml的配置:

  其中一方:
<set name="teachers" table="students_teachers">

  <key column="sid"></key>

  <many-to-many class="net.nw.vo.Teachers" column="tid"/>

</set>
View Code

  另外一方:

<set name="students" table="students_teachers">

  <key column="tid"></key>

  <many-to-many class="net.nw.vo.Students" column="sid"/>

</set>
View Code

  具體代碼:

<hibernate-mapping>
  <class name="net.nw.vo.bfk.mtm.Students" table="students">
    <id name="sid" column="sid" type="int">
      <generator class="native"/>
    </id>
    
    <property name="sname" column="sname" type="string"/>
    
    <set name="teachers" table="students_teachers" cascade="all">
      <key column="sid"/>
      <many-to-many class= "net.nw.vo.bfk.mtm.Teachers" column="tid"/>
    </set>
  </class>
</hibernate-mapping>

---------------------------------------------------------------------

<hibernate-mapping>
  <class name="net.nw.vo.bfk.mtm.Teachers" table="teachers">
    <id name="tid" column="tid" type="int">
      <generator class="native"/>
    </id>

    <property name="tname" column="tname" type="string"/>

    <set name="stus" table="students_teachers" cascade="all">
      <key column="tid"/>
      <many-to-many class= "net.nw.vo.bfk.mtm.Students" column="sid"/>
    </set>
  </class>
</hibernate-mapping>
View Code

  

  注意:set元素配置;

  屬性name  指定類的屬性名,table指定多對多關聯關係中間表,cascade 級聯操做屬性:save-update、delete、all、none,通常all就ok,lazy屬性能夠指定是不是懶加載。set的子元素key元素——設定本表在中間表的外鍵名稱。

    inverse屬性設置:

  inverse是Hibernate中雙向關聯關係中的基本概念,在xml配置裏,用來設置關係由哪一方來維護,inverse=true 表示被控方,false表示主控方,在多對一,一對一關聯關係中,Hibernate默認設置多方的inverse=true,即多方爲被控方,一方的inverse=false,即一方爲主控方。在多對多關係中須要咱們本身設置哪一方爲被控方即設置inverse=true。上述例子沒有設置,其實須要手動設置的,對應註解裏的mappedBy屬性。

 

  關聯關係的優缺點

  使用關聯關係,就能夠直接操做內存中的對象,不用每次都查詢數據庫,會提升效率;並且域模型真實反映了客觀世界的關係,可是缺點就是 創建複雜的關聯關係會給程序開發帶來麻煩,當修改一個對象時,會牽連其它的對象,這也是爲何不少人說什麼hibernate很差用……其實就是沒學好,通常人掌握的不紮實,總出錯,後來MyBatis橫空出現,你們就去用它了,比起hibernate來,上手很是簡單,使用原生SQL……本質不是一個真正的ORM架構模式的實現框架。固然Mybatis也有它的優勢和缺點,之後再總結它。
  一句話: 具體要創建對象之間的什麼關聯關係要根據具體的需求。具體使用什麼框架,要聽領導的,哈哈。、
 

  問題小結

  • 注意在多對一/一對多關係裏:多方必須保留一個不帶參數的構造器!
  • 若是沒有設置級聯ALL,那麼須要在保存的時候先保存班級,在保存學生,不然出錯: object references an unsaved transient instance - save the transient instance before flushing: 
  • 多對一時候,多方設置EAGER加載,一對多的時候,一方設置LAZY加載
  • 多對多關聯,多方須要保留一個無參構造器。
相關文章
相關標籤/搜索