QBC数据检索

8.1  QBC数据检索
  前面,介绍了Hibernate常用的查询检索方式HQL。HQL是一种面向对象的,语法类似于SQL语句的检索语言。对于HQL检索,需要以字符串的形式来组织HQL语句,如果查询语句比较复杂,较长的HQL语句会影响程序的可读性。而且,除了参数外,HQL语句的条件和其他各部分组成都是静态地写入HQL语句字符串中的。在开发中,经常会遇到根据用户输入一些查询条件来设置查询,对于这种情况,组织HQL语句就比较麻烦。
  Hibernate还提供了另一种形式的面向对象的查询方式QBC(Query by Criteria)。这种查询是以函数API的方式动态地设置查询条件,组成查询语句。基本结构是session创建criteria接口,然后调用criteria接口的add函数,增加查询条件(即criterion类),然后调用criteria的list()函数,即可以以List的形式返回查询结果。
  除了有当前session创建的criteria接口外,Hibernate还提供了在session不可用时,组织查询语句的游离态的DetachCriteria类,它的查询条件设置与criteria类似,只是它不由session创建,而是在需要session可用时,再调用getExecutableCriteria()执行查询。
  另外,为了让criteria的查询方法与HQL一样功能丰富,Hibernate还提供了projections类,用于提供各种函数来产生可以使用聚集函数,进行投影查询和进行查询结果设置的各种projection接口,提供了Restrictions类用于提供各种函数来产生各种criterion作为查询条件,提供了用于结果排序的order类。
  图8-1是Criteria相关类的结构图。

图8-1  Criteria相关类图
  由图8-1可见,Criteria接口和DetachedCriteria类都继承了CriteriaSpecification接口,开发人员在使用QBC查询时,都是直接选择用Criteria或DetachedCriteria。而它们两个都是一样使用可以设置各种查询条件或者查询结果的Criterion,Projection等接口。
8.1.1  QBC查询主要类
  下面分别对QBC查询中使用的几个主要接口和类进行介绍。
  1. Criteria接口
  Criteria调用add函数添加Criterion对象来组成查询条件,然后根据查询条件来查询持久化类对象。与HQL查询的Query类一样,也是由Session调用createCriteria()函数创建。
  以下代码是检索价格大于25的Product对象。
      Criteria crit = session.createCriteria(Product.class);
          crit.add(Restrictions.gt("price",new Double(25.0)));
          List results = crit.list();
  相应地,HQL实现的代码如下:
          Query query = session.createQuery(
                  "from Product where price>:price");
          query.setDouble("price",25.0);
          List results = query.list();
  使用Criteria查询的步骤是:
  (1) 调用一个可用的session的createCriteria()方法创建Criteria对象,其中createCriteria()方法以被查询的实体类类型作为参数。
  (2) 根据需要使用add函数设置各种criterion查询条件,使用setProjection设置projection
  (3) 调用Criteria的list或者scroll方法即执行查询语句,检索数据库,把查询结果返回放在List或ScrollableResults中。
  (4) 读取List和ScrollableResults,即可取出查询对象。
  Criteria接口在调用List或者Scroll方法时执行数据库检索,返回结果可以是以List形式或者ScrollableResults形式,与Query相比,少了iterate()方法执行返回iterator形式的结果。另外,与Query类似,Criteria也可以使用setFlushMode、setCacheMode、setTimeOut等设置各种Criteria查询的环境,可以使用setFirstResult()、setMaxResult()等方法设置检索结果。
  下面介绍在Query中没有的,但在Criteria中使用的主要的函数:
  (1) Criteria add(Criterion criterion)
  用于添加查询条件,相当于设置HQL中的where从句。当有多个查询条件时,可以逐个增加。例如:
  Criteria crit = session.createCriteria(Product.class);
          crit.add(Restrictions.gt("price",new Double(25.0)));
          crit.add(Restrictions.gt("name","Clothes"));
  List results = crit.list();
或者使用方法链编程风格:
  List results=session.createCriteria(Product.class)
   .add(Restrictions.gt("price",new Double(25.0)))
   .add(Restrictions.gt("name","Clothes"))
   .list();
  多个add相当于对查询条件进行and操作,即要求同时满足这些条件。
  (2) Criteria addOrder(Order order)
  如果需要排序,可以使用Criteria的addOrder()方法增加排序用的Order类。
  (3) Criteria createAlias(String associationPath, String alias)
     Criteria createAlias(String associationPath, String alias, int joinType)
  用于设置连接查询,第一种重载形式使用的是默认的内连接;第二种重载形式可以选择连接方式。
  associationPath:属性的路径,使用点“.”作为分割符。例如,Basiccar持久化类中有集合属性persons,而persons中的元素是持久化类Person,而Person类中有集合属性clotheses,其中存有关联对象clothes,如果要从Basiccar对象关联到clothes对象,属性路径就写成:persons.clotheses,使用点隔开。
  alias:关联属性的别名,后面可以使用这个别名来设置关联条件。
  joinType:连接查询时选择的关联方式。joinType可以是内连接CriteriaSpecification.INNER_JOIN(默认值)、全连接CriteriaSpecification.FULL_JOIN, 或者左连接CriteriaSpecification.LEFT_JOIN。
  (4) Criteria createCriteria(String associationPath)
     Criteria createCriteria(String associationPath, int joinType) 
  Criteria createCriteria(String associationPath, String alias) 
  Criteria createCriteria(String associationPath, String alias, int joinType) 
  创建一个指向主类关联的实体的criteria查询,在进行存在关联关系的对象检索时会用到。
  joinType可以指定关联是内连接、左联接还是全连接。alias则可以指定别名。
  返回值是被创建的子查询。
  (5) Criteria setProjection(Projection projection)
  如果需要使用投影查询、聚集函数等,可以按需要生成Projection类,然后使用Criteria的setProjection()方法,对返回结果进行某些列的投影或聚集运算。
  2. DetachedCriteria类
  使用Criteria查询需要当前有可用的session,但是,有时构造查询语句时,并不一定能得到可用的session。例如,在分层的项目开发过程中,通常会在表现层,根据用户选择的条件,动态生成SQL语句,进行查询。这个时候,表现层是得不到session的,但是它需要把构造好的查询传给业务层进行session查询。DetachedCriteria就可以解决这个问题,即在表现层,应用程序使用DetachedCriteria来构造查询条件,然后把这个detachedCriteria传递给业务层对象,让业务层在session范围内构造criteria查询。所以,detachedCriteria在构造查询时,session还不可用,detachedCriteria查询面对的是游离的POJO类,而不是Criteria查询时面对的持久化对象,所以称为detachedCriteria。以下是一个使用DetachedCriteria查询的简单例子。
  DetachedCriteria detachedCriteria= DetachedCriteria.forClass (BasicCar.class); detachedCriteria.add(Restrictions.eq("id", new Integer(1)));
  上面的代码片段先创建了一个DetachedCriteria的实例,然后只要在有可用的session时,使用它的getExecutableCriteria()方法,把这个可用的session关联进来即可,它会返回一个在这个session内进行查询的criteria类。代码如下所示:
  Criteria crit=detachedCriteria.getExecutableCriteria(session);
   List results = crit.list();
  DetachedCriteria 提供了4个静态方法forClass(Class)或forEntityName(Name)进行DetachedCriteria实例的创建。DetachedCriteria也提供了add(Criterion)、addOrder(Order)、setProjection(Projection)、createAlias()、createCriteria()等方法用于构造查询及其返回结果,与Criteria提供的函数不同的是,DetachedCriteria类提供的函数返回也是DetachedCriteria 类。
  3. Criterion和Retrictions类
  Criterion被Criteria接口的add()方法调用,作为查询条件。内置的Criterion类型由Restrictions这个工厂类提供。实现Criterion接口的可以用来作为查询条件的类包括Expression、Example、Jonction等。
  Criterion的类图关系如图8-2所示。

图8-2  Criterion相关类图
  4. Projection和Projections
  Projection接口是Criteria查询结果集的投影表示。内置的Projection类由Projections工厂类提供。实现projection接口的类包括AliasedProjection、Distinct、ProjectionList、SimpleProjection、SQLProjection等5个。Projection的类图如图8-3所示。

图8-3  Projection相关类图
  Projection接口被作为Criteria的setProjection()函数的参数,对查询结果进行选择某些属性的投影,进行avg、max、min、count、sum等聚集运算。
  Projections提供多种静态方法来产生对查询的结果进行各种运算的projection实例。
  表8-1列出了Projections提供的各种结果运算的函数,以及相应的HQL运算符。
表8-1  Projections的函数
意    思
Projections函数
HQL例子
计算平均值
avg(String?propertyName)
Select avg(o.property) from Object o
计算数目
count(String?propertyName)
Select count(o.property) from Object o
过滤相同值的属性然后计算数目
countDistinct(String?propertyName)
Select count(distinct o.property) from Object o
过滤相同值的属性
distinct(Projection?proj)
Select distinct o.property from Object o
按某个属性分组
groupProperty(String?propertyName)
From Object o group by o.property
取对象的主键
id()
Select o.id from Object o
计算最大值
max(String?propertyName)
Select max(o.property) from Object o
计算最小值
min(String?propertyName)
Select min(o.property) from Object o
投影某个属性
property(String?propertyName)
Select o.property from Object o
计算行数目
rowCount()
Select count(*) from Object o
计算总和
sum(String?propertyName)
Select sum(o.property) from Object o
  5. Order
  Order类提供对Criteria查询结果集的排序,其静态函数生成升序或降序的Order类,被作为Criteria的参数传给addOrder()方法。
  其主要函数有:
  static Order asc(String propertyName) 按降序排
  static Order desc(String propertyName) 按升序排
8.1.2  使用Expression类和Example类设置查询条件
  下面分别介绍Expression类和Example类的查询条件。
  1.Expression类
  Expression类由Restrictions工厂类的各个设置条件的静态方法产生。例如,Restrictions类中的gt(String propertyName, Object value)静态方法,用于设置某个属性等于某个值的查询条件,返回的SimpleExpression相当于where propertyName='value'的条件。
  表8-2列出了Restrictions提供的各种条件设置,以及相应的HQL运算符。
表8-2  Restrictions的函数
意    思
Restrictions函数
HQL例子
查询条件逻辑and运算
and(Criterion?lhs, Criterion?rhs)
From Object o where o.property1 = ? and o.property2=?
检索在两个数值之间的
between(String?propertyName, Object?lo,
Object?hi)
From Object o where o.property between ? and ?
等于
eq(String?propertyName, Object?value)
eqProperty(String?propertyName, String?otherPropertyName)
From Object o where
o.property=?
大于等于
ge(String?propertyName, Object?value)
geProperty(String?propertyName, String?otherPropertyName)
From Object o where
o.property>=?
大于
gt(String?propertyName, Object?value)
gtProperty(String?propertyName, String?otherPropertyName)
From Object o where
o.property>?
主键等于
idEq(Object?value)
From Object o where o.id=?
字符串匹配,不区分大小写
ilike(String?propertyName, Object?value)
ilike(String?propertyName, String?value, MatchMode?matchMode)
From Object o where lower
(o.property) like ??
(续表)  
意    思
Restrictions函数
HQL例子
范围运算
in(String?propertyName, Collection?values)
in(String?propertyName, Object[]?values)
From Object o where o.property in(‘a’,’b’…)
检查对象的一个集合属性是否为空,也就是在多对多或多对一双向关联中,“一”方对应的“多”方为空,没有关联的
isEmpty(String?propertyName)
From Object o where
o.properties is empty
检查对象的一个集合属性是否不为空
isNotEmpty(String?propertyName)
From Object o where
o.properties is not empty
一个属性是否不为空
isNotNull(String?propertyName)
From Object o where o.property is not null
一个属性是否为空
isNull(String?propertyName)
From Object o where o.property
is null
小于等于
le(String?propertyName, Object?value)
leProperty(String?propertyName, String?otherPropertyName)
From Object o where
o.property<=?
字符串匹配
like(String?propertyName, Object?value)
like(String?propertyName, String?value, MatchMode?matchMode)
From Object o where o.property
like ‘??’
小于
lt(String?propertyName, Object?value)
ltProperty(String?propertyName, String?otherPropertyName)
From Object o where
o.property<?
Not equal
ne(String?propertyName, Object?value)
neProperty(String?propertyName, String?otherPropertyName)
From Object o where o.property not
查询条件逻辑非
not(Criterion?expression)
From Object o where not(一个查询条件)
查询条件逻辑and
运算
or(Criterion?lhs, Criterion?rhs)
From Object o where o.property1 =? or o.property2=?
  
  具体各种方法的使用例子,请看8.3节。
  2.Example类
  使用Example类进行查询,首先需要创建一个对象样板,然后检索出所有与这个样板对象相同的对象。例如,
  BasicCar bc=new BasicCar();
  bc.setFactory("aaaa");
  
  Criteria crit = session.createCriteria(BasicCar.class);
  crit.add(Example.create(bc));
  
  List results = crit.list();
  Example使用静态方法create(Object entity)来创建一个Criterion对象,它按照对象的属性来比较查询条件,以上的程序段检索factory属性为"aaaa"的BasicCar对象。
  另外,Example也提供了给这个样板对象设置一些查询过滤。例如,
  Example exampleUser =Example.create(bc)
  .ignoreCase() //忽略大小写
  .enableLike(MatchMode.ANYWHERE); //任意位置的匹配
  
  Criteria crit = session.createCriteria(BasicCar.class);
  crit.add(exampleUser);
  List results = crit.list();
  这个查询相当于查询factory属性为"%aaaa%",即只要对factory属性值进行aaaa的模糊字符串匹配。
  Example类提供的各种进行查询过滤的方法如下。
● enableLike(MatchMode):用于过滤string匹配,有4个选择。分别为MatchMode.ANYWHERE(默认匹配模式,表示任意位置的匹配,形如%value%)、MatchMode.END(表示匹配结尾,形如%value)、MatchMode.EXACT(表示完全匹配)、MatchMode.START (表示匹配开头,形如value%)。
● excludeNone():排除为null或0的属性值匹配。
● excludeProperty(String name):排除名字为name的属性的匹配。
● excludeZeroes():排除为0的属性值匹配。
● ignoreCase():匹配String时忽略大小写。
● setEscapeCharacter(Character escapeCharacter):对like从句的字符串匹配,排除某些字符。
● setPropertySelector(Example.PropertySelector selector):选择属性的匹配策略。
  这些过滤方法都是返回Example类,所以可以连接着设置各种过滤。
8.1.3  使用QBC各种检索例子
  下面将通过具体的例子来介绍,如何使用Criteria查询,结合各种Criterion查询条件和Projection投影运算,满足各种数据检索要求。
  1.返回所有的对象
  下面是一个最简单的Criteria查询,只是在创建Criteria时制定检索的对象,不设置任何查询条件,即返回的List为所有的BasicCar对象。
  Criteria crit = session.createCriteria(BasicCar.class);
  List results = crit.list();
  返回的List中存放的是BasicCar对象。
  以上的代码相当于执行了select * from basiccar。
  2. 聚集函数
  Criteria使用Projections类可以使用聚集函数max、min、avg、count、countDistinct、rowCount等。
  如果需要使用多个Projections设置多个条件,则需要使用Projections的projectionList()函数创建一个ProjectList。
  下面的例子Projections的max、min、countDistinct返回BasicCar的最大id、最小日期和计算不同的名字有多少个。
  Criteria crit = session.createCriteria(BasicCar.class)
  ProjectionList projList = Projections.projectionList();
  projList.add(Projections.max("id"));
  projList.add(Projections.min("date"));
  projList.add(Projections.countDistinct("name"));
  crit.setProjection(projList);
  List results = crit.list();
  返回的List中存放有最大id,最小日期,不同name的个数,这三者组成的对象数组。
  其对应的SQL语句如下:
  select max(this_.id) as y0_, min(this_.date) as y1_, count(distinct this_.name) as y2_ from basiccar this_
  3. many-to-one双向关联检索
  检索sid=1的Salesman对象关联的carorder对象cid大于1的数据
  Criteria crit = session.createCriteria(Salesman.class);
  crit.add(Restrictions.eq("sid",new Long(1)));
  Criteria prdCrit = crit.createCriteria("carorders");
  prdCrit.add(Restrictions.gt("cid",new Long(1)));
  List results = crit.list();
  返回的List中存放的是Salesman对象。
  如果使用了连接检索策略,以上查询相当于执行以下的SQL语句:
  select * from salesman this_ inner join carorder1 carorder1_ on this_.sid=carorder1_.salesId left outer join salesman salesman4_ on carorder1_.salesId=salesman4_.sid where this_.sid=? and carorder1_.cid>?
  4.使用数学运算符>=、>、!=、=
  下面的例子检索日期大于等于"1994-03-04"的BasicCar对象,并且使用setMaxResults(1)函数设置返回的列表只取一个结果。
     Criteria crit = session.createCriteria(BasicCar.class);
   crit.add( Expression.ge("date", Date.valueOf("1994-03-04")) );
   crit.setMaxResults(1);
   List results = crit.list();
  其对应的SQL语句如下:
  select *  from basiccar b where b.date>='1994-03-04'
  下面的例子检索id大于1的BasicCar对象。
     Criteria crit = session.createCriteria(BasicCar.class);
   crit.add( Expression.gt("id", new Long(1)) );
   List results = crit.list();
  其对应的SQL语句如下:
  select *  from basiccar b where b.id>1
  下面的例子检索id不等于1的BasicCar对象。
  Criteria crit = session.createCriteria(BasicCar.class);
  crit.add( Expression.ne("id", new Long(1)) );
  List results = crit.list();
  其对应的SQL语句如下:
  select *  from basiccar b where b.id<>1
  5. 查询id
  下面的例子检索主键为1的对象。
  Criteria crit = session.createCriteria(BasicCar.class);
  crit.add( Expression idEq(new Long(1)) );
  crit.setMaxResults(1);
  List results = crit.list();
  其对应的SQL语句如下:
  select *  from basiccar b where b.id = 1
  6. group by
  下面的例子按name来进行分组。
  Criteria crit = session.createCriteria(BasicCar.class);
  crit.setProjection(Projections.groupProperty("name"));
  List results = crit.list();
  返回对象List用来存放不同名字的数组,存放的是string行的name。
  其对应的SQL语句如下:
  select b.name from basiccar b group by b.name
  7. 字符串匹配,like和islike
  下面的例子使用like()函数检索name开头字母为A的BasicCar对象。
  Criteria crit = session.createCriteria(BasicCar.class);
  crit.add(Restrictions.like("name","A%"));
  List results = crit.list();
  其对应的SQL语句如下:
  select * from basiccar b where b.name like ‘A%’
  使用islike()函数,代表不区分大小写的字符串匹配。
  MatchMode可以选择匹配模式,MatchMode.END从末端开始匹配,MatchMode.ANYWHERE表示任意地方的字符串匹配, MatchMode.START从开始的地方开始匹配, MatchMode.EXACT表示整个字符串精确匹配。
  下面的例子检索数据库name的后面字符为不区分大小写的a的BasicCar对象。
  Criteria crit = session.createCriteria(BasicCar.class);
  crit.add(Restrictions.islike("name","a", MatchMode.END));
  List results = crit.list();
  8. is null
  下面的例子检索数据库name为空的BasicCar对象。
  Criteria crit = session.createCriteria(BasicCar.class);
  crit.add(Restrictions.isNull("name"));
  List results = crit.list();
  9. 投影某些属性
  使用Projections.property()函数,可以对结果进行某些属性的投影,相当于使用select语句。
  下面的例子返回结果进行name和date列的投影。
  Criteria crit = session.createCriteria(BasicCar.class);
  ProjectionList projList = Projections.projectionList();
  projList.add(Projections.property("name"));
  projList.add(Projections.property("date"));
  crit.setProjection(projList);
  List results = crit.list();
  返回的List中存放的是由name和date组成的对象数组。
  其对应的SQL语句如下:
  select b.name as n, b.date as d from basiccar b
  10. 计算列返回的数目
  下面的例子计算返回的BasicCar对象的数量。
  Criteria crit = session.createCriteria(BasicCar.class);
  crit.setProjection(Projections.rowCount());
  List results = crit.list();
  其对应的SQL语句如下:
  select count(*) as y0_ from basiccar this_
  11. 设置返回结果
  下面的例子使用setFirstResult(2)设置从第二个开始返回,使用setMaxResults(2)设置每次最多返回2个对象。
  Criteria crit = session.createCriteria(BasicCar.class);
  crit.setFirstResult(2);
  crit.setMaxResults(2);
  List results = crit.list();
  下面的例子使用uniqueResult得到单个对象。
  Criteria crit = session.createCriteria(BasicCar.class);
  Criterion price = Restrictions.gt("name",new Lone(1));
  crit.setMaxResults(1);
  Product product = (Product) crit.uniqueResult();
  12. 结果排序
  使用Order类调用desc()函数、asc()函数可以进行排序。
  下面的例子根据BasicCar对象的name进行升序排序。
  Criteria crit = session.createCriteria(BasicCar.class);
  crit.addOrder(Order.desc("name"));
  对应的SQL语句是:
  Select * from basiccar b by b.name desc
  13. 逻辑or、and条件
  下面的例子中设置了3个条件,分别是id大于等于1,name开头字母为P,factory的开头字母为f。然后把前两个条件进行逻辑或操作,即只要满足其中之一,再把最后一个条件进行逻辑与操作。
  Criteria crit = session.createCriteria(BasicCar.class);
  Criterion id = Restrictions.gt("id",new Long(1));
  Criterion name = Restrictions.like("name","P%");
  LogicalExpression orExp = Restrictions.or(id,name);
  crit.add(orExp);
  crit.add(Restrictions.ilike("factory","f%"));
  List results = crit.list();
  在上面的例子中Restrictions.or()把id和name两个检索条件进行or结合;在原来的查询条件上不断地add(),那么就相当于把检索条件进行and逻辑与操作。
  其对应的SQL语句如下:
  select * from basiccar this_ where (this_.id>? or this_.name like ?) and lower(this_.factory) like ?
8.2  连 接 查 询
  在数据库检索过程中,表之间的连接查询是很常见,先来看一下各种连接的定义。
8.2.1  连接定义
  本节将介绍三种连接方式,分别是内连接、右外连接和左外连接。
● 左连接:左连接的结果集包括LEFT OUTER 子句中指定的左表的所有行,而不仅仅是连接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表的列均为空值。
● 右连接:右连接是左连接的反向连接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。
● 内连接:内连接使用比较运算符根据每个表共有的列的值匹配两个表中的行,即返回相关联的列所匹配的行。
  下面用具体实例来说明这三种连接。
  数据库中有两个表,person和basiccar,其中person的列carId是关联basiccar的外键,现在表中的数据如下:
  basiccar表
  +----+------+----------+------------+
  | id | name | factory  | date         |
  +----+------+----------+------------+
  |  1 | car1 | shenzhen | 1992-02-02 |
  |  2 | car2 | nanjing  | 1992-04-02  |
  +----+------+----------+------------+
  person表
  +----+-----------+-------+
  | id | salesname  | carId |
  +----+-----------+-------+
  |  1 | man1        |     1  |
  |  2 | man2        |  NULL |
  |  3 | man3        |     1  |
  +----+-----------+-------+
  
  
  由上面的数据可以看出,basiccar表有2组数据;person表有3组数据。其中存在两组关联:id为1的person和id为1的basiccar关联;id为3的person和id为1的basiccar关联。下面,分别来看看各种连接下,数据的检索结果。
  1.内连接
  在数据库中输入以下查询语句:
  select person.id as pid,
         basiccar.id as bid,
         person.carid as carid,
         person.salesname as sName,
         basiccar.name as carName,
         basiccar.factory factory,
         basiccar.date as date
  from person person inner join basiccar basiccar
  on person.carId=basiccar.id;
  数据查询结果如下:
  +-----+-----+------+-------+--------+----------+------------+
  | pid | bid | carid | sName | carName | factory  | date       |
  +-----+-----+------+-------+---------+----------+-----------+
  |   1 |   1  |     1 | man1  | car1    | shenzhen | 1992-02-02 |
  |   3 |   1  |     1 | man3  | car1    | shenzhen | 1992-02-02 |
  +-----+-----+------+-------+---------+----------+-----------+
  由上面的查询结果可以看出,内连接检索出有关联关系的两组对象组成的数据,内连接是把关联关系返回。
  2.左连接
  先看使用person表左连接basiccar表的查询结果。在数据库中输入以下查询语句:
  select person.id as pid,
         basiccar.id as bid,
         person.carid as carid,
         person.salesname as sName,
         basiccar.name as carName,
         basiccar.factory factory,
         basiccar.date as date
  from person person left join basiccar basiccar
  on person.carId=basiccar.id;
  数据查询结果如下:
  
  +-----+-----+-------+-------+---------+----------+----------+
  | pid | bid  | carid | sName | carName | factory  | date       |
  +-----+------+-------+-------+---------+----------+---------+
  |   1 |    1  |     1  | man1  | car1    | shenzhen  | 1992-02-02 |
  |   2 | NULL |  NULL | man2  | NULL    | NULL       | NULL        |
  |   3 |    1  |     1  | man3  | car1    | shenzhen  | 1992-02-02 |
  +-----+------+-------+-------+---------+----------+---------+
  由上面的查询结果可以看出,person表的3组数据都被检索出来了,与其相关联的basiccar数据,如果有值,则把关联的basiccar记录检索出来;如果为空,则赋值null。
  下面再看一下使用basiccar表左连接person表的查询结果。在数据库中输入以下查询语句:
  select person.id as pid,
         basiccar.id as bid,
         person.carid as carid,
         person.salesname as sName,
         basiccar.name as carName,
         basiccar.factory factory,
         basiccar.date as date
  from basiccar basiccar left join person person
  on person.carId=basiccar.id;
  数据查询结果如下:
  +------+-----+-------+-------+---------+----------+---------+
  | pid  | bid | carid | sName | carName | factory  | date       |
  +------+-----+-------+-------+---------+----------+---------+
  |    1  |   1 |     1  | man1  | car1    | shenzhen | 1992-02-02 |
  |    3  |   1 |     1  | man3  | car1    | shenzhen | 1992-02-02 |
  | NULL |   2 |  NULL | NULL  | car2    | nanjing   | 1992-04-02 |
  +------+-----+-------+-------+---------+----------+---------+
  由上面的查询结果可以看出,basiccar表的两组数据都被检索出来了,与其相关联的person数据,如果为空,则赋值null。在上表结果中,由于basiccar.id=1的basiccar记录关联两组person记录(id=1,id=3),所以会把这两组组合作为两个记录返回;basiccar.id=2的basiccar记录,虽然没有关联的person记录,则把person记录记为null,也把其作为一个记录返回。
  由此可见,左连接是内连接的检索结果加上左连接左边表中没有关联关系的数据。
  3.右连接
  先看一下使用person表右连接basiccar表的查询结果。在数据库中输入以下查询语句:
  select person.id as pid,
         basiccar.id as bid,
         person.carid as carid,
         person.salesname as sName,
         basiccar.name as carName,
         basiccar.factory factory,
         basiccar.date as date
  from person person right join basiccar basiccar
  on person.carId=basiccar.id;
  数据查询结果如下:
  +------+-----+-------+-------+---------+---------+----------+
  | pid  | bid | carid | sName | carName | factory  | date       |
  +------+-----+-------+-------+---------+---------+----------+
  |    1 |   1 |     1 | man1  | car1    | shenzhen | 1992-02-02 |
  |    3  |   1 |     1  | man3  | car1    | shenzhen | 1992-02-02 |
  | NULL |   2 |  NULL | NULL  | car2    | nanjing   | 1992-04-02 |
  +------+-----+-------+-------+---------+---------+----------+
  由上面的查询结果可以看出,person表右连接basiccar表的查询结果与basiccar表左连接person表的查询结果一样。basiccar表的两组数据都被检索出来了,与其相关联的person数据,如果为空,则赋值null。在上表结果中,由于basiccar.id=1的basiccar记录关联两组person记录(id=1,id=3),所以会把这两组组合作为两个记录返回;basiccar.id=2的basiccar记录,虽然没有关联的person记录,则把person记录记为null,也把其作为一个记录返回。
  再看一下使用basiccar表右连接person表的查询结果。在数据库中输入以下查询语句:
  select person.id as pid,
         basiccar.id as bid,
         person.carid as carid,
         person.salesname as sName,
         basiccar.name as carName,
         basiccar.factory factory,
         basiccar.date as date
  from basiccar basiccar right join person person
  on person.carId=basiccar.id;
  数据查询结果如下:
  +-----+------+-------+-------+---------+---------+----------+
  | pid | bid  | carid | sName | carName | factory  | date       |
  +-----+------+-------+-------+---------+---------+----------+
  |   1 |    1  |     1  | man1  | car1    | shenzhen | 1992-02-02 |
  |   2 | NULL |  NULL | man2  | NULL    | NULL      | NULL         |
  |   3 |    1  |     1  | man3  | car1    | shenzhen | 1992-02-02 |
  +-----+------+-------+-------+---------+---------+----------+
  由上面的查询结果可以看出,basiccar表右连接person表的查询结果与person表左连接basiccar表的查询结果一样。person表的3组数据都被检索出来了,与其相关联的basiccar数据,如果有值,则把关联的basiccar记录检索出来;如果为空,则赋值null。
  由此可见,右连接是内连接的检索结果加上右连接右边表中没有关联关系的数据。
8.2.2  Hql,Criteria对连接的支持
  分别在数据库中建立两张关联的salesman表和carorder1表,其中carorder1表使用salesId列关联到salesman。
  其中,salesman表及其数据如下:
  +-----+-----------+
  | sid | salesName |
  +-----+-----------+
  |   1 | man32      |
  +-----+-----------+
  carorder1表及其数据如下:
  +-----+-------------+---------+
  | cid | carname      | salesId |
  +-----+-------------+---------+
  |   1 | order212122  |       1  |
  |   2 | order2221    |       1  |
  +-----+-------------+---------+
  使用多对一双向关联来建立它们的对象关系模型。如果两个对象模型之间没有设立关联关系,虽然数据库表中有关联关系,也无法通过Hibernate 的Hql和Criteria进行连接查询,因为这两个查询是面向对象的,必须是对象之间建立了关联关系。如果是两个对象间没有关系的表进行连接查询,那么可以使用SQL查询,这样可以使用on来指定进行任意列的关联。下面是两个表对应的Salesman对象和CarOrder对象的主要部分。
  Salesman对象的代码:
  public class Salesman implements java.io.Serializable{
   private long sid;
  
   private String salesName;
  
   private Set carOrders = new HashSet();
  //…省略了getter/setter和构造函数
  
  Salesman对象的映射文件Salesman.hbm.xml的主要部分,定义了salesman的属性和数据库表映射,以及关联到carorder的集合映射。代码如下:
  <class name="basicCar.bean.Salesman" table="salesman" lazy="false">
    <id column="sid" name="sid" type="long">
    <generator class="increment"></generator>
    </id>
    <property name="salesname" column="salesName" type="string"></property>
    <set name="carorders" inverse="false" cascade="delete">
    <key column="salesId"/>
    <one-to-many class="basicCar.bean.CarOrder"/>
    </set>
    </class>
  CarOrder对象的代码:
  public class CarOrder  implements java.io.Serializable {
  
   private long cid;
  
   private String carName;
  
   private Salesman salesman;
  //…省略了getter/setter和构造函数
  CarOrder对象的映射文件CarOrder.hbm.xml的主要部分,定义了carorder的属性和数据库表映射关系,以及和salesman对象的关联。
  <class name="basicCar.bean.CarOrder" table="carorder1" lazy="false">
    <id column="cid" name="cid" type="long">
    <generator class="increment"></generator>
    </id>
    <property name="carname"  column="carname" type="string"></property>
     <many-to-one name="salesman"
     column="salesId"
     class="basicCar.bean.Salesman"
     cascade="all"
     not-null="false">
     </many-to-one>
    </class>
  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语句如下:
  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默认的类就是延迟加载。如下所示:
  <class name="basicCar.bean.Salesman" table="salesman" lazy="true">
    <id column="sid" name="sid" type="long">
    <generator class="increment"></generator>
    </id>
  …省略了其他属性的映射内容
  </class>
这个设置,当执行下面的代码:
  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方法
其对应的映射文件的主要部分如下所示:
  <hibernate-mapping>
    <class name="basicCar.bean.CarOrder" table="carorder1" lazy="true">
    <id column="cid" name="cid" type="long">
    <generator class="increment"></generator>
    </id>
    <property name="carname"  column="carname" type="string"></property>
     <many-to-one name="salesman"
     column="salesId"
     class="basicCar.bean.Salesman"
     cascade="all"
     not-null="false"
     lazy="proxy"
     fetch="select">
     </many-to-one>
    </class>
  </hibernate-mapping>
  这里,对于在载入与carorder对象关联的salesman对象时,采用怎样的载入策略,是由<many-to-one>中的lazy和fetch属性,以及salesman映射文件类级别的lazy属性决定的。
  1.<many-to-one>中的lazy属性
  <many-to-one>中的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也会影响延迟载入。<many-to-one>中的Lazy属性设置,结合关联类的lazy属性设置,载入情况如表8-3所示。
表8-3  类级别的载入策略
<many-to-one>中的Lazy属性
关联类的lazy属性
关联类什么时候被载入
false
true
立即载入
No-proxy/proxy
true
延迟载入
No-proxy/proxy
false
立即载入
false
false
立即载入
默认(proxy)
默认(true)
延迟载入
  说明:
  这里的延迟或立即载入,是指当主类被载入时,何时载入关联类对象。立即载入,即主类被载入时就把关联类对象载入;延迟载入,即主类被载入时不把关联类载入,直到通过这个实体属性,访问这个关联类的某个非主键属性时才把关联类载入。至于主类何时载入,主要根据主类的类级别的lazy属性设置为true还是false。
  在本例中,如果在<many-to-one>中设置lazy="false",也就是对关联的salesman对象进行立即检索。那么在carorder对象被载入时,salesman对象也被载入。例如,如果在carorder.hbm.xml中carorder的类级别检索策略设置为立即检索,也就是
  <class name="basicCar.bean.CarOrder" table="carorder1" lazy="false">
那么执行下面的代码:
  CarOrder co=(CarOrder) session.load(CarOrder.class, new Long(1));
会把carorder对象和关联的salesman对象都载入内存。
  如果在<many-to-one>中设置lazy="false",也就是对关联的salesman对象进行立即检索,然后在carorder.hbm.xml中,carorder的类级别检索策略设置为延迟检索,也就是:
  <class name="basicCar.bean.CarOrder" table="carorder1" lazy="true">
那么会在检索carorder对象的任何非主键属性(包括carName和salesman属性)时把关联的salesman对象都载入内存。例如,执行下面代码:
  CarOrder co=(CarOrder) session.load(CarOrder.class, new Long(1));
  co.getCarname();
这样把carorder对象载入的同时,把关联的salesman对象也载入内存。
  上面演示的例子都是对关联对象执行立即检索。
  如果在carorder的映射文件中把<many-to-one>元素设置如下:
  …
    <many-to-one name="salesman"
     column="salesId"
     class="basicCar.bean.Salesman"
     cascade="all"
     not-null="false"
     lazy="proxy"
     fetch="select">
     </many-to-one>
  …
然后salesman中的类级别延迟属性设置为true,即如下:
    <class name="basicCar.bean.Salesman" table="salesman" lazy="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.<many-to-one>中的fetch属性
  <many-to-one>中的fetch属性有两个值可以选择,即join和select。join代表使用连接查询检索对象。select则代表另外发送select 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联对象。默认是使用select值。
  使用join会在检索主类对象时,使用连接查询,把关联对象也载入,这样就需要一条sql语句就可以检索主对象和其关联对象了。例如,下面carorder对应的映射文件段落,设置关联类是使用代理进行延迟加载,并且加载方式是select:
  …
  <many-to-one name="salesman"
     column="salesId"
     class="basicCar.bean.Salesman"
     cascade="all"
     not-null="false"
     lazy="false"
     fetch="join">
     </many-to-one>
  …
那么执行下面的代码时:
  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:
  …
  <many-to-one name="salesman"
     column="salesId"
     class="basicCar.bean.Salesman"
     cascade="all"
     not-null="false"
     lazy="proxy"
     fetch="select">
     </many-to-one>
  …
那么执行下面的代码:
  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。
  <one-to-one>关联的实体对象,其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方法
  其对应的映射文件主要部分如下所示:
  <hibernate-mapping>
    <class name="basicCar.bean.Salesman" table="salesman" lazy="true">
    <id column="sid" name="sid" type="long">
    <generator class="increment"></generator>
    </id>
    <property name="salesname" column="salesName" type="string"></property>
    <set name="carorders"  inverse="false" cascade="delete" fetch="select" lazy="extra">
    <key column="salesId"/>
    <one-to-many class="basicCar.bean.CarOrder"/>
    </set>
    </class>
  </hibernate-mapping>
  与映射java集合相关的元素包括<set>、<idbag>、<list>和<map>。在这些集合元素中与载入策略有关的属性有set和lazy。这里,对于在载入与salesman对象关联的集合carorder对象时,采用什么载入策略,是由<set>中的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。
  在本例中,如果在<set>元素中设置lazy="false",也就是对关联的carorders集合进行立即检索。那么在salesman对象被载入时,carorders集合也被载入。例如,如果在salesman.hbm.xml中,salesman的类级别检索策略设置为延迟检索,也就是:
  <class name="basicCar.bean.Salesman" table="salesman" lazy="true">
那么执行下面的代码,即load这个Salesman对象访问它的非主键属性时,会把salesman对象载入:
  Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));
  ss.getSalesname();
这时,如果salesman.hbm.xml的<set>元素中设置lazy="false",并且fetch设置为fetch="select"(使用select方式来检索相关类,语义和<many-to-one>的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的映射文件中把<set>元素设置如下,即lazy="true",对集合中进行延迟载入:
  …
  <set name="carorders"  inverse="false" fetch="select" lazy="true">
  <key column="salesId"/>
  <one-to-many class="basicCar.bean.CarOrder"/>
  </set>
  …
那么,会对关联类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方式来检索相关类,语义和<many-to-one>的fetch="select"相同),那么相当于执行SQL语句:
  select * from carorder1 c where c.salesId=1
即载入所有的carorder对象初始化集合。
  如果在salesman的映射文件中把<set>元素设置如下,即lazy="extra",对集合中每一个元素都进行延迟载入:
          …
     <set name="carorders"  inverse="false" fetch="select" lazy="extra">
    <key column="salesId"/>
    <one-to-many class="basicCar.bean.CarOrder"/>
    </set>
       …
那么,会对关联类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属性
  <set>中的fetch属性有3个值可以选择,即join、subselect和select。它代表对关联集合采取什么SQL语句进行数据库读取,默认值为select。join代表使用连接查询检索对象。select则代表另外发送select 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联对象。默认使用select值。
● 如果设置为join,那么不管lazy设置为什么属性值,都会使用外连接立即检索主类和与主类关联的所有对象,初始化集合对象。join值和select值与<many-to-one>的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载入策略
  关联实体的载入和关联集合的载入都可以设置批量检索属性,即<many-to-one>、<one-to-one>和集合元素<set>、<map>等都有batch-size="N"属性,值为一个整数,说明每次批处理的个数。下面以<set>元素的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属性。例如下面的映射文件片断:
  <set name="carorders"  inverse="false" fetch="select" lazy="true"  batch-size="3">
    <key column="salesId"/>
    <one-to-many class="basicCar.bean.CarOrder"/>
   </set>
那么当访问sale1.getCarorder().iterator()方法时,由于session的缓存中,salesman1和salesman2关联的2个carorders集合代理类没有被初始化,由于<set>元素的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集合代理类了。
  可见,设置批量处理,可以把缓存中的代理类进行批量初始化。这样可以减少数据库的访问次数。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页