一、Hibernate检索
1、Hibernate提供五种检索数据的方式
1)导航对象图检索方式:根据已加载的对象导航到其他对象。
Customer c = (Customer)session.get(Customer.class, 1); // 持久态对象
c.getOrders().size(); // c 对象关联 order 集合 ,hibernate 会自动检索 order数据
2)OID检索方式:根据对象的OID来检索对象。
session.get/session.load
3)HQL检索方式:使用面向对象的HQL查询语言
session.createQuery(hql);
4)QBC检索方式:使用QBC(Query By Criteria)API来检索对象,这种API封装了基于字符串形式的查询语句,提供了更加面向对象的查询接口。
session.createCriteria(Xxx.class);
5)本地SQL检索方式:使用本地数据库的SQL查询语句。
session.createSQLQuery(sql);
2、HQL是Hibernate最常用检索方式。
支持所有SQL支持检索方式。
步骤:
1)获得Session
2)编写HQL
3)通过session.createQuery(hql)创建Query对象
4)为Query对象设置条件参数
5)执行查询list()---返回一个集合列表、uniqueResult();---返回一个查询结果
*Query接口支持方法链编程风格,将上面所有步骤写入一句程序代码中。
3、编写测试用例,创建初始数据
创建4个Customer,每个Customer 创建 10个 Order
package lsq.hibernate.domain;
import java.util.HashSet;
import java.util.Set;
public class Customer {
private Integer id;
private String name;
private int age;
// 关联多个order
private Set<Order> orders = new HashSet<Order>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
}
package lsq.hibernate.domain;
public class Order {
private Integer id;
private String address;
private Double money;
// 关联一个客户
private Customer customer;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
Order.hbm.xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="lsq.hibernate.domain.Order" table="orders" catalog="hibernate3day3">
<id name="id">
<generator class="identity"></generator>
</id>
<property name="address"></property>
<property name="money"></property>
<many-to-one name="customer" class="lsq.hibernate.domain.Customer" column="customer_id" ></many-to-one>
</class>
</hibernate-mapping>
Customer.hbm.xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="lsq.hibernate.domain.Customer" table="customers" catalog="hibernate3day3">
<id name="id">
<generator class="identity"></generator>
</id>
<property name="name"></property>
<property name="age"></property>
<!-- 由一方控制级联,将外键维护权交给多方 -->
<set name="orders" cascade="all-delete-orphan" inverse="true">
<key column="customer_id"></key>
<one-to-many class="lsq.hibernate.domain.Order"/>
</set>
</class>
</hibernate-mapping>
hibernate.cfg.xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- JDBC基本连接参数 -->
<session-factory> <!-- 理解为连接池 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernate3day3</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<!-- 配置方言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 常见其它配置 -->
<property name="hibernate.show_sql">true</property> <!-- 控制台上打印SQL -->
<property name="hibernate.format_sql">true</property> <!-- 控制台输出时,对SQL语句格式化 -->
<!-- 测试环境 create/ create-drop 正式环境 update validate -->
<property name="hibernate.hbm2ddl.auto">update</property> <!-- 自动建表 -->
<property name="hibernate.connection.autocommit">true</property>
<!-- 在核心配置文件中 引用 mapping 映射文件 -->
<mapping resource="lsq/hibernate/domain/Customer.hbm.xml"/>
<mapping resource="lsq/hibernate/domain/Order.hbm.xml"/>
</session-factory>
</hibernate-configuration>
package lsq.hibernate.domain;
import lsq.hibernate.utils.HibernateUtils;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
public class HibernateTest {
@Test
// 初始化数据
// 插入4个Customer ,每个Customer 10个订单
public void demo1() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setName("Durant");
customer.setAge(29);
for (int i = 1; i <= 10; i++) {
Order order = new Order();
order.setAddress(customer.getName() + "_addr" + i);
order.setMoney(i * 100d);
// 订单关联客户 使订单维护外键
order.setCustomer(customer);
// 客户关联订单, 为了级联保存订单
customer.getOrders().add(order);
}
session.save(customer);
transaction.commit();
session.close();
}
}
4、简单查询:Hibernate企业开发主流查询HQL和QBC
查询所有数据:
@Test
public void demo2(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//HQL
String hql = "from Customer";
Query query = session.createQuery(hql);
List<Customer> list = query.list();
System.out.println(list);
//QBC
Criteria criteria = session.createCriteria(Customer.class);
List<Customer> list2 = criteria.list();
System.out.println(list2);
//HQL链式编程写法
List<Customer> list3 = session.createQuery("from Customer").list();
System.out.println(list3);
transaction.commit();
session.close();
}
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 ;
* 当返回很多列时,默认将每条记录保存在 Object[]中, 返回 List<Object[]>
@Test
public void demo3(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
String sql = "select * from customers , orders where customers.id = orders.customer_id and customers.name = ?";
SQLQuery sqlQuery = session.createSQLQuery(sql);
//设置参数
sqlQuery.setParameter(0, "Thompson");
List list = sqlQuery.list();
System.out.println(list);
transaction.commit();
session.close();
}
* 将返回结果 与实体类 绑定,将每条数据 转换实体类对象
@Test
public void demo3(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
String sql = "select orders.* from customers , orders where customers.id = orders.customer_id and customers.name = ?";
SQLQuery sqlQuery = session.createSQLQuery(sql);
//设置参数
sqlQuery.setParameter(0, "Thompson");
sqlQuery.addEntity(Order.class);
List list = sqlQuery.list();
System.out.println(list);
transaction.commit();
session.close();
}
6、多态查询
Hibernate检索一个类对应数据时,会将该类所有子类(PO类)对应数据表记录返回。
*from关键字后面,如果是PO类,可以省略包名,如果不是PO类,必须写完整包名类名。
@Test
//多态查询
public void demo4(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
List<Order> orders = session.createQuery("from Order").list();
System.out.println(orders.size());
//Object不是PO类,查询Object返回所有Object子类的对应表数据
List list = session.createQuery("from java.lang.Object").list();
System.out.println(list.size());
transaction.commit();
session.close();
}
7、查询结果排序
@Test
//查询结果排序
public void demo5(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//查询所有客户数据,按照name升序
//HQL
List<Customer> list = session.createQuery("from Customer order by name asc").list();
System.out.println(list);
//QBC
List<Customer> list2 = session.createCriteria(Customer.class).addOrder(org.hibernate.criterion.Order.asc("name")).list();
System.out.println(list2);
transaction.commit();
session.close();
}
8、分页查询
Query接口和Criteria接口都提供setFirstResult()、setMaxResults()两个方法,用于分页查询。
*setFirstResult():起始记录索引,第一条记录索引为0。
*setMaxResults():查询返回记录条数。
@Test
//分页查询
public void demo6(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//分页查询,返回25~34条订单
//HQL
String hql = "from Order";
Query query = session.createQuery(hql);
//设置分页参数
query.setFirstResult(24);//索引=起始记录-1
query.setMaxResults(10);
List list = query.list();
System.out.println(list);
transaction.commit();
session.close();
}
query.uniqueResult()、criteria.uniqueResult()
*该方法主要用于只有一条返回结果的查询
*什么情况下只有一条结果返回?用户登录、使用聚集函数(sum、count、avg、max、min)
☆:如果查询结果小于等于一条结果,使用uniqueResult没有问题,但是如果查询结果大于一条记录,会报如下错误:
org.hibernate.NonUniqueResultException: query did not return a unique result:
@Test
//检索单一对象 uniqueResult
public void demo7(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//查询Curry的信息
Customer customer = (Customer) session.createQuery("from Customer where name = 'Curry'").uniqueResult();
System.out.println(customer);
//使用聚集函数(查询客户的最大年龄)
Integer age = (Integer) session.createQuery("select max(age) from Customer").uniqueResult();
System.out.println(age);
transaction.commit();
session.close();
}
10、带有条件参数的查询
1)单表条件查询
* Restrictions 用来添加查询条件 ,面向对象条件查询
@Test
//单表条件查询
public void demo8(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//查询姓名为Green的客户信息
//HQL
Customer customer1 = (Customer) session.createQuery("from Customer where name = ?").setParameter(0, "Green").uniqueResult();
System.out.println(customer1);
Customer customer2 = (Customer) session.createQuery("from Customer where name = :cname").setParameter("cname", "Green").uniqueResult();
System.out.println(customer2);
//QBC
Customer customer3 = (Customer) session.createCriteria(Customer.class).add(Restrictions.eq("name", "Green")).uniqueResult();
System.out.println(customer3);
transaction.commit();
session.close();
}
*参数是一个对象的单表查询:
☆:当条件写成customer时,只能通过setId()来构造查询条件,而不能用setName(),因为* setEntity 关联对象 ,必须要有OID ,否则会报错
其中:List list = session.createQuery("from Order where customer.id = ?").setParameter(0, 1).list();
是以下几行的简化:
Customer customer = new Customer();
customer.setId(1);
List list2 = session.createQuery("from Order where customer = ?").setEntity(0, customer).list(); // 通过customer_id 查询
@Test
//单表查询,参数是一个对象
public void demo9(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//查询1号客户的所有订单
//HQL
List list = session.createQuery("from Order where customer.id = ?").setParameter(0, 1).list();
System.out.println(list);
//当条件写成customer,通过customer_id查询
Customer customer = new Customer();
customer.setId(1);
List list2 = session.createQuery("from Order where customer = ?").setEntity(0, customer).list();
System.out.println(list2);
//QBC
List list3 = session.createCriteria(Order.class).add(Restrictions.eq("customer", customer)).list();
System.out.println(list3);
transaction.commit();
session.close();
}
Hibernate HQL支持7种连接写法:
* (SQL标准)内连接 inner join 可以省略 inner,直接 join
* 迫切内连接 inner join fetch ------ 不是SQL写法,是hibernate 提供
* 隐式内连接 不写任何关键字,完成表关联
* (SQL标准)左外连接 left outer join ,可以省略 outer ,直接 left join
* 迫切左外连接 left outer join fetch ----- 不是SQL语法
* (SQL标准)右外连接 right outer join
* (SQL标准)交叉连接 (笛卡尔积 )
问题一:区分内连接和迫切内连接,左外连接和迫切左外连接
@Test
//区分左外连接和迫切左外连接
public void demo10(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//左外连接
//Customer c 对应Customer表
//c.orders对应 Order表
//使用Hibernate不需要些on
//List list = session.createQuery("from Customer c left outer join c.orders").list();
//迫切左外连接
//List list = session.createQuery("from Customer c left outer join fetch c.orders").list();
List list = session.createQuery("select distinct c from Customer c left outer join fetch c.orders").list();
System.out.println(list);
transaction.commit();
session.close();
}
☆:左外连接查询结果,默认返回Object数组,每个数组两个元素 ,一个Customer 一个Order
☆:迫切左外连接,默认返回Customer对象和其对应的Order信息,包含重复内容
List list = session.createQuery("from Customer c left outer join fetch c.orders").list();
☆:为了去除迫切左外连接中的重复内容,需要distinct 排重重复,我们修改HQL语句如下:
List list = session.createQuery("select distinct c from Customer c left outer join fetch c.orders").list();
问题二:多表关联条件查询(隐式内连接和QBC方式)
☆:QBC 连接查询,必须使用 criteria.createAlias()
* 在 createAlias 默认使用 inner join 内连接
criteria.createAlias("customer", "c", Criteria.LEFT_JOIN); 在关联时使用左外连接
@Test
//多表条件查询(重要)
public void demo11(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//查询Curry的所有订单
//隐式内连接
List<Order> list = session.createQuery("from Order o where o.customer.name = ?").setParameter(0, "Curry").list();
System.out.println(list);
//QBC连接查询
Criteria criteria = session.createCriteria(Order.class);
criteria.createAlias("customer", "c");
criteria.add(Restrictions.eq("c.name", "Curry"));
List<Order> list2 = criteria.list();
System.out.println(list2);
transaction.commit();
session.close();
}
11、投影查询
查询结果仅包含实体的部分属性,通过select关键字实现。
Query的list()方法返回的集合中包含的是数组类型的元素,每个对象数组代表查询结果的一条记录。
可以在持久化类中定义一个对象的构造器来包装投影查询返回的记录,使程序代码能完全运用面向对象的语义来访问查询结果集。
可以通过DISTINCT关键字来保证查询结果不会返回重复元素。
@Test
//投影查询
public void demo12(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//HQL
Query query = session.createQuery("select name,age from Customer");
//投影查询,如果返回一列,List<Object>,如果返回多列,List<Object[]>
List list = query.list();
System.out.println(list);
//HQL将投影结果转换到对象中
List list2 = session.createQuery("select new Customer(name,age) from Customer").list();
System.out.println(list2);
transaction.commit();
session.close();
}
查询结果:
12、命名查询
命名查询语句:在开发中,HQL语句写在代码中不方便维护,可以将HQL定义到配置文件中。
在Customer.hbm.xml中配置:
@Test
//命名查询
public void demo13(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Query query = session.getNamedQuery("findCustomerByName");
query.setParameter(0, "Durant");
Customer customer = (Customer) query.uniqueResult();
System.out.println(customer);
transaction.commit();
session.close();
}
☆:主要用于javaee分层开发,可以在web层封装查询条件,传递到数据层,然后再关联Session进行查询。
@Test
public void demo14(){
//封装查询条件
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Customer.class);
detachedCriteria.add(Restrictions.eq("name", "Thompson"));
//传递到数据层关联Session查询
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Criteria criteria = detachedCriteria.getExecutableCriteria(session);
Customer customer = (Customer) criteria.uniqueResult();
System.out.println(customer);
transaction.commit();
session.close();
}
二、Hibernate进行数据检索时的抓取策略
1、区分立即检索和延迟检索
立即检索(get):立即加载检索方法指定的对象。
延迟检索(load):延迟加载检索方法指定的对象。
☆为什么要使用延迟检索?
延迟加载的主要好处:延缓数据加载,在使用时才进行加载,可以缩短数据在内存中时间。
2、load方法
在load方法之后,返回的是Customer类的代理对象,在代理对象内部有一个handler,整个延迟加载策略由handler来完成,最初handler中的initialized=false,target = null; 当我们去访问当中数据时,才会去执行查询。此时,initialized=true,target=Customer。
☆:load方法底层原理:
Hibernate返回代理对象由javassist-3.12.0.GA.jar提供工具类负责创建,Javassist是一个开源的分析、编辑和创建Java字节码的类库。
3、区别类级别检索和关联级别检索
类级别检索,通过session直接检索 某一个类 对应数据表数据
session.load(Customer.class , 1) ; 类级别
session.createQuery("from Order"); 类级别
关联级别检索,程序内部已经获得持久对象,通过对象引用关系,进行数据检索
Customer c = session.load(Customer.class , 1) ; 类级别
c.getOrders().size() ; 关联级别检索
order.getCustomer().getName() ; 关联级别检索
4、类级别检索策略(抓取策略)
*类级别可选的检索策略包括立即检索和延迟检索, 默认为延迟检索 (针对load方法 )
* 类级别的检索策略可以通过 <class> 元素的 lazy 属性进行设置
类级别检索get、Query默认使用立即检索策略;load默认使用延迟检索策略,在hbm文件<class>中配置lazy=‘false’使类级别检索变为立即检索。
*lazy=false之后,load方法效果和get方法相同,是立即检索。
5、关联级别检索策略
1)多对多和一对多情况下<set>元素中配置抓取策略(关联集合)
<set>元素提供了fetch和lazy两个属性来决定检索策略。
*fetch属性(select、subselect、join),主要决定SQL语句生成格式。
*lazy属性(false、true、extra),主要决定集合被初始化的时机。
fetch和lazy共有9种组合:
fetch属性为join,lazy属性会被忽略,生成SQL将采用迫切左外连接(left outer join fetch),*SQL语句左外连接,采用立即检索。
fetch属性为select,将生成多条简单SQL查询
lazy = false 立即检索;
lazy = true 延迟检索;
lazy = extra 加强延迟检索(极其懒惰,比延迟更加延迟)
fetch属性为subselect,将生成子查询的SQL语句
lazy = false 立即检索;
lazy = true 延迟检索;
lazy = extra 加强延迟检索 (极其懒惰,比延迟更加延迟)
**** lazy=false 立即检索,检索类级别数据时,关联级别数据也进行检索
lazy=true 延迟检索,检索类级别数据时,不会检索关联级别的数据,用到了再进行检索
lazy="extra" 及其懒惰,当程序第一次访问 order 属性的 size(), contains() 和 isEmpty() 方法时, Hibernate 不会初始化 orders 集合类的实例 ,例如 查询size时,生成select count(*)
**** 因为fetch=join , session.get... 生成迫切左外连接查询
使用Query对象查询数据时,需要自己编写hql语句, fetch=join 无效果,关联集合将根据lazy 设置进行 加载
结论 : session.load/ session.get , fetch=join 生成迫切左外连接, lazy被忽略
session.createQuery(hql).list() 将忽略 fetch=join, lazy 将重新产生效果
2) 多对一 和 一对一 情况下 <many-to-one> 或者 <one-to-one> 配置抓取策略 ---- 关联单一对象
<many-to-one> 元素也有一个 lazy 属性和 fetch 属性
fetch 决定SQL语句格式, lazy决定数据加载时间
fetch取值 join、 select
lazy取值 false、 proxy、no-proxy
fetch 和 lazy 共有4种组合
fetch 属性为 join, lazy属性会被忽略, 生成SQL将采用迫切左外连接查询 (left outer join fetch )
fetch 属性为 select, 产生多条SQL
lazy=false 立即检索
lazy=proxy 有关联对象类级别检索策略决定立即检索 或者 延迟检索
* 由Customer.hbm.xml <class name="cn.itcast.domain.Customer" lazy="??"> lazy决定立即检索 还是 延迟检索
**** Query的list 会忽略 fetch="join", lazy重新起作用
结论: 开发中能延迟都延迟,必须立即的 才立即的
6、 批量检索的使用
批量检索 可以解决 N+1 查询问题
1) Customer 一方设置批量检索
<set> 元素有一个 batch-size 属性 ,设置批量检索数量
N+1问题:查询每个客户订单数,一次查询所有客户, 每个客户订单数产生单独SQL查询,如果有n个客户,产生n条SQL
解决: Customer.hbm.xml 设置<set>元素 batch-size 一次查询多个用户订单数
* <set name="orders" batch-size="3">
2) Order多方 设置批量检索
查询订单和客户,产生多余SQL
配置批量检索,一次多查询几个客户数据
解决: Customer.hbm.xml 在 <class name="cn.itcast.domain.Customer" batch-size="3">