组件(组成)映射
例1:
public class Person {
private Integer pid; //OID 值
private String name;
//第一种方案
private String homeAddr;
private String homeTel;
private String companyAddr;
private String companyTel;
↑一般项目都都采用此方案()
*通过面向对象角度,使用设计模式(组件|组合),将数据都抽取到一个对象中。将多个对象组合在一起,达到重复利用目的。
例2:抽取数据(地址)到一个对象中, 这个值对象既为组件,组件不支持共享引用(没有OID)
public class Address {
//值对象 (vo 值对象/po 持久对象/bo 业务对象)
private String addr;
private String tel;
public class Person {
private Integer pid; //OID 值
private String name;
//第二种方案:组合
private Address homeAddress;
private Address companyAddress;
组件映射配置
<hibernate-mapping>
<class name="com.itheima.b_component.Person" table="t_person">
<id name="pid">
<generator class="native"></generator>
</id>
<property name="name"></property>
<component name="homeAddress" class="com.itheima.b_component.Address">
<property name="addr" column="homeAddr"></property>
<property name="tel" column="homeTel"></property>
</component>
<component name="companyAddress" class="com.itheima.b_component.Address">
<property name="addr" column="companyAddr"></property>
<property name="tel" column="companyTel"></property>
</component>
</class>
</hibernate-mapping>
注意:必须确定组合javabean类型 <component class="">
每一个对象属性必须在表中都存在独有列名。<property column="...">
继承映射
应用场景:
公司的员工,保存:小时工和正式员工
员工类
public class Employee {
private Integer eid;
private String name;
小时工
public class HourEmployee extends Employee {
private int rate; //小时薪水
正式员工
public class SalaryEmployee extends Employee {
private int salary; //正式薪金
简单方式
public class Employee {
private Integer eid;
private String name;
private String temp;//标志 1;小时工2;正式工
private int money; //金额
继承方式1:<sub-class>
*所有内容保存一张表,给表提供表示字段,每一个子类具有独有字段
缺点:存在null值。
配置
继承方式2:<joined-subclass> (掌握)
*将提供三种表,主表存放基本信息,两个从表存放独有字段内容。
表之间存在一对一关系
一对一:
方式1:主表的主键,与从表的外键(唯一),形成主外键关系
方式2:主表的主键,与从表都主键,形成主外键关系
继承方式3:<union-class>
*缺点: 主键生成策略 increment,不能通过数据库自动生成。存在并发问题
每一次都执行select语句,从三张表中查询oid的最大值。
配置
抓取(检索)策略--优化
*开发中,使用都是默认值,最后进行优化,根据业务要求进行相应设置
3.1检索方式
* 立即检索:在执行查询方法之后,立即执行select语句,进行查询。
↓
//class 标签中lazy=false 立即检索
public void loadCustomerfalse(){
Session session=sessionFacoty.openSession();
Transaction tx=session.beginTransaction();
//该行代码让hibernate执行select语句,查询数据库
Customer c=(Customer)session.load(Customer.class, 1);
c.getId();
c.getAge();
tx.commit();
session.close();
}
*延迟检索:在执行查询方法之后,没有进行查询,底层生成代理对象,直到需要相应的数据,再进行查询。
↓
//class 标签中lazy=true 延迟检索
public void loadCustomertrue(){
Session session=sessionFacoty.openSession();
Transaction tx=session.beginTransaction();
Customer c=(Customer)session.load(Customer.class, 1);
//该行代码让hibernate执行select语句,
//查询数据库(需要用的时候查数据库)
c.getId();
c.getAge();
tx.commit();
session.close();
}
-相应的数据:除OID之外的值,关联数据。
-延迟加载在开发中,主要好处,延缓数据加载,在使用时才进行加载 (缩短数据在内存中时间)
-理解延迟检索中的代理
3.2检索类型
* 类级别检索:当前对象所有属性值。例如:Customer自己数据
Customer c=(Customer)session.load(Customer.class, 1);
session的方法直接检索Customer对象,对Customer对象到底采用立即检索 还是延迟检索方式
通过class元素的lazy属性设定
*关联级别检索:当前对象关联对象数据。例如:Customer 关联 Order 数据
Set set=c.getOrders()//检索Order对象的set集合
session.load(Customer.class, 1):查询的主体表
c.getOrders().size():查询客体表的集合大小
通过set元素lazy属性设定
3.3类级别检索策略
*类级别:查询当前类的所有内容,只查询一次。优化指的是查询时机优化,让空闲时间服务器做出其他处理。
*session.get(Customer.class ,oid) 通过OID立即检索(查询),如果数据不存在返回null。
*session.load(Customer.class , oid ) 默认通过OID延迟检索,如果数据不存在将抛异常。
类级别配置,只对load方法有效。
Customer.hbm.xml <class name="" table=""lazy="true|false">
true:默认值,延迟检索
false:立即检索。
*无论 <class>元素的 lazy 属性是 true 还是 false,
-Session 的 get() 方法及 Query 的 list() 方法在类级别总是使用立即检索策略
*若 <class> 元素的 lazy 属性为 true 或取默认值,
-Session 的 load() 方法不会执行查询数据表的 SELECT 语句, 仅返回代理类对象的实例, 该代理类实例有如下特征:
>由 Hibernate 在运行时采用 javassist 工具动态生成
>Hibernate创建代理类实例时, 仅初始化其 OID 属性
>在应用程序第一次访问代理类实例的非 OID 属性时, Hibernate 会初始化代理类实例
*应用:
-如果程序加载一个对象的目的是为了访问它的属性, 可以采取立即检索.
-如果程序加载一个持久化对象的目的是仅仅为了获得它的引用, 可以采用延迟检索
3.4一对多和多对多关联的检索策略
*共同特点都有集合<set> 通过集合进行配置。
*通过 fetch 和 lazy 属性 进行配置
*lazy: (确定什么时候执行sql语句。)
-主要决定 orders 集合被初始化的时机.
-即到底是在加载 Customer 对象时就被初始化,
-还是在程序访问 orders 集合时被初始化
*fetch:(确定hibernate 生成 sql样子 )
-取值为 “select” 或 “subselect” 时, 决定初始化 orders 的查询语句的形式;
-若取值为”join”, 则决定 orders 集合被初始化的时机
-若把 fetch 设置为 “join”, lazy 属性将被忽略
*对比:
fetch="join",lazy无效,hibernate 将使用“迫切左外连接”,底层执行sql语句就是“左外连接”
只执行一条select,将当前对象及关联对象数据一次性查询出来。
fetch="select",默认值,执行多条select语句
lazy="false"立即执行,在执行get方法时,立即执行多条select语句。
lazy="true"延迟执行,在执行get方法时先查询客户Customer,直到使用Order数据时才查询Order
lazy="extra"极其懒惰,在执行get方法时先查询客户Customer(select t_customer),
如果需要order 订单数,将执行聚合函数只查询数量(select count()t_order),
如果需要详情再查询详情(select t_order))。
fetch="subselect",采用子查询
lazy取值与 fetch="select"相同。
注意:必须使用Query进行查询。
↑测试(子查询使用query测试)
*延迟检索和增强延迟检索
-在延迟检索(lazy 属性值为 true) 集合属性时, Hibernate 在以下情况下初始化集合代理类实例
>应用程序第一次访问集合属性: iterator(), size(), isEmpty(), contains() 等方法
>通过 Hibernate.initialize() 静态方法显式初始化
-增强延迟检索(lazy 属性为 extra): 与 lazy=“true” 类似.
主要区别是增强延迟检索策略能进一步延迟 Customer 对象的 orders 集合代理实例的初始化时机:
>当程序第一次访问 orders 属性的 iterator() 方法时, 会导致 orders 集合代理类实例的初始化
>当程序第一次访问 order 属性的 size(), contains() 和 isEmpty() 方法时, Hibernate 不会初始化 orders 集合类的实例, 仅通过特定的 select 语句查询必要的信息, 不会检索所有的 Order 对象
*用带子查询的 select 语句整批量初始化 orders 集合(fetch属性为“subselect”)
<set> 元素的 fetch 属性: 取值为“select”或“subselect”时, 决定初始化 orders 的查询语句的形式;
--若取值为”join”, 则决定 orders 集合被初始化的时机.默认值为 select
--当 fetch 属性为 “subselect” 时
>假定 Session 缓存中有 n 个 orders 集合代理类实例没有被初始化,
Hibernate 能够通过带子查询的 select 语句, 来批量初始化 n 个 orders 集合代理类实例
*迫切左外连接检索(fetch 属性值设为 “join”)
-当 fetch 属性为 “join” 时:
>检索 Customer 对象时, 会采用迫切左外连接(通过左外连接加载与检索指定的对象关联的对象)策略来检索所有关联的 Order 对象
>lazy 属性将被忽略
>Query 的list() 方法(因为hql是自己指定的)会忽略映射文件中配置的迫切左外连接检索策略(fetch=join无效), 而依旧采用立即检索还是延迟加载策略由set集合的lazy属性决定(这时候lazy重新启动应用)
3.5多对一和一对一关联的检索策略
*共同特点拥有一方(<many-to-one> /<one-to-one>)
-和 <set> 一样,<many-to-one> 元素也有一个lazy 属性和 fetch 属性.
•若fetch 属性设为join, 那么lazy 属性被忽略
•迫切左外连接检索策略的优点在于比立即检索策略使用的 SELECT 语句更少.
•无代理延迟检索需要增强持久化类的字节码才能实现
*Query 的list() 方法(因为hql是自己指定的)会忽略映射文件中配置的迫切左外连接检索策略(fetch=join无效), 而依旧采用立即检索还是延迟加载策略由set集合的lazy属性决定(这时候lazy重新启动应用)
*如果在关联级别使用了延迟加载或立即加载检索策略, 可以设定批量检索的大小,以帮助提高延迟检索或立即检索的运行性能
\
fetch="join" , lazy 无效,hibernate使用“迫切左外连接”,一次性查询订单和关联客户
fetch="select" 查询多条select语句
lazy="false"立即 ,执行两条select语句
lazy="proxy"代理,order关联客户Customer,客户是否延迟,取决客户类级别检查策略。
Customer.hbm.xml<class lazy="false"> 立即客户
Customer.hbm.xml<class lazy="true"> 延迟客户
lazy="no-proxy"不研究
--------------------------------------------------------------------
批量检索
问题:什么叫n+1问题?
有五个客户,要查询出它的订单,我们正常执行会产生6条sql语句。这就是n+1问题.
解决方案:使用in来解决。
在hibernate中可以合适batch-size(批量检索)来解决。
*批量检索 从一的一端查询 查询所有的客户
-<set> 元素有一个 batch-size 属性, 用来为延迟检索策略或立即检索策略设定批量检索的数量.
批量检索能减少 SELECT 语句的数目, 提高延迟检索或立即检索的运行性能. 默认值是1
注:query.list()属于hql检索,hql检索忽略关联级别的迫切左外连接检索,只与lazy属性有关.
配置
检索方式总结
1、检索方式
1.使用OID获取
>session.get(User.class,1);立即加载如果不存在,就返回null
>session.load(User.class,2); 延迟加载 如果不存在,就抛异常
PS:使用以上两个方法进行查询,结果都为持久态对象,持久对象就在一级缓存中。
2.导航对象图检索方式
>customer.getOrderSet().size()
3.使用HQL
>session.createQuery("FROMEmployee e WHERE e.id>? ORDER BY e.id DESC").list();
4.原生的SQL方式
>session.createSQLQuery(Stringsql);
5.QBC 检索方式: 使用 QBC(QueryBy Criteria) API 来检索对象.
Query By Criteria提供 纯面向对象查询语句
2、 HQL 是 Hibernate最常用检索方式
*与SQL语法基本一致,不同的是HQL是面象对象的查询,查询的是对象和对象中的属性
*HQL的关键字不区分大小写,但类名和属性名区分大小写
支持 所有 SQL支持检索方式
步骤 :
1) 获得Session
2) 编写HQL
3) 通过 session.createQuery(hql) 创建Query对象
4) 为Query对象 设置条件参数
5) 执行查询 list() ---- 返回一个集合列表 、 uniqueResult();--- 返回一个查询结果
* Qurey 接口支持方法链编程风格 ,将上面所有步骤写入一句程序代码中
3、 编写测试用例,创建初始数据
创建4个Customer , 每个Customer 创建 10个 Order
4、 简单查询, hibernate 企业开发主流查询 HQL 和 QBC
* 查询所有数据
//HQL
String hql = "from Customer";
Queryquery = session.createQuery(hql);
List<Customer>list = query.list();
System.out.println(list);
//QBC
Criteriacriteria = session.createCriteria(Customer.class);
List<Customer>list2 = criteria.list();
System.out.println(list2);
*HQL 和 QBC 都支持链式编程写法
List<Customer>list3 = session.createQuery("from Customer").list();
5、 本地SQL 检索
*编写及其复杂的查询,企业内部大多使用 SQL语句
//内连接 写法一 : select *from A inner join B on A.id = B.A_id;
//内连接 写法二 (隐式): select* from A,B where A.id = B.A_id ;
String sql = "select * from customers
,orders where customers.id =orders.customer_id and customers.name = ?";
SQLQuerysqlQuery = session.createSQLQuery(sql);
//设置参数
sqlQuery.setParameter(0,"mary");
Listlist = sqlQuery.list();
System.out.println(list);
*当返回很多列时,默认将每条记录保存在 Object[]中, 返回 List<Object[]>
*将返回结果 与实体类 绑定,将每条数据 转换实体类对象
String sql = "select orders.* from customers
,orders where customers.id = orders.customer_id and customers.name = ?";
sqlQuery.addEntity(Order.class);
6、 编写HQL时,通过as关键字 为类 起别名
from Customer as c where c.name=:custname
原来写法: from Customer wherename=:custname
使用别名时 ,as可以省略 fromCustomer c where c.name=:custname
* 别名主要使用关联复杂查询时
7、 多态查询
hibernate 检索一个类 对应数据时, 将类所有子类(PO类) 对应数据表记录返回
session.createQuery("fromjava.lang.Object").list();
* 将Object 所有子类 对应数据表的 数据查询返回
from 关键字后面,如果PO类,省略包名, 如果不是PO类,必须写完整包名类名
8、 查询结果排序
//HQL
String hql = "from Customer order by name asc";
Listlist = session.createQuery(hql).list();
System.out.println(list);
//QBC
Listlist2 =session.createCriteria(Customer.class)
.addOrder(org.hibernate.criterion.Order.asc("name")).list();
System.out.println(list2);
9、 分页查询
Query 接口和 Criteria 接口 都提供 setFirstResult 、setMaxResults 两个方法,用于分页查询
* setFirstResult 起始记录索引,第一条数据 索引 0
* setMaxResults 查询返回记录条数
案例:
//分页查询,返回 25-34条订单
//HQL
String hql = "from Order";
Queryquery = session.createQuery(hql);
//设置分页参数
query.setFirstResult(24);// 索引 是 起始记录 -1
query.setMaxResults(10);
10、 检索单一对象 query.uniqueResult() 、 criteria.uniqueResult()
* 该方法主要用于,只有1条数据结果
* 什么情况只有一条结果 : 用户登录、使用聚集函数 sum、count、avg、max、min
//查询mary的信息
Customercustomer = (Customer) session
.createQuery("from Customer where name ='mary'").uniqueResult();
System.out.println(customer);
//使用聚集函数 -- 查询客户最大年龄
Integerage = (Integer) session
.createQuery("select max(id) fromCustomer").uniqueResult();
System.out.println(age);
如果查询结果 只有一条记录或者 无记录,使用uniqueResult 是没问题的, 但是如果查询结果大于 一条记录,报错
org.hibernate.NonUniqueResultException:query did not return a unique result: 4
===============================================================================================================================
11、 带有参数条件的查询 (重点)
1) 单表条件查询
HQL写法:
Customer customer1 = (Customer)session.createQuery("from Customer where name = ?")
.setParameter(0,"tom").uniqueResult();
Customer customer2 = (Customer)session.createQuery("from Customer where name =:cname")
.setParameter("cname", "tom").uniqueResult();
QBC写法:
Customer customer3 = (Customer)session.createCriteria(Customer.class)
.add(Restrictions.eq("name","tom")).uniqueResult();
*Restrictions 用来添加查询条件 ,面向对象条件查询
将参数绑定到一个持久化对象
Customer customer = new Customer();
customer.setId(1);
Listlist2 = session.createQuery("from Order where customer =?")
.setEntity(0, customer).list(); // 通过customer_id 查询
* 简化为
List list = session.createQuery("from Order where customer.id =?")
.setParameter(0, 1).list();
* setEntity 关联对象 ,必须要有OID ,否则会报错
使用QBC 为参数绑定 持久化对象
Listlist3 =session.createCriteria(Order.class)
.add(Restrictions.eq("customer",customer)).list(); // 通过customer_id 查询
2) 多表条件查询
hibernate HQL 支持7种 连接写法
*(SQL标准)内连接 inner join 可以省略 inner,直接 join
*迫切内连接 inner joinfetch ------ 不是SQL写法,是hibernate 提供
*隐式内连接 不写任何关键字,完成表关联
*(SQL标准)左外连接 left outer join ,可以省略 outer ,直接 left join
*迫切左外连接 left outerjoin fetch ----- 不是SQL语法
*(SQL标准)右外连接 right outer join
*(SQL标准)交叉连接 (笛卡尔积 )
问题: 区分内连接和迫切内连接,左外连接和迫切左外连接
*左外连接
List list =session.createQuery( "from Customer c left outer joinc.orders").list();
返回 List<Object[]>
每个数组两个元素 ,一个Customer 一个Order
*迫切左外连接
List list =session
.createQuery("select distinct c from Customer c left outer joinfetch c.orders").list();
返回 List<Customer> 保存所有Customer对象,需要distinct 排重重复
问题:多表关联条件查询,隐式内连接 和 QBC方式
//隐式内连接 o.customer 当做Customer类数据表
List<Order>list = session
.createQuery("from Order o where o.customer.name =?")
.setParameter(0, "mary").list();
//QBC 连接查询,必须使用 criteria.createAlias()
Criteriacriteria = session.createCriteria(Order.class);
criteria.createAlias("customer","c"); // 为订单关联Customer属性起了别名
criteria.add(Restrictions.eq("c.name","mary"));
Listlist2 = criteria.list();
* 在 createAlias 默认使用 inner join 内连接
criteria.createAlias("customer","c", Criteria.LEFT_JOIN); 在关联时使用左外连接
12 、投影查询
查询结果仅包含实体的部分属性
* 查询Customer的所有 name,age 属性
HQL方式
session.createQuery("select name,age from Customer");
返回 List<Object[]>
*将结果保存Customer对象中,提供name和age 构造方法
session.createQuery("select new Customer(name,age) from Customer");
返回 List<Customer>
可以将查询结果 保存List 或者 Map集合
*select new list(name,age) from Customer
*select new map(name,age) from Customer
QBC方式 (开发中不用,非常麻烦)
Listlist3 = session
.createCriteria(Customer.class)
.setProjection(
Projections.projectionList()
.add(Property.forName("name"))
.add(Property.forName("age"))).list();
13、 分组统计查询
count sum avg max min
*Long count = (Long) session.createQuery("select count(*) fromOrder").uniqueResult();
*List list2 = session.createQuery("select count(*) from Order group bycustomer").list();
14、 命名查询语句
在开发中 hql语句 写到代码中,不方便维护, 将HQL定义到配置文件中 ------------ 命名查询语句
在hbm映射文件 (也可以用注解配置)
<!--这里可以定义命名查询 -->
<!-- 定义 HQL 语句 <query name=""></query> -->
<!-- 定义 SQL 语句 <sql-queryname=""></sql-query> -->
<query name="findCustomerByName">
<![CDATA[from Customer where name = ?]]>
</query>
* 为hql语句 起了一个名字
程序代码
Query query = session.getNamedQuery("findCustomerByName");
query.setParameter(0,"tom");
Customer customer = (Customer) query.uniqueResult();
15、 离线Criteria对象 --- DetachedCriteria
*主要用于javaee分层开发,可以在web层封装查询条件,传递数据层 关联Session进行查询
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Customer.class);
detachedCriteria.add(Restrictions.eq("name","kitty"));
//传递数据层
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//将离线查询对象 关联到Session
Criteria criteria = detachedCriteria.getExecutableCriteria(session);
Customer customer = (Customer) criteria.uniqueResult();
HQL和QBC比较: 两种查询功能类似, 简单查询建议编写HQL,对于复杂查询,多条件组合查询 建议编写 QBC方式
============================================================================================================================