Hibernate 学习笔记06 --关联关系的cascade_&_fetch

来源:互联网 发布:sqlserver排序规则 编辑:程序博客网 时间:2024/05/16 01:50

知识点

1、  设定cascade可以设定在持久化时对于关联对象的操作(CUD,R归fetch管)

2、  Cascade仅仅是帮我们省了编程的麻烦而已,不要把它的作用看的太大

a)        cascade的属性指明做什么操作的时候关联对象是绑在一起的

b)       Merge =  save + update

c)        refresh = A里面需要读B改过之后的数据

3、  铁律:双向关系在程序中要设定双向关联

4、  铁律:双向必设mappedBy

5、  fetch

a)        铁律:双向不要两边设置EAGER(会有多余的查询语句发出)

b)       对多方设置fetch的时候要谨慎,结合具体应用,一般用LAZY不用EAGER,特殊情况(多方数量不多的时候可以考虑,提高效率的时候可以考虑)

6、  O/R Mapping编程模型

a)        映射模型

                       i.             jpaannotation

                     ii.             hibernate annotation extension

                   iii.             hibernate xml

                    iv.             jpa.xml

b)       编程接口

                       i.             jpa

                     ii.             hibernate

c)        数据库查询

                       i.             HQL

                     ii.             EJBQL(JPQL)

7、  要想删除或者更新,先做load,除了精确知道ID之外

8、  如果想消除关联关系,先设定关系为null,再删除对应记录,如果不删除记录,该记录就变成垃圾数据。

9、  如果指定@OneToOne的属性fetch为FetchType.LAZY,会延迟对于关联对象的加载,不管是load还是get。

 

一对一关联关系

增(save)

       我们用Country类和Continent类作范例,其中Country与Continent是多对一关系。

       Country类如下,在Country类中设置多对一关联@ManyToOne:

package com.wolex.hibernate.model;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

import javax.persistence.ManyToOne;

 

@Entity

public class Country {

    @Id

    @GeneratedValue

    private int id;

    private Stringname;

    private int population;

    @ManyToOne

    private Continentcontinent;

 

    public int getId() {

        returnid;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public String getName() {

        returnname;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public int getPopulation() {

        returnpopulation;

    }

 

    public void setPopulation(int population) {

        this.population = population;

    }

 

    public Continent getContinent() {

        returncontinent;

    }

 

    public void setContinent(Continent continent) {

        this.continent = continent;

    }

}

       Continent类,不需要添加额外注解:

package com.wolex.hibernate.model;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

 

@Entity

public class Continent {

    @Id

    @GeneratedValue

    private int id;

    private Stringname;

    private float area;

 

    public int getId() {

        returnid;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public String getName() {

        returnname;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public float getArea() {

        returnarea;

    }

 

    public void setArea(float area) {

        this.area = area;

    }

}

       测试类,添加一个Country与Continent对象,并设置他们的关联,再分别存储到数据库中。

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.cfg.Configuration;

import org.hibernate.tool.hbm2ddl.SchemaExport;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class ShemaExportDemo {

    public static void main(String[] args) {

        new SchemaExport(new Configuration().configure()).create(true,true);

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Country country = new Country();

        Continent continent = new Continent();

        country.setContinent(continent);

        continent.setName("Europe");

        country.setName("France");

        country.setPopulation(63860000);

        session.save(continent);

        session.save(country);

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

    }

}

    drop table Continent cascade constraints

 

    drop table Country cascade constraints

 

    drop sequence hibernate_sequence

 

    create table Continent (

        id number(10,0) not null,

        area float not null,

        name varchar2(255 char),

        primary key (id)

    )

 

    create table Country (

        id number(10,0) not null,

        name varchar2(255 char),

        population number(10,0) not null,

        continent_id number(10,0),

        primary key (id)

    )

 

    alter table Country

        add constraint FK_mmhc76t5q1rodo0njifgoee60

        foreign key (continent_id)

        references Continent

 

create sequence hibernate_sequence

2013-7-27 1:24:00 org.hibernate.tool.hbm2ddl.SchemaExport execute

INFO: HHH000227: Running hbm2ddl schema export

Hibernate:

    drop table Continent cascade constraints

Hibernate:

    drop table Country cascade constraints

Hibernate:

    drop sequence hibernate_sequence

Hibernate:

    create table Continent (

        id number(10,0) not null,

        area float not null,

        name varchar2(255 char),

        primary key (id)

    )

Hibernate:

    create table Country (

        id number(10,0) not null,

        name varchar2(255 char),

        population number(10,0) not null,

        continent_id number(10,0),

        primary key (id)

    )

Hibernate:

    alter table Country

        add constraint FK_mmhc76t5q1rodo0njifgoee60

        foreign key (continent_id)

        references Continent

Hibernate:

    create sequence hibernate_sequence

2013-7-27 1:24:00 org.hibernate.tool.hbm2ddl.SchemaExport execute

INFO: HHH000230: Schema export complete

Hibernate:

    select

        hibernate_sequence.nextval

    from

        dual

Hibernate:

    select

        hibernate_sequence.nextval

    from

        dual

Hibernate:

    insert

    into

        Continent

        (area, name, id)

    values

        (?, ?, ?)

Hibernate:

    insert

    into

        Country

        (continent_id, name, population, id)

    values

        (?, ?, ?, ?)

       观察数据库表:

SQL> select * from continent;

        ID       AREA NAME

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

         1          0 Europe

SQL> select * from country;

        ID NAME                 POPULATION CONTINENT_ID

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

         2 France                 63860000           1

       可见,此时成功存储这两个对象到数据库中,并且在country表的continent_id字段中正确插入了continent表的主键值。

       那如果我们在测试类中不执行“session.save(continent);”此句呢?观察结果:

……

country.setName("France");

        country.setPopulation(63860000);

//      session.save(continent);

        session.save(country);

        session.getTransaction().commit();

……

……

Exception in thread "main" org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.wolex.hibernate.model.Continent

……

       此时出错了。意思大概是在存储Country实例化的对象时(即往DB中country表插入数据),涉及到未存储的瞬时实例,请先将Continent实例化对象刷(flushing)到DB中。这里的flushing指的是将内存中的内容(即“脏数据”)刷回数据库,保持一致性。

那是否有方法可以实现在存储country对象时,级联存储与其相关的对象呢?答案是有的。需要用到的是@ManyToOne中的cascade可选元素。而cascade的可设置值为CascadeType[]数组中的元素。

       CascadeType是枚举类型,其说明如下:

javax.persistence 
Enum CascadeType

java.lang.Object

  java.lang.Enum<CascadeType>

      javax.persistence.CascadeType

       其中,枚举常量有如下几个:

No

名字

描述

1

ALL

Cascade all operations

2

DETACH

Cascade detach operation

3

MERGE

Cascade merge operation

4

PERSIST

Cascade persist operation

5

REFRESH

Cascade refresh operation

6

REMOVE

Cascade remove operation

       上面除了ALL以外,都规定只有在执行指定的方法时才会发生级联,如merge()方法,persist()方法等。需要注意的是,这里并没有单独指定save()、update()等方法的级联。而指定为MERGE时是不包括update()方法的;同样,指定为PERSIST时是不包括save()方法的。而要在执行save()update()等方法发生级联,则必须设置为ALL因为ALL表示包括所有操作方法。

       下面在Country类中的添加cascade,让其实现级联存储。修改后的Country类:

package com.wolex.hibernate.model;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

import javax.persistence.ManyToOne;

import javax.persistence.CascadeType;

// 不要导入hibernate的包

//import org.hibernate.annotations.CascadeType;

 

@Entity

public class Country {

    @Id

    @GeneratedValue

    private int id;

    private Stringname;

    private int population;

    @ManyToOne(cascade = (CascadeType.ALL))

    private Continentcontinent;

 

    public int getId() {

        returnid;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public String getName() {

        returnname;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public int getPopulation() {

        returnpopulation;

    }

 

    public void setPopulation(int population) {

        this.population = population;

    }

 

    public Continent getContinent() {

        returncontinent;

    }

 

    public void setContinent(Continent continent) {

        this.continent = continent;

    }

}

       Continent类不变。测试类也不变,如下:

……

country.setName("France");

        country.setPopulation(63860000);

//      session.save(continent);

        session.save(country);

        session.getTransaction().commit();

……

       结果成功,continent对象和country对象都存储到数据库中了。

      

 

 

一对多双向关联关系

       为了实现双向关联,需要在Continent类中添加country属性和@OneToMany注解。上面我们实现了当存储country对象时,级联存储continent对象。如果我们现在要想实现存储continent对象时级联存储country对象,也必须在Continent类中添加cascade,修改后的Continent类:

package com.wolex.hibernate.model;

import java.util.HashSet;

import java.util.Set;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

import javax.persistence.OneToMany;

import javax.persistence.CascadeType;

 

@Entity

public class Continent {

    @Id

    @GeneratedValue

    private int id;

    private Stringname;

    private float area;

    @OneToMany(mappedBy="continent",cascade=(CascadeType.ALL))

    private Set<Country>countries = new HashSet<Country>();

 

    public Set<Country> getCountries() {

        returncountries;

    }

 

    public void setCountries(Set<Country> countries) {

        this.countries = countries;

    }

 

    public int getId() {

        returnid;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public String getName() {

        returnname;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public float getArea() {

        returnarea;

    }

 

    public void setArea(float area) {

        this.area = area;

    }

}

       测试类中,在continent对象中添加对country的导向(我们先不设置Country到Continent的导向,观察结果)。之后,只存储continent对象。

package com.wolex.hibernate.model;

 

import org.hibernate.Session;

import org.hibernate.cfg.Configuration;

import org.hibernate.tool.hbm2ddl.SchemaExport;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class ShemaExportDemo {

    public static void main(String[] args) {

        new SchemaExport(new Configuration().configure()).create(true,true);

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Country country01 = new Country();

        Country country02 = new Country();

        Country country03 = new Country();//用以作对比

        Continent continent = new Continent();

        continent.setName("Europe");

        country01.setName("France");

        country02.setName("England");

//      country01.setContinent(continent);//在一对多的多方设置导向

//      country02.setContinent(continent);

//      country03.setContinent(continent);

        continent.getCountries().add(country01);//在一对多的一方设置导向

        continent.getCountries().add(country02);

        session.save(continent);

//      session.save(country);

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

    }

}

       数据库表:

SQL> select * from country;

        ID NAME                 POPULATION CONTINENT_ID

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

         2 England                       0

         3 France                        0

SQL> select * from continent;

        ID       AREA NAME

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

         1          0 Europe

       可以发现,因为在Continent类中设置了cascade,所以会级联存储与continent对象有关系的Country实例化对象,即country01和country02而不会存储country03。但此时在country表中的continent_id字段中并没有插入值,这是因为在测试类中没有设置Country到Continent的导向。修改后的测试类:

……

        country01.setContinent(continent);//在一对多的多方设置导向

        country02.setContinent(continent);

        country03.setContinent(continent);

……

       数据库表:

SQL> select * from country;

        ID NAME                 POPULATION CONTINENT_ID

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

         2 France                        0           1

         3 England                       0           1

SQL> select * from continent;

        ID       AREA NAME

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

         1          0 Europe

       可以看到,此时在country表中成功插入了continent_id字段的值。但此时依然不会存储country03对象,因为continent对象并没有设置到country03对象的导向,即使country03对象设置了到continent对象的导向。

       小结:一对多关系中,在“多的”那方操作比较方便,只需要设置一方的导向关系即可,如:country.setContinent(continent) 。而如果在“一方”操作的话,则必须设置上两边的导向关系。

 

 

 

 

Fetch

       上面提到cascade只影响CRUD中的“CUD”,而“R”则受另一个可选元素控制——fetch。

       在关联关系的注解(如@ManyToOne、@OneToOne、@OneToMany等)中,都会同时存在cascade和fetch这两个元素。其中在@ManyToOne的fetch的说明如下:

●  public abstract FetchType fetch

(Optional)Whether the association should be lazily loaded or must be eagerly fetched. TheEAGER strategy is a requirement on the persistence provider runtime that theassociated entity must be eagerly fetched. The LAZY strategy is a hint to thepersistence provider runtime.

Default:

javax.persistence.FetchType.EAGER

 

       FetchType是枚举类型,有两个常量:EAGER、LAZY。其中@ManyToOne和@OneToOne的默认值为EAGER,而@ManyToMany和@OneToMany的默认值为LAZY。至于为什么这样,下面有解释。

       Country与Continent的关联关系是多对一,所以在Country类中的注解为@ManyToOne。我们先在country表和continent表中插入数据,在通过查询多对一的“多方(即Country)”来观察是否会级联将“一方(即Continent)”一并查询出来。测试类如下:

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class FetchTest {

    public static void main(String[] args) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Country country = (Country) session.get(Country.class, 2);

        System.out.println("The country's name: " + country.getName());

        System.out.println("The continent's name: "

                + country.getContinent().getName());

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

    }

}

Hibernate:

    select

        country0_.id as id1_1_1_,

        country0_.continent_id as continen4_1_1_,

        country0_.name as name2_1_1_,

        country0_.population as populati3_1_1_,

        continent1_.id as id1_0_0_,

        continent1_.area as area2_0_0_,

        continent1_.name as name3_0_0_

    from

        Country country0_

    left outer join

        Continent continent1_

            on country0_.continent_id=continent1_.id

    where

        country0_.id=?

The country's name: England

The continent's name: Europe

       可见,查询多对一的“多方”时,会同时把“一方”查询出来,但当反过来呢?下面通过查询Continent来对比:

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class FetchTest {

    public static void main(String[] args) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Continent continent = (Continent) session.get(Continent.class, 1);

//      System.out.println("The country's name: " + continent.getCountries());

        System.out.println("The continent's name: " + continent.getName());

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

    }

}

Hibernate:

    select

        continent0_.id as id1_0_0_,

        continent0_.area as area2_0_0_,

        continent0_.name as name3_0_0_

    from

        Continent continent0_

    where

        continent0_.id=?

The continent's name: Europe

       由后台发出的SQL语句可以看到,该查询语句并没有连结查询,即只是查询了continent一个表。此时如果尝试取出country表的数据,则会出错,如下:

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class FetchTest {

    public static void main(String[] args) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Continent continent = (Continent) session.get(Continent.class, 1);

//      System.out.println("The country's name: " + continent.getCountries());

        System.out.println("The continent's name: " + continent.getName());

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

        for(Country c : continent.getCountries()){

            System.out.println(c.getName());

        }

    }

}

Hibernate:

    select

        continent0_.id as id1_0_0_,

        continent0_.area as area2_0_0_,

        continent0_.name as name3_0_0_

    from

        Continent continent0_

    where

        continent0_.id=?

The continent's name: Europe

Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.wolex.hibernate.model.Continent.countries, could not initialize proxy - no Session

……

       可见,由于@OneToMany中的fetch默认为FetchType.LAZY,所以在get()或load() Continent对象后,不会立刻去读取Country。而上面程序中的for循环是在会话关闭后执行,所以程序没法向数据库中读取country表信息了,因此报错:no Session。需要注意的是,如果上面程序中for循环是在会话关闭前执行的,则不会报错。

所以如果想执行get或load后立刻读取continent表和country表的数据,就要设置FetchType为EAGER。如果设置为LAZY(@OneToMany中默认),则只有在需要用到country表数据时,才会在后台发出SELECT语句将country表读取到内存中。如下:

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class FetchTest {

    public static void main(String[] args) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Continent continent = (Continent) session.get(Continent.class, 1);

        System.out.println("The country's name: " +continent.getCountries());

        System.out.println("The continent's name: " + continent.getName());

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

        for(Country c : continent.getCountries()){

            System.out.println(c.getName());

        }

    }

}

Hibernate:

    select

        continent0_.id as id1_0_0_,

        continent0_.area as area2_0_0_,

        continent0_.name as name3_0_0_

    from

        Continent continent0_

    where

        continent0_.id=?

Hibernate:

    select

        countries0_.continent_id as continen4_0_1_,

        countries0_.id as id1_1_1_,

        countries0_.id as id1_1_0_,

        countries0_.continent_id as continen4_1_0_,

        countries0_.name as name2_1_0_,

        countries0_.population as populati3_1_0_

    from

        Country countries0_

    where

        countries0_.continent_id=?

The country's name: [com.wolex.hibernate.model.Country@4d16318b, com.wolex.hibernate.model.Country@6c0ec436]

The continent's name: Europe

England

France

       可见,当执行continent.getCountries()后,后台会发出一条对country表查询的SELECT语句。然后将记录读取到内存中。

修改Continent类,添加FetchType,设置为EAGER,修改的内容highlight显示:

@OneToMany(mappedBy = "continent", cascade = (CascadeType.ALL),fetch = FetchType.EAGER)

       在执行测试类,此时注释掉continent.getCountries():

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class FetchTest {

    public static void main(String[] args) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Continent continent = (Continent) session.get(Continent.class, 1);

//      System.out.println("The country's name: " + continent.getCountries());

        System.out.println("The continent's name: " + continent.getName());

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

        for(Country c : continent.getCountries()){

            System.out.println(c.getName());

        }

    }

}

Hibernate:

    select

        continent0_.id as id1_0_1_,

        continent0_.area as area2_0_1_,

        continent0_.name as name3_0_1_,

        countries1_.continent_id as continen4_0_3_,

        countries1_.id as id1_1_3_,

        countries1_.id as id1_1_0_,

        countries1_.continent_id as continen4_1_0_,

        countries1_.name as name2_1_0_,

        countries1_.population as populati3_1_0_

    from

        Continent continent0_

    left outer join

        Country countries1_

            on continent0_.id=countries1_.continent_id

    where

        continent0_.id=?

The continent's name: Europe

England

France

       但把fetch设置为FetchType.EAGER后,后台在get或者load Continent对象时,都会立刻将country表一并读取出来。

       通过上面两段程序代码,我们大概知道了FetchType.EAGER与FetchType.LAZY的区别。小结一下它们目前的两个区别:

n  当设置为EAGER时,会把双方(country表与continent表)都读取到内存中,即使会话关闭也能取出表记录,如上面的for循环就是在会话结束后执行的。相反,如果Continent类中设置为LAZY, 在会话关闭后再执行for循环则肯定报错。

n  EAGER是立刻把关联方的信息一并读取出来放到内存中,而LAZY是在需要的时候才会去读取关联方的信息。

下面我们验证一下第一点。我们在Continent类中设置LAZY,等待会话关闭后再执行读取Country对象信息,观察是否报错。

       Continent类中的@OneTOMany注解:

@OneToMany(mappedBy = "continent", cascade = (CascadeType.ALL), fetch =FetchType.LAZY)

       Country类中的@ManyToOne注解:

@ManyToOne(cascade = (CascadeType.ALL),fetch = FetchType.EAGER)

       测试类:

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class FetchTest {

    public static void main(String[] args) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Continent continent = (Continent) session.get(Continent.class, 1);

//      System.out.println("The country's name: " + continent.getCountries());

        System.out.println("The continent's name: " + continent.getName());

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

        for(Country c : continent.getCountries()){

            System.out.println(c.getName());

        }

    }

}

Hibernate:

    select

        continent0_.id as id1_0_0_,

        continent0_.area as area2_0_0_,

        continent0_.name as name3_0_0_

    from

        Continent continent0_

    where

        continent0_.id=?

The continent's name: Europe

Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.wolex.hibernate.model.Continent.countries, could not initialize proxy - no Session

       和预想一样,出错了。因为此时内存中并没有country的信息。

       下面继续实验,观察实验结果并尝试解释原因。Country类和Continent类保持上面的fetch设置,测试类如下:

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class FetchTest {

    public static void main(String[] args) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Country country = (Country) session.get(Country.class, 2);

        System.out.println("The country's name: " + country.getName());

        System.out.println("The continent's name: "

                + country.getContinent().getName());

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

    }

}

Hibernate:

    select

        country0_.id as id1_1_1_,

        country0_.continent_id as continen4_1_1_,

        country0_.name as name2_1_1_,

        country0_.population as populati3_1_1_,

        continent1_.id as id1_0_0_,

        continent1_.area as area2_0_0_,

        continent1_.name as name3_0_0_

    from

        Country country0_

    left outer join

        Continent continent1_

            on country0_.continent_id=continent1_.id

    where

        country0_.id=?

The country's name: England

The continent's name: Europe

       结果在后台发出了一条SELECT语句,连结country与continent表。继续实验,将双方都设置为EAGER,执行上面测试类,结果如下:

Hibernate:

    select

        country0_.id as id1_1_1_,

        country0_.continent_id as continen4_1_1_,

        country0_.name as name2_1_1_,

        country0_.population as populati3_1_1_,

        continent1_.id as id1_0_0_,

        continent1_.area as area2_0_0_,

        continent1_.name as name3_0_0_

    from

        Country country0_

    left outer join

        Continent continent1_

            on country0_.continent_id=continent1_.id

    where

        country0_.id=?

Hibernate:

    select

        countries0_.continent_id as continen4_0_1_,

        countries0_.id as id1_1_1_,

        countries0_.id as id1_1_0_,

        countries0_.continent_id as continen4_1_0_,

        countries0_.name as name2_1_0_,

        countries0_.population as populati3_1_0_

    from

        Country countries0_

    where

        countries0_.continent_id=?

The country's name: England

The continent's name: Europe

此时后台发出了两条SELECT语句,为什么呢?这是因为在一对多关系中,当双方都设置为EAGER时,如果此时取出“多方(即Country)”,后台会发出SELECT语句将双方取出,同时因为“一方Continent”也是EAGER,所以又会多发出一条SELECT语句去读取“多方Country”信息。

       小结:一般情况下不会在@ManyToOne和@OneToMany双方都将fetch设置为EAGER,默认情况下@OneToMany为LAZY也就是这个原因。

 

 

 

 

 

更新(update)

       设置Country和Continent的cascade = (CascadeType.ALL),验证当更新一方时,另一方是否会级联更新。测试类如下:

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class UpdateTest {

    public static void main(String[] args) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Country country = (Country) session.get(Country.class, 2);

        country.setName("Korea");

        country.getContinent().setName("Asia");

        session.update(country);

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

    }

}

Hibernate:

    select

        country0_.id as id1_1_1_,

        country0_.continent_id as continen4_1_1_,

        country0_.name as name2_1_1_,

        country0_.population as populati3_1_1_,

        continent1_.id as id1_0_0_,

        continent1_.area as area2_0_0_,

        continent1_.name as name3_0_0_

    from

        Country country0_

    left outer join

        Continent continent1_

            on country0_.continent_id=continent1_.id

    where

        country0_.id=?

Hibernate:

    update

        Continent

    set

        area=?,

        name=?

    where

        id=?

Hibernate:

    update

        Country

    set

        continent_id=?,

        name=?,

        population=?

    where

        id=?

       测试类中我们修改了Country和Continent对象的信息,但只更新Country对象,由于设置了cascade,所以后台发出了两条更新语句。

 

 

 

 

删除

       首先,往数据库插入几条记录,如下:

SQL> select * from country;

        ID NAME                 POPULATION CONTINENT_ID

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

         2 England                       0

         3 France                        0

SQL> select * from continent;

        ID       AREA NAME

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

         1          0 Europe

       现在只删除country表中 ID为2的一条记录。测试类:

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class DeleteTest {

    public static void main(String[] args) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Country country = (Country) session.get(Country.class, 2);

        session.delete(country);

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

    }

}

Hibernate:

    select

        country0_.id as id1_1_1_,

        country0_.continent_id as continen4_1_1_,

        country0_.name as name2_1_1_,

        country0_.population as populati3_1_1_,

        continent1_.id as id1_0_0_,

        continent1_.area as area2_0_0_,

        continent1_.name as name3_0_0_

    from

        Country country0_

    left outer join

        Continent continent1_

            on country0_.continent_id=continent1_.id

    where

        country0_.id=?

Hibernate:

    select

        countries0_.continent_id as continen4_0_1_,

        countries0_.id as id1_1_1_,

        countries0_.id as id1_1_0_,

        countries0_.continent_id as continen4_1_0_,

        countries0_.name as name2_1_0_,

        countries0_.population as populati3_1_0_

    from

        Country countries0_

    where

        countries0_.continent_id=?

Hibernate:

    delete

    from

        Country

    where

        id=?

Hibernate:

    delete

    from

        Country

    where

        id=?

Hibernate:

    delete

    from

        Continent

    where

        id=?

       我们只想删除一条记录,但后天却发出了3条delete语句,为什么呢?先查询数据表:

SQL> select * from country;

no rows selected

SQL> select * from continent;

no rows selected

       可见,两个表的数据全没了!这是由于级联惹的祸。当删除country表中ID为2的记录时,由于级联,会把continent表的关联记录删除掉,又由于删除continent的记录时,要先将与此记录有关联的country表中的记录删除,所以country表中ID为3的记录被删除了。

       那如果在不修改cascade的情况下,能否解决此问题呢?当然可以!有以下两个方法可以解决级联删除的问题。

       方法一:执行delete()方法前,先消除对象之间的关联关系。(执行下面程序前,我们要先往表中插入数据)

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class DeleteTest {

    public static void main(String[] args) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Country country = (Country) session.get(Country.class, 2);

        country.setContinent(null);

        session.delete(country);

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

    }

}

Hibernate:

    select

        country0_.id as id1_1_1_,

        country0_.continent_id as continen4_1_1_,

        country0_.name as name2_1_1_,

        country0_.population as populati3_1_1_,

        continent1_.id as id1_0_0_,

        continent1_.area as area2_0_0_,

        continent1_.name as name3_0_0_

    from

        Country country0_

    left outer join

        Continent continent1_

            on country0_.continent_id=continent1_.id

    where

        country0_.id=?

Hibernate:

    delete

    from

        Country

    where

        id=?

       此时后台只发出一条delete语句了。

       方法二:使用HQL执行删除,这需要再确定ID的情况下。

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class DeleteTest {

    public static void main(String[] args) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        session.createQuery("delete Country where id=3").executeUpdate();

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

    }

}

Hibernate:

    delete

    from

        Country

    where

        id=3

       以后工作中,一般使用HQL的情况比较多,因为HQL来得方便。

       下面我们删除continent表的数据,要避免级联删除,同样需要将关联关系设置为null。(在此之前,先往数据库表中插入数据)

package com.wolex.hibernate.model;

import org.hibernate.Session;

import org.hibernate.tutorial.util.HibernateUtil;

 

public class DeleteTest {

    public static void main(String[] args) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Continent continent = (Continent) session.load(Continent.class, 1);

        for(Country c : continent.getCountries()){

            c.setContinent(null);

        }

        continent.setCountries(null);// 切记也要设置ContinentCountry的导航

        session.delete(continent);

        session.getTransaction().commit();

        HibernateUtil.getSessionFactory().close();

    }

}

Hibernate:

    select

        continent0_.id as id1_0_0_,

        continent0_.area as area2_0_0_,

        continent0_.name as name3_0_0_

    from

        Continent continent0_

    where

        continent0_.id=?

Hibernate:

    select

        countries0_.continent_id as continen4_0_1_,

        countries0_.id as id1_1_1_,

        countries0_.id as id1_1_0_,

        countries0_.continent_id as continen4_1_0_,

        countries0_.name as name2_1_0_,

        countries0_.population as populati3_1_0_

    from

        Country countries0_

    where

        countries0_.continent_id=?

Hibernate:

    update

        Country

    set

        continent_id=?,

        name=?,

        population=?

    where

        id=?

Hibernate:

    update

        Country

    set

        continent_id=?,

        name=?,

        population=?

    where

        id=?

Hibernate:

    delete

    from

        Continent

    where

        id=?

       后台先发出update语句将country表中的有关记录的外键修改为null,之后再删除continent表中的指定行记录。

       这样做要保证在country表中的continent_id字段允许为空。不过一般来说,如果删除了一对多关系中的“一方”,留着对应的“多方”也没什么用,属于垃圾记录,例如forum上的版块与帖子为一对多的关系,如删除一个版块,则留着哪些帖子也没用。所以必须谨慎此操作。

 

0 0
原创粉丝点击