hibernate QBC和QBE精讲与案列分析(下)

 1.使用HQL进行连接查询

  HQL中支持连接查询分别使用inner join,left outer join,right outer join,其中,左外连接和右外连接可以简写为left join和right join。

  另外,HQL还支持抓取连接查询的方式,即使用fetch关键字。实际上,是否使用fetch执行的SQL语句基本上是一样的,它们只是返回不同。

  下面分别是内连接、左连接、右连接查询的例子和它们对应的SQL语句。

  下面使用内连接来关联carorder表和salesman表,即连接的是一个关联实体。

  from CarOrder c inner join c.salesman

  它对应的SQL语句如下:

  select carorder0_.cid as cid0_0_, salesman1_.sid as sid1_1_, carorder0_.carname as carname0_0_, carorder0_.salesId as salesId0_0_, salesman1_.salesName as salesName1_1_ from carorder1 carorder0_ inner join salesman salesman1_ on carorder0_.salesId=salesman1_.sid

  下面使用左连接来关联carorder表和salesman表,连接的是一个集合中的元素:

  from Salesman c left join c.carorders

  它对应的SQL语句如下:

扫描二维码关注公众号,回复: 3640001 查看本文章

  select salesman0_.sid as sid1_0_, carorders1_.cid as cid0_1_, salesman0_.salesName as salesName1_0_,

  carorders1_.carname as carname0_1_, carorders1_.salesId as salesId0_1_ from salesman salesman0_ 

  left outer join carorder1 carorders1_ on salesman0_.sid=carorders1_.salesId

  说明:

  虽然Salesman类中的集合carOrders属性中的字母o用的是大写,但是和映射文件要求一样,这里也要求使用小写字母,否则Hibernate找不到这个属性。

  还可以使用where子句为连接查询设置过滤条件和使用select语句进行属性投影。

  select c.carname from CarOrder c right join c.salesman where c.cid>1

  对应的SQL语句如下:

  select carorder0_.carname as col_0_0_ from carorder1 carorder0_ right outer join salesman salesman1_ on carorder0_.salesId=salesman1_.sid where carorder0_.cid>1

  另外,如果对象间还有别的关联关系,可以把内连接、左连接、右连接多次结合进行查询。例如,

  from Cat as cat 

      inner join fetch cat.mate

      left join fetch cat.kittens

  2.使用Criteria进行连接查询

  使用Criteria接口的createCriteria()函数,可以导航到检索类关联实体,进行关联查询,这个函数返回的是一个新的Criteria实例。

  例如,下面的例子,检索名字以F开头的salesman对象关联的名字以c开头的carorder对象。

  Criteria crit1 = session.createCriteria(Salesman.class);

  crit1.add( Restrictions.like("salesname", "F%") );

  Criteria crit2 =crit1.createCriteria("carorders");//

  crit2.add( Restrictions.like("carname", "c%") );

  List results =crit2.list();

  在上面的例子中,通过crit1.createCriteria("carorders")关联到Salesman的carorders集合属性,返回的是一个指向CarOrder对象的新的Criteria实例,然后在这个实例上设置对CarOrder对象检索的条件。它的返回是Salesman对象,要得到关联的carorder对象,还得通过carorders集合来取得。

  还可以使用Criteria接口的createAlias()函数为对象的实体属性设置别名来实现关联,而且,这样可以实现的关联查询功能往往更强大。createAlias()并不会返回新的实例。

  例如,下面的例子与上面使用createCriteria()函数实现的关联查询的结果和意思都是一样的,检索名字以F开头的salesman对象关联的名字以c开头的carorder对象。

  Criteria crit = session.createCriteria(Salesman.class);

  crit.add( Restrictions.like("salesname", "F%") );

  crit.createAlias("carorders", "carorder");

  crit.add( Restrictions.like("carorder.carname", "c%") );

  List results2 =crit.list();

  在上面的代码中,createAlias()并不会创建新的Criteria实例,它还是原来的指向CarOrder对象的Criteria实例,所以当检索名字以c开头的carorder对象时,需要使用carorder.carname进行导向。createAlias("carorders", "carorder");的意思是把carorders集合属性的每个元素设置别名为carorder,所以这时的carorder对象指的是每一个CarOrder对象,这样就设置了salesman对象与carorder对象的关联关系

  由于使用了别名,所以,可以进行关联对象之间任意属性的匹配,例如下面的例子,检索carorder中车的名字和salesman中售货员名字相同的对象。

  List results = session.createCriteria(Salesman.class)

  .createAlias("carorders", "co")

  .add( Restrictions.eqProperty("co.carname", "salesname") )

  .list()

  如果对象中存在多个关联的实体对象,可以多次使用createAlias()为它们设置别名,然后也可以进行他们之间属性或条件的设置。如果关联的对象还有其他关联的对象,也可以继续使用createAlias()来设置别名,只是取关联实体时用“.”来导航。例如,

  List results3 = session.createCriteria(Salesman.class)

  .createAlias("carorders", "co")

  .createAlias("co.salesman", "cs")

  .add( Restrictions.eqProperty("cs.salesname", "salesname") )

  .list();

  Criteria查询还支持使用setFetchMode().设置抓取模式,进行关联抓取,具体在下面进行介绍。

8.2.3  使用fetch和不使用fetch的区别

  HQL支持内连接、左外连接和右外连接,HQL进行连接查询时还支持一种叫做抓取连接查询的方式,即使用fetch关键字。下面以内连接为例子,介绍使用fetch或不使用fetch连接查询的区别。

  假设有两个POJO类Person和Basiccar,并且Person持久化类中通过basiccar属性关联到Basiccar对象。

  执行下面的HQL语句,不使用fetch关键字:

  from Person p inner join p.basiccar

  它执行的SQL语句相当于:

  select

    person0_.id as id7_0_,

    basiccar1_.id as id6_1_,

    person0_.carId as carId7_0_,

    person0_.salesname as salesname7_0_,

    basiccar1_.name as name6_1_,

    basiccar1_.factory as factory6_1_,

    basiccar1_.date as date6_1_ 

   from

    test.person person0_ 

   inner join

    test.basiccar basiccar1_ 

     on person0_.carId=basiccar1_.id

  返回的list中存放的对象是存在关联关系的Person,Basiccar对象组。即list的每个元素是Object[],其中Object[0]是Person对象,Object[1]是Basiccar对象。

  执行下面的HQL语句,使用fetch关键字:

  from Person p inner join fetch p.basiccar

  它执行的SQL语句相当于:

  select

    person0_.id as id7_0_,

    basiccar1_.id as id6_1_,

    person0_.carId as carId7_0_,

    person0_.salesname as salesname7_0_,

    basiccar1_.name as name6_1_,

    basiccar1_.factory as factory6_1_,

    basiccar1_.date as date6_1_ 

   from

    test.person person0_ 

   inner join

    test.basiccar basiccar1_ 

     on person0_.carId=basiccar1_.id

  可见它们都使用内连接来检索关联对象,SQL语句没有太大差异,但是与不使用fetch相比,这个HQL语句返回的list存放的对象不同。返回的list中存放的对象是存在的主类Person对象,而不是以对象组形式返回关联的两个类对象。如果需要得到关联的Basiccar对象,需要调用getBasiccar()方法。

8.3  检 索 策 略

  Hibernate提供多种检索策略用来设置对象和集合检索时,对象的载入策略。这个载入策略主要是设置两个问题,即什么时候载入,如何载入。

8.3.1  什么时候载入

  关于设置什么时候载入问题,可以分为类级别的和关联级别的。

  1.类级别

  类级别是关于检索的持久化类什么时候被载入的策略。Hibernate提供的类级别载入策略包括以下几种。

● 立即检索(Immediate fetching):当检索一个类时,立刻把类的所有属性都从数据库检索出来,然后放到session缓存中。

● 代理检索(Proxy fetching):对类对象进行延迟加载。这时加载的类对象其实是只有一个主键属性的代理对象。使用这个策略,对于一个类对象,只有在访问它的非主键属性时,才把真正的对象载入,即才初始化其他对象属性。

● 非代理检索("No-proxy fetching):与代理检索策略一样,对类进行延迟加载。不同的是,访问对象的任何属性,包括主键属性,都会载入真正的类对象。这种方法需要在编译期间进行字节码增强操作,因此很少用到。

● 属性延迟检索(Lazy attribute fetching):可以设置具体某个属性为lazy,即延迟加载。只有在具体地使用get***(),访问到这个属性时,这个属性值才被加载进来。这种方法需要在编译期间进行字节码增强操作,因此很少用到。

  上面的4个策略是类级别的,针对什么时候载入类对象的设置。最后两种策略需要编译期间进行字节码增强操作,而且是没有必要的,因为"No-proxy" fetching对用户来说是透明的,和代理检索没有区别;而Lazy attribute fetching,只会使得频繁访问数据库,而且一般多增加一个属性也不会耗费多少缓存,除非是特别大的属性时可能会用到。另外,如果只是需要对象的某些属性,更合理的方法应该是使用select语句或者criteria的property()函数进行列的投影。

  将在8.3.4节进一步阐述有关立即检索和代理检索的运用。

  2.关联级别

  关联级别是关于检索的主类的关联实体或者关联集合什么时候载入的策略,它可以分成以下几种。

● 延迟集合检索(Lazy collection fetching):假设检索的对象中有集合属性,例如一对多的关联,那么只有在调用sets这个集合属性时,才会把所有符合要求的关联对象载入。

● 进一步延迟集合检索("Extra-lazy" collection fetching):它比延迟集合检索更加延迟。Sets集合属性中的每一个对象,都只有在具体访问到这个对象时才把这个关联对象载入,而不是像延迟集合检索一样在访问集合时即把所有关联对象载入。

  上面的两个策略是关联级别的,针对的是什么时候载入关联集合对象。

8.3.2  如何检索

  如何检索策略是针对存在关联关系的对象的检索方式。

  一般情况下,对数据库的关联记录进行检索时,有两种方法进行关联对象检索。例如,检索名字为aaa的salesman对象关联的carorder对象,可以先检索合适的salesman对象,再检索合适的carorder对象。

  Select * from salesman where salesname='aaa'

  假设检索出来的salesman对象有两个,其主键分别为1和2,那么下一步执行:

  Select * from carorder where salesId=1;

  Select * from carorder where salesId=2;

则需要通过3条SQL语句来实现。

  另一种选择是,也可以使用连接查询,这样就只要一条SQL语句即可。例如,上面的操作可以使用左连接。代码如下:

  select * from salesman ss left outer join carorder co on ss.sid=co.salesId where ss. salesname='aaa'

  Hibernate对于如何加载关联对象,即如何抓取关联对象提供的4种策略,分别是:

● 连接检索(join fetching),即在检索关联对象时,采用outer join的select来进行关联查询,如上面提到的第二种方法。不管进行关联对象的立即加载时可以选择这种方式,这样只要一条SQL语句就可以把主对象和关联对象同时检索进缓存。

● 查询检索(Select fetching),即在检索关联对象时,先检索主对象。根据主对象连接关联对象的外键来作为检索关联对象的查询条件,然后检索关联对象,如上面提到的第一种方法。所以不管立即加载还是延迟加载都可以使用这种方法。

● 子查询检索(Subselect fetching),另外发送一条select语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合。除非显式地指定lazy="false" 禁止延迟抓取(lazy fetching),否则只有在真正访问关联关系的时候,才会执行第二条select语句。与上面第一种select抓取方式不同的是,select fetching只会检索载入实体的关联集合;而这里是检索查询到的所有实体的关联集合。具体在8.3.5节的集合的fetch属性中介绍。

● 批量检索(Batch fetching),对查询抓取的优化方案, 通过指定一个主键或外键列表,Hibernate使用单条SELECT语句获取一批对象实例或集合。将在8.3.6节做介绍。

  下面将对各种主要的检索策略进行介绍。

8.3.3  类级别的延迟加载

  对于类级别的延迟加载策略,也就是代理检索策略,即在载入持久化类对象时先不初始化类的属性,只是生成一个代理类,在必要时再载入对象属性初始化类。延迟加载只会在调用load函数,载入对象时有用。如果使用get函数,或者使用Query.list()执行查询,都会立即加载相应的类对象。

  设置类的延迟加载[即只有在调用对象的(除主键外)属性时,才执行select语句,从数据库中载入对象初始化对象的属性]方法是,在映射文件的类中增加lazy属性,它有两个值,分别是true和false,如果设置lazy="true"的属性,或者不设置这个lazy属性,因为hibernate默认的类就是延迟加载。如下所示:

  

    

    

    

  …省略了其他属性的映射内容

  

这个设置,当执行下面的代码:

  Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));

其实hibernate并不会执行select语句从数据库中读取数据。这时返回的salesman实例,其实是一个id为1,其他属性都为空的代理类。这样,可以节约内存,在必要的时候才载入对象。

  如果,访问这个salesman对象的属性,那么就会触发hibernate去读取数据库。例如,

  ss.getSalesname();

那么hibernate会执行下面的sql语句,把其他属性也载入:

  select salesman0_.sid as sid1_0_, salesman0_.salesName as salesName1_0_ from salesman salesman0_ where salesman0_.sid=1

  需要说明的是,如果是取主键,不会触发读取对象的操作。例如,

    ss.getSid();

它仍是从代理类返回主键值。

  所以,如果load检索的对象不存在,那么,在load()函数执行时是不会发现的,只有在get***()访问对象的某个非主键属性时才抛出对象不存在的异常。

  另外,对象只能在session范围内被加载,如果session被关闭了,这时再返回对象,就会抛出异常。例如下面的代码:

  Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));

  ss.getSid();

  session.close();

  ss.getSalesname();

由于在session关闭前没有初始化对象,所以这时的salesman还是代理类,则调用ss.getSalesname()时无法在session范围内加载,从而抛出异常:could not initialize proxy - no Session。

  如果需要显示地把对象初始化,除了调用get方法访问属性外,还可以调用hibernate提供的静态方法initialize()来初始化对象。例如,

  Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));

  Hibernate.initialize(ss);

  session.close();

这样也会触发select语句载入对象。

  如果不设置延迟加载策略,即映射文件中设置lazy="false",那么在调用load函数时,就会立即执行select语句,初始化对象属性来载入对象。

8.3.4  关联实体的载入策略

  下面来看看,对于存在关联关系的实体,它又有哪些可选策略来载入关联类对象。

  下面介绍有关的持久化类和映射文件。

  CarOrder对象通过属性salesman关联到Salesman实体。其中CarOrder对象代码如下所示:

  public class CarOrder  implements java.io.Serializable {

  

   private long cid; 

  

   private String carName;

  

   private Salesman salesman;

  

  //省略了构造函数和其他getter/setter方法

其对应的映射文件的主要部分如下所示:

  

    

    

    

    

    

     

      column="salesId" 

      class="basicCar.bean.Salesman" 

      cascade="all"

      not-null="false"

      lazy="proxy"

      fetch="select">

     

    

  

  这里,对于在载入与carorder对象关联的salesman对象时,采用怎样的载入策略,是由中的lazy和fetch属性,以及salesman映射文件类级别的lazy属性决定的。

  1.中的lazy属性

  中的Lazy属性决定关联实体什么时候载入,它有3个值可以设置,分别是proxy、no-proxy、false。

● 如果设置proxy,那么表示对关联实体采取延迟加载策略,也就是说,在Carorder对象被载入的同时,只是载入salesman对象的代理类,所以这时的salesman对象除了sid,其他属性都是空的。只有在调用salesman对象的非主键属性的getter方法时,这个对象才被载入。

● 如果设置no-proxy指定此属性,应该在实例变量第一次被访问时延迟抓取(fetche lazily)(需要运行时字节码的增强)。这个设置不常用。本章不做介绍。

● 如果设置false,那么表示对关联实体采取立即检索策略,也就是说,在Carorder对象被载入的同时,把salesman对象也载入。

  但是,是否延迟载入,它同时也会参考关联对象自身是否延迟载入的设置,也就是说,salesman.hbm.xml的class元素的lazy属性为true还是false也会影响延迟载入。中的Lazy属性设置,结合关联类的lazy属性设置,载入情况如表8-3所示。

表8-3  类级别的载入策略

中的Lazy属性

关联类的lazy属性

关联类什么时候被载入

false

true

立即载入

No-proxy/proxy

true

延迟载入

No-proxy/proxy

false

立即载入

false

false

立即载入

默认(proxy)

默认(true)

延迟载入

  说明:

  这里的延迟或立即载入,是指当主类被载入时,何时载入关联类对象。立即载入,即主类被载入时就把关联类对象载入;延迟载入,即主类被载入时不把关联类载入,直到通过这个实体属性,访问这个关联类的某个非主键属性时才把关联类载入。至于主类何时载入,主要根据主类的类级别的lazy属性设置为true还是false。

  在本例中,如果在中设置lazy="false",也就是对关联的salesman对象进行立即检索。那么在carorder对象被载入时,salesman对象也被载入。例如,如果在carorder.hbm.xml中carorder的类级别检索策略设置为立即检索,也就是

  

那么执行下面的代码:

  CarOrder co=(CarOrder) session.load(CarOrder.class, new Long(1));

会把carorder对象和关联的salesman对象都载入内存。

  如果在中设置lazy="false",也就是对关联的salesman对象进行立即检索,然后在carorder.hbm.xml中,carorder的类级别检索策略设置为延迟检索,也就是:

  

那么会在检索carorder对象的任何非主键属性(包括carName和salesman属性)时把关联的salesman对象都载入内存。例如,执行下面代码:

  CarOrder co=(CarOrder) session.load(CarOrder.class, new Long(1));

  co.getCarname();

这样把carorder对象载入的同时,把关联的salesman对象也载入内存。

  上面演示的例子都是对关联对象执行立即检索。

  如果在carorder的映射文件中把元素设置如下:

  …

    

      column="salesId" 

      class="basicCar.bean.Salesman" 

      cascade="all"

      not-null="false"

      lazy="proxy"

      fetch="select">

     

  …

然后salesman中的类级别延迟属性设置为true,即如下:

    

  …

那么,会对关联类salesman进行延迟加载,即以下的代码只会载入carorder对象,但是不会载入与之关联的salesman对象:

             CarOrder co=(CarOrder) session.load(CarOrder.class, new Long(1));

    co.getCid();

    co.getCarname();

    Salesman salesman= co.getSalesman();

  但是,如果访问salesman对象的某个非主键属性时,就会使用select语句把salesman对象载入内存。例如,下面的语句:

              ss.getSalesname();

   ss.getCarorders();

都可以,即除了getSid()外,其他不管集合还是普通值属性都会初始化salesman对象。

  2.中的fetch属性

  中的fetch属性有两个值可以选择,即join和select。join代表使用连接查询检索对象。select则代表另外发送select 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联对象。默认是使用select值。

  使用join会在检索主类对象时,使用连接查询,把关联对象也载入,这样就需要一条sql语句就可以检索主对象和其关联对象了。例如,下面carorder对应的映射文件段落,设置关联类是使用代理进行延迟加载,并且加载方式是select:

  …

  

      column="salesId" 

      class="basicCar.bean.Salesman" 

      cascade="all"

      not-null="false"

      lazy="false"

      fetch="join">

     

  …

那么执行下面的代码时:

  CarOrder co=(CarOrder) session.load(CarOrder.class, new Long(1));

  co.getCarname();

会使用外连接把carorder对象和关联的salesman对象都载入,相当于执行了下面的SQL语句;

  select c.cid, c.carname, c.salesId, s.sid, s.salesName from carorder1 c left outer join salesman s on c.salesId=s.sid where c.cid=1

  由于使用连接查询把关联对象同时载入,所以对于延迟检索设置join抓取模式是没有意义的。例如上面设置lazy="proxy",还是使用连接查询把关联对象也同时载入。

  使用select会先检索主类对象。至于何时检索与符合条件的类关联的关联类,就要看关联类设置的是延迟加载还是立即加载策略了。

  例如,下面的carorder对应的映射文件段落如下,设置关联类是使用代理进行延迟加载,并且加载方式是select:

  …

  

      column="salesId" 

      class="basicCar.bean.Salesman" 

      cascade="all"

      not-null="false"

      lazy="proxy"

      fetch="select">

     

  …

那么执行下面的代码:

  CarOrder co=(CarOrder) session.load(CarOrder.class, new Long(1));

  co.getCarname();

相当于执行了select * from carorder where cid=1把carorder对象载入。假设这个carorder对象中的salesId=13。

  然后在访问关联的salesman对象的非主键属性时才使用select语句载入

  Salesman ss= co.getSalesman();

  ss.getSalesname();

相当于执行了select * from salesman s where s.sid=13把关联对象载入。如果对关联对象设置的是立即加载策略,那么在载入carorder对象时也执行这条SQL语句载入salesman。

  关联的实体对象,其fetch和lazy值的选择和设置意义与上面描述的一样。

8.3.5  关联集合的载入策略

  Salesman对象从通过集合属性carOrders关联到CarOrder实体集合。其中Salesman对象代码如下所示:

  public class Salesman implements java.io.Serializable{

   private long sid; 

  

   private String salesName;

  

   private Set carOrders = new HashSet();

  //省略了构造函数和其他getter/setter方法

  其对应的映射文件主要部分如下所示:

  

    

    

    

    

    

    

    

    

    

    

  

  与映射java集合相关的元素包括、、和。在这些集合元素中与载入策略有关的属性有set和lazy。这里,对于在载入与salesman对象关联的集合carorder对象时,采用什么载入策略,是由中的lazy和fetch属性决定的。

  1.集合的lazy属性

  集合元素的lazy属性决定集合属性什么时候载入,它有3个值可以选择,即true、extra和false。

● 如果设置true,表示延迟集合检索(Lazy collection fetching)。即只有在调用sets这个集合属性时,才会把所有符合要求的关联对象载入。在这个例子中,也就是说,在Saleman对象被载入的同时,只是载入carorders这个集合属性的集合代理类实例,真正的集合中的每一个关联的carorder实例并没有被载入。只有在应用程序第一次访问这个实例时,才初始化这个集合。

● 如果设置extra,表示进一步延迟集合检索"Extra-lazy" collection fetching。即Sets集合属性中的每一个对象,都只有在具体访问到这个对象时才把这个关联对象载入。例如本例子中,在访问集合时,不会把所有carorder实例都载入,只有在具体访问某个关联carorder时才把它载入。

● 如果设置false,表示对关联实体采取立即检索策略,也就是说,在salesman对象被载入的同时,把所有关联的carorder对象也载入,初始化集合carorders。

  在本例中,如果在元素中设置lazy="false",也就是对关联的carorders集合进行立即检索。那么在salesman对象被载入时,carorders集合也被载入。例如,如果在salesman.hbm.xml中,salesman的类级别检索策略设置为延迟检索,也就是:

  

那么执行下面的代码,即load这个Salesman对象访问它的非主键属性时,会把salesman对象载入:

  Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));

  ss.getSalesname();

这时,如果salesman.hbm.xml的元素中设置lazy="false",并且fetch设置为fetch="select"(使用select方式来检索相关类,语义和的fetch="select"相同),那么上面的代码也会载入关联的carorder对象初始化carorders集合,相当于执行下面的sql语句:

  select * from salesman s where s.sid=1

  select * from carorder1 c where c.salesId=1

这样把salesman对象载入的同时,把关联的carorder对象也都载入内存初始化carorders集合。

  上面演示的例子都是对关联对象执行立即检索。

  如果在salesman的映射文件中把元素设置如下,即lazy="true",对集合中进行延迟载入:

  …

  

  

  

  

  …

那么,会对关联类salesman进行延迟加载,即以下的代码只会载入carorder对象,但是不会载入与之关联的carorder对象初始化集合属性:

  Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));

  ss.getSalesname();

  Set carOrders=ss.getCarorders();

这时只相当于执行了SQL语句:

  select * from salesman s where s.sid=1

  如果使用这个carOrders集合,即调用carOrders集合的任何函数[getClass()除外],都会触发select语句读取数据库,把所有关联的carorder对象载入内存。例如下面的语句都可以:

  carOrders.isEmpty();

  carOrders.iterator();

  这时如果set元素的fetch设置为fetch="select",(使用select方式来检索相关类,语义和的fetch="select"相同),那么相当于执行SQL语句:

  select * from carorder1 c where c.salesId=1

即载入所有的carorder对象初始化集合。

  如果在salesman的映射文件中把元素设置如下,即lazy="extra",对集合中每一个元素都进行延迟载入:

          …

     

    

    

    

       …

那么,会对关联类salesman进行延迟加载,即以下的代码只会载入carorder对象,但是不会载入与之关联的carorder对象初始化集合属性:

        Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));

     ss.getSalesname();

    Set carOrders=ss.getCarorders();

这时只相当于执行了SQL语句:

  select * from salesman s where s.sid=1

  如果使用这个carOrders集合,即调用carOrders集合的函数并不一定会触发select语句读取数据库,把所有关联的carorder对象载入内存,它只会在所有函数需要载入所有数据时才select所有对象。例如,下面的语句不会使select所有关联对象:

  carOrders.isEmpty();

它只是判断集合是否为空,所以只会触发执行下面的sql语句:

  select count(cid) from carorder1 where salesId =1

  但是如果执行有些需要访问所有对象的函数就会触使初始化所有对象。例如,

  carOrders.iterator();

这时如果set元素的fetch设置为fetch="select",那么相当于执行SQL语句:

  select * from carorder1 c where c.salesId=1

即载入所有的carorder对象初始化集合。

  2.集合的fetch属性

  中的fetch属性有3个值可以选择,即join、subselect和select。它代表对关联集合采取什么SQL语句进行数据库读取,默认值为select。join代表使用连接查询检索对象。select则代表另外发送select 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联对象。默认使用select值。

● 如果设置为join,那么不管lazy设置为什么属性值,都会使用外连接立即检索主类和与主类关联的所有对象,初始化集合对象。join值和select值与的fetch属性上介绍的一样,这里不再赘述。

● Subselect属性值代表把本次查询得到的所有主类的关联集合都初始化。

  例如,假设数据库中存在salesman对象和carorder对象,数据及其关系如下:

 

图8-4  数据库类对象及其关系

  然后在数据库中执行如下语句:

  Salesman ss=(Salesman)session.createQuery("from Salesman").list().get(0);

  ss.getSalesname();

  Set carOrders=ss.getCarorders();

  carOrders.isEmpty();

  这里的HQL语句载入了所有的salesman对象,包括salesman1和salesman2,但是get(0)只会访问salesman1对象。

  假设设置fetch="select",那么只会载入与salesman1关联的carorder1和carorder2对象,执行的SQL语句:

  select * from salesman

  select * from carorder c where c.salesId=1

只检索载入的salesman1关联的carorder对象。

  假设设置fetch="subselect",那么会把检索到的所有salesman对象关联的集合carorders都初始化。则载入与salesman1关联的carorder1和carorder2对象,载入与salesman2关联的carorder3,执行的sql语句:

  select * from salesman 

  select * from carorder c where c.salesId in (select s.sid from salesman s)

只检索第一次select得到的所有实体的关联集合。

8.3.6  batch载入策略

  关联实体的载入和关联集合的载入都可以设置批量检索属性,即、和集合元素、等都有batch-size="N"属性,值为一个整数,说明每次批处理的个数。下面以元素的batch-size为例,说明批量检索的意思。

  先设置fetch="select",不设置batch-size值,那么,执行下面的方法:

  List results=session.createQuery("from Salesman").list();

  Iterator sales=results.iterator();

  

  Salesman sale1=(Salesman)sales.next();

  Salesman sale2=(Salesman)sales.next();

  

  Iterator order1=sale1.getCarorders().iterator();

  Iterator order2=sale2.getCarorders().iterator();

调用列表的iterator的next()方法可以不断地遍历列表,读出对象。上面的代码从数据库中载入了salesman1和salesman2对象后,并调用它们的getCarorders().iterator(),通过两个select语句把它们关联的carorder对象分别载入。

  SQL语句如下:

  select * from salesman

  select * from carorder1 c where c.salesId=1

  select * from carorder1 c where c.salesId=2

这样使用的SQL语句比较多。

  如果在映射文件中设置batch-size属性。例如下面的映射文件片断:

  

    

    

   

那么当访问sale1.getCarorder().iterator()方法时,由于session的缓存中,salesman1和salesman2关联的2个carorders集合代理类没有被初始化,由于元素的batch-size="3",即最多批量初始化3个carorders集合代理类,所以,salesman1和salesman2关联的carorders集合都会被同时初始化。上面的代码执行的SQL语句如下:

  select * from salesman

  select * from carorder1 c where c.salesId=1 or c.salesId=2

  当访问sale2.getCarorder().iterator()方法时,就不需要再初始化它的carorders集合代理类了。

  可见,设置批量处理,可以把缓存中的代理类进行批量初始化。这样可以减少数据库的访问次数。

8.4  小    结

  本章首先讲解了Hibernate的另一种常用的面向对象的检索方式,即QBC检索方式。然后介绍了在数据查询中经常用到的连接查询在Hibernate中的HQL和QBC检索方式的实现。最后,详细地介绍了Hibernate3提供的各种持久化对象、关联实体和关联集合的检索方式,即设置什么时候载入对象,如何载入,以优化检索性能。

猜你喜欢

转载自blog.csdn.net/zxssoft/article/details/83055137