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

本文详细阐述了数据库中一对一、一对多关联关系的优化策略,包括级联操作、fetch属性使用、更新与删除操作注意事项,以及如何避免级联删除带来的问题。重点介绍了cascade和fetch属性的作用,以及在一对多关联关系中的应用。通过示例代码,展示了如何通过设置cascade为ALL和fetch为EAGER来优化数据操作效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

知识点

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 String name;

    private int population;

    @ManyToOne

    private Continent continent;

 

    public int getId() {

        return id;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public int getPopulation() {

        return population;

    }

 

    public void setPopulation(int population) {

        this.population = population;

    }

 

    public Continent getContinent() {

        return continent;

    }

 

    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 String name;

    private float area;

 

    public int getId() {

        return id;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public float getArea() {

        return area;

    }

 

    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 String name;

    private int population;

    @ManyToOne(cascade = (CascadeType.ALL))

    private Continent continent;

 

    public int getId() {

        return id;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public int getPopulation() {

        return population;

    }

 

    public void setPopulation(int population) {

        this.population = population;

    }

 

    public Continent getContinent() {

        return continent;

    }

 

    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 String name;

    private float area;

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

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

 

    public Set<Country> getCountries() {

        return countries;

    }

 

    public void setCountries(Set<Country> countries) {

        this.countries = countries;

    }

 

    public int getId() {

        return id;

    }

 

    public void setId(int id) {

        this.id = id;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public float getArea() {

        return area;

    }

 

    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上的版块与帖子为一对多的关系,如删除一个版块,则留着哪些帖子也没用。所以必须谨慎此操作。

 

&lt;think&gt;好的,用户现在问的是关于如何使用`set_property CASCADE_ORDER &quot;CASCADE&quot; [get_cells -hier -filter {PRIMITIVE_TYPE =~ BMEM.*}]`这条Tcl命令的。之前用户遇到了在Vivado 2020.2中使用旧指令报错的问题,现在他们可能已经按照之前的建议尝试了新的方法,但需要更详细的指导。 首先,我需要确认用户的需求。他们可能正在尝试手动配置BRAM的级联,但不太清楚具体的步骤或者命令的细节。用户可能对Tcl命令的各个部分不太熟悉,比如`get_cells`的过滤条件,或者如何验证设置是否生效。 接下来,我要回忆这条命令的结构。`CASCADE_ORDER`属性需要应用到具体的BRAM实例上,而`get_cells -hier -filter {PRIMITIVE_TYPE =~ BMEM.*}`是用来获取设计中所有BRAM原语的。这里需要确认用户是否正确使用了过滤条件,是否了解BMEM的含义,以及是否需要针对特定模块进行调整。 用户可能的问题点包括:命令是否应该放在哪个阶段执行(综合前还是实现阶段),如何确认命令生效,或者是否有例外情况需要处理。例如,用户可能遇到某些BRAM无法级联的情况,或者级联后时序问题,这时候需要提供进一步的解决方案。 另外,考虑到用户之前遇到的版本兼容性问题,需要再次强调Vivado 2020.2之后的版本支持`CASCADE_ORDER`属性,而旧版本可能不支持。用户可能需要确认他们的Vivado版本是否正确,或者是否有其他替代方案。 还需要检查用户是否知道如何生成报告来验证级联效果,比如使用`report_bram_cascading`命令,或者通过GUI查看布局情况。这些验证步骤对于确保配置正确非常重要。 可能的误区包括用户误用了过滤条件,或者没有正确指定BRAM实例。例如,`BMEM.*`是否覆盖了所有类型的BRAM,或者是否需要更具体的过滤条件。需要解释清楚过滤条件的写法,以及如何根据设计需求调整。 此外,用户可能需要了解级联对设计的影响,比如资源利用率和时序的变化。应建议他们在应用级联后做全面的时序分析,确保没有负面影响。 总结来说,用户需要详细的步骤说明、命令各部分的解释、验证方法以及常见问题的解决方法。确保回答结构清晰,逐步引导用户正确应用该命令,并验证结果,同时预防可能的问题。&lt;/think&gt;在 Vivado 中,使用 `set_property CASCADE_ORDER &quot;CASCADE&quot;` 命令可以手动控制 BRAM(Block RAM)的级联行为。以下是具体用法和注意事项: --- ### **命令功能** - **作用**:强制将多个 BRAM 原语级联成连续的逻辑块,优化资源利用和时序。 - **适用场景**: - 需要手动控制 BRAM 布局(例如:跨时钟域隔离) - 自动级联未按预期触发(如混合宽度 BRAM) - 级联路径需要固定物理位置(例如:避免跨 SLR 分割) --- ### **命令语法详解** ```tcl set_property CASCADE_ORDER &quot;CASCADE&quot; [get_cells -hier -filter {PRIMITIVE_TYPE =~ BMEM.*}] ``` 1. **`CASCADE_ORDER`** - 可选值:`CASCADE`, `FIRST`, `MIDDLE`, `LAST`, `NONE` - `CASCADE`:强制该 BRAM 参与级联链(自动分配位置) - `FIRST/MIDDLE/LAST`:手动指定级联顺序(需物理位置配合) 2. **`get_cells -hier`** - `-hier`:递归搜索所有层级模块 - `-filter {PRIMITIVE_TYPE =~ BMEM.*}`:匹配所有 BRAM 原语(包括 `BMEM.RAMB36E2`, `BMEM.RAMB18E2`) --- ### **分步操作指南** #### **Step 1:定位目标 BRAM** - **通过 GUI 查看实例名** 打开综合或实现后的设计 &rarr; 在 `Netlist` 窗口展开层次结构 &rarr; 右键 BRAM 实例 &rarr; `Copy Full Name` ![示例:BRAM 实例名通常为 u_bram_0/u_ram_block](https://example.com/path/to/bram_instance.png) - **Tcl 过滤特定 BRAM** 若需选择部分 BRAM,可添加更多过滤条件: ```tcl # 例:筛选名称包含 &quot;video_buf&quot; 的 BRAM get_cells -hier -filter {PRIMITIVE_TYPE =~ BMEM.* &amp;&amp; NAME =~ *video_buf*} ``` #### **Step 2:设置级联属性** - **全局级联(所有 BRAM)** ```tcl set_property CASCADE_ORDER &quot;CASCADE&quot; [get_cells -hier -filter {PRIMITIVE_TYPE =~ BMEM.*}] ``` - **指定级联顺序(需物理布局配合)** ```tcl # 设置级联链起点 set_property CASCADE_ORDER &quot;FIRST&quot; [get_cells bram_inst0] # 设置中间节点 set_property CASCADE_ORDER &quot;MIDDLE&quot; [get_cells bram_inst1] # 设置终点 set_property CASCADE_ORDER &quot;LAST&quot; [get_cells bram_inst2] ``` #### **Step 3:验证设置** - **方法 1:生成级联报告** ```tcl report_bram_cascading -file cascade_report.txt ``` 检查输出报告中的 `Cascade Order` 列是否显示 `CASCADE`: ![报告示例:显示级联状态和物理位置](https://example.com/path/to/cascade_report.png) - **方法 2:查看物理布局** ```tcl start_gui # 如果处于非 GUI 模式 select_objects [get_cells -hier -filter {CASCADE_ORDER == &quot;CASCADE&quot;}] ``` - 级联 BRAM 的物理位置(Site)应连续(例如:`RAMB36_X0Y5 &rarr; RAMB36_X0Y6`) --- ### **关键注意事项** 1. **执行时机** - **综合前设置**:需在 `synth_design` 之前执行,否则可能被综合器覆盖 - **实现阶段覆盖**:若在布局后修改,需执行 `reset_property CASCADE_ORDER` 并重新布局 2. **级联物理限制** - 级联 BRAM 必须在同一 SLR(Super Logic Region)内 - 级联链长度建议 &le; 4(超长链可能导致时序恶化) 3. **混合宽度级联** 若 BRAM 数据宽度不同,需额外启用兼容模式: ```tcl set_property ALLOW_MIXED_WIDTH_CASCADING 1 [current_design] ``` --- ### **调试案例** #### **问题:设置后级联未生效** - **可能原因**: - BRAM 被其他约束覆盖(如 `DONT_TOUCH`) - 物理位置冲突(如跨越时钟区域边界) - **排查步骤**: ```tcl # 检查属性是否实际写入 report_property [get_cells bram_inst0] -file prop_check.txt # 查看日志中是否有冲突警告 grep -i &quot;cascade&quot; vivado.log ``` #### **问题:级联后时序违例** - **优化方案**: ```tcl # 对级联路径放宽约束 set_max_delay -from [get_pins bram_inst0/DOUT*] -to [get_pins bram_inst1/DIN*] 6.0 # 降低级联链长度 set_property CASCADE_ORDER &quot;NONE&quot; [get_cells bram_inst3] ``` --- ### **扩展应用** #### **与 `LOC` 约束结合使用** 若需固定级联 BRAM 的物理位置: ```tcl set_property LOC RAMB36_X0Y10 [get_cells bram_inst0] set_property LOC RAMB36_X0Y11 [get_cells bram_inst1] set_property CASCADE_ORDER &quot;CASCADE&quot; [get_cells {bram_inst0 bram_inst1}] ``` #### **动态修改级联策略** 在实现过程中根据时序结果调整: ```tcl # 初次布局后评估时序 if {[get_timing_paths -slack_lesser_than 0] != &quot;&quot;} { reset_property CASCADE_ORDER [get_cells -hier -filter {PRIMITIVE_TYPE =~ BMEM.*}] set_property CASCADE_ORDER &quot;NONE&quot; [get_cells bram_inst3] # 断开问题链 place_design -post_route_opt } ``` --- 通过以上方法,可以精确控制 BRAM 级联行为,建议结合设计需求选择全局配置或手动指定顺序。实际使用前请通过小规模测试验证策略有效性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值