Hibernate3 第三天 html
第一天:三个准备、七个步骤java
次日:一级缓存、快照、多对多和一对多的配置mysql
学习内容:程序员
1)条件查询分类(对象导航检索)。web
2)HQL\SQL\QBC的各类查询(基础查询、条件查询、排序、分页、投影查询、统计分组、命名查询、离线查询等)。算法
1)延迟抓取和当即抓取策略 sql
类级别的抓取策略 数据库
关联集合级别的抓取策略 编程
2)批量抓取策略 数组
学习目标:
建立Hibernate项目:
构建Hibernate环境:导入jar包、hibernate.cfg.xml、log4j.properties、util工具类。
建立包:cn.itcast.a_onetomany,配置一对多的实体类和hbm映射文件的编写:
实体类(Customer):
package cn.itcast.a_onetomany;
import java.util.HashSet; import java.util.Set;
public class Customer {
private Integer id; private String name; private String city;
//集合 //set:无需不重复 //也能够用list:有序重复 //配置hbm.xml的时候,若是类中用的是list集合的话,那边hbm中也可使用<bag>标签配置集合 //<bag>:有序不重复,可是效率低下 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 String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public Set<Order> getOrders() { return orders; }
public void setOrders(Set<Order> orders) { this.orders = orders; }
@Override public String toString() { return "Customer [id=" + id + ", name=" + name + ", city=" + city + "]"; } } |
实体类(Order):
package cn.itcast.a_onetomany;
public class Order { private Integer id; private String name; private Double price;
private Customer customer ;
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 Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }
public Customer getCustomer() { return customer; }
public void setCustomer(Customer customer) { this.customer = customer; }
@Override public String toString() { return "Order [id=" + id + ", name=" + name + ", price=" + price + "]"; } } |
hbm映射文件:
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> <!-- 配置java类与表之间的对应关系 --> <!-- name:类名:类对应的完整的包路径 table:表名 --> <class name="cn.itcast.a_onetomany.Customer" table="t_customer"> <!-- 主键 --> <id name="id"> <generator class="native"></generator> </id> <property name="name"></property> <property name="city"></property>
<set name="orders"> <key column="cid"></key> <one-to-many class="cn.itcast.a_onetomany.Order"/> </set>
</class> </hibernate-mapping>
|
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> <!-- 配置java类与表之间的对应关系 --> <!-- name:类名:类对应的完整的包路径 table:表名 --> <class name="cn.itcast.a_onetomany.Order" table="t_order"> <id name="id"> <generator class="native"></generator> </id> <property name="name"></property> <property name="price"></property>
<many-to-one name="customer" class="cn.itcast.a_onetomany.Customer" column="cid"></many-to-one> </class> </hibernate-mapping>
|
核心配置文件中引入HBM映射配置:
<!-- 在核心配置文件中 引用 mapping 映射文件 --> <mapping resource="cn/itcast/a_onetomany/Customer.hbm.xml"/> <mapping resource="cn/itcast/a_onetomany/Order.hbm.xml"/> |
建表测试是否配置成功:
@Test public void createTable(){ HibernateUtils.getSessionFactory(); } |
批量插入3个客户和相应的订单(共30个)
@Test @Test public void prepareData() { Session session = HibernateUtils.openSession(); session.beginTransaction();
//一个客户对应多个订单,一个客户对应10个订单 Customer customer = new Customer(); customer.setName("jack"); customer.setCity("北京");
session.save(customer);
for(int i=1;i<=10;i++) { Order o = new Order(); o.setName(customer.getName()+"的订单"+i); o.setPrice(i*10d);
o.setCustomer(customer); session.save(o); }
session.getTransaction().commit(); session.close(); } |
【扩展】
问题:若是你在大批量的插入数据的时候,可能会报内存溢出的错误!
缘由:当save操做的时候,会将瞬时态转换为持久态,对象都放在了session的一级缓存中,若是超大量的数据,会撑爆一级缓存,致使内存溢出。
解决方案:
// 批插入的对象当即写入数据库并释放内存
if(i%10000==0){ //刷出到数据库 session.flush(); //清空一级缓存,释放内存 session.clear(); } |
【提示:】
若是真的有大批量(几十万,上百万,上千万)的操做,其实,不太建议用hibernate,直接用jdbc(stmt. executeBatch())
Hibernate是经过检索对象来查询数据的,下面咱们了解一下,Hibernate提供的几种检索对象的方式:
彻底不须要懂HQL或者SQL,彻底的面向对象的操做方式
其中,前两种属于快捷检索方式,比较简单且经常使用,当这两种检索方式不能知足须要的时候,就须要使用后面几种检索方式,来自定义检索,如复杂检索条件等等。
后面三种是可代替的,
什么是对象导航检索?
当两个对象之间配置了一对1、一对多或者多对多的关系的时候,能够经过一个对象关联获取到另一个对象的检索方式
如:Customer和Order对象的对象导航检索:
【示例】建立cn.itcast.b_query,建立类TestQuery,而后进行以下的测试
1).查询某客户信息,而且打印其下全部的订单;
2).查询某订单的信息,并打印其所属客户的信息。
@Test public void testNavigate(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //1).查询某客户信息,而且打印其下订单; // Customer customer = (Customer) session.get(Customer.class, 1); // System.out.println(customer); //遍历全部的订单 // for(Order o:customer.getOrders()) // { // System.out.println(o); // }
//2).查询某订单的信息,并打印其所属客户的信息。 Order order = (Order) session.get(Order.class, 21); System.out.println(order); System.out.println(order.getCustomer());
session.getTransaction().commit(); session.close(); }
|
【提示】
在Hibernate的多表开发中,几乎全部的关联均可以进行双向导航。
【提示2】
报错的缘由:在customer的toString 方法中打印了orders集合,在order的toString方法中打印了customer,因为会进行导航,因此致使内存溢出
因此改进:
【注意】
导航检索必须是持久态对象,不然不能导航!
【导航检索的概念扩展】
导航检索就是在查询出某个的PO对象(持久态)后,再访问其关联的集合对象属性的时候,会自动发出SQL,来填充关联属性的所引用的对象。
如:查询客户后,再访问其订单属性的时候,Hibernate会自动发出查询订单的语句,并自动填充订单的值。
【注意】
默认状况下,关联属性是延迟加载的,只有在访问其关联属性的时候才发出SQL从数据库查询,致使查询两张表数据时,至少发出两部分SQL语句,一个是主对象查询语句,一个是关联属性的语句。
HQL支持各类各样的常见查询,和sql语言有点类似,它是Hibernate中使用最普遍的一种检索方式。
QBC也支持HQL所支持的查询方式,但彻底采用面向对象的思想来编程。完整使用详见:
【提示了解】
HQL\QBC和SQL的区别?
HQL\QBC面向类和属性,由hibernate自动生成sql语句,效率低
SQL面向表和字段,你写啥语句,就运行啥语句,不会去组建sql语句,效率高
下面的课程将着重分别研究HQL、SQL、QBC这三种检索方式。
【三种方式的选择】
其中HQL和QBC是Hibernate推荐的检索方式,但性能上会有折扣,而SQL的检索方式虽然效率很高,但不是面向对象的,开发上麻烦一些。
【示例】
查询出全部客户信息。
@Test public void testQueryAll(){ //查询出全部客户信息 Session session = HibernateUtils.openSession(); session.beginTransaction();
//HQL方式一 // List<Customer> list = session.createQuery("from Customer").list(); //HQL方式二:给对象起别名的方式查询 // List<Customer> list = session.createQuery("select c from Customer c").list(); //请注意:HQL不支持*的查询方式的, // List<Customer> list = session.createQuery("select * from Customer").list();
//SQL //List<Customer> list = session.createSQLQuery("select * from t_customer").addEntity(Customer.class).list();
//QBC:彻底的面向对象 Criteria criteria = session.createCriteria(Customer.class); List<Customer> list = criteria.list();
System.out.println(list); session.getTransaction().commit(); session.close(); } |
【总结】
1.sql查询的默认结果是List<Object[]>(用数组包装了不少小customer),须要进行实体的绑定,SQLQuery提供了addEntity方法。
2.SQL的语句生成方式:
Hql和sql的方式:语句是可控的,能够自定义的
Qbc:语句是彻底由hibernate本身生成。
HQL和SQL的查询时的条件值能够直接写死,可是写死的这种方式,几乎不用,如今主要使用匿名参数(占位符?)和命名参数这两种主要方式进行参数注入。
【示例】
查询姓名是rose的客户,只返回rose的一条记录。
//查询姓名是rose的客户,只返回rose的一条记录。 @Test public void testQueryByCondition(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //hql //方式一:写死的方式,几乎不用 // Customer customer = (Customer)session.createQuery("from Customer where name='rose'").uniqueResult(); //方式二:匿名方式:占位符? // Customer customer = (Customer)session.createQuery("from Customer where name = ?") // .setParameter(0, "rose") // .uniqueResult(); //方式三: // Customer customer = (Customer)session.createQuery("from Customer where name = ?") // .setString(0, "rose") // .uniqueResult(); //方式四:命名的方式:注入参数 // Customer customer = (Customer)session.createQuery("from Customer where name = :name") //// .setString("name", "rose") // .setParameter("name", "rose") // .uniqueResult(); //sql //方式一:写死 // Customer customer = (Customer)session.createSQLQuery("select * from t_customer where name ='rose'") // .addEntity(Customer.class)//使用sql,必定不能忘记封装实体 // .uniqueResult(); //方式二:匿名方式:占位符? // Customer customer = (Customer)session.createSQLQuery("select * from t_customer where name = ?") // .addEntity(Customer.class)//必须先封装实体,再注入参数 //// .setString(0, "rose") // .setParameter(0, "rose") // .uniqueResult();
//方式三:命名方式: // Customer customer = (Customer)session.createSQLQuery("select * from t_customer where name = :name") // .addEntity(Customer.class) // .setString("name", "rose") //// .setParameter("name", "rose") // .uniqueResult();
//qbc Criteria criteria = session.createCriteria(Customer.class); //玩命的加条件 criteria.add(Restrictions.eq("name", "rose")); // /继续加条件 criteria.add(Restrictions.like("city", "%上%")); //...继续加条件
//当条件都加完以后, // List<Customer> list = criteria.list();//当结果是0/1条的时候,也可使用uniqueResult(),任何状况之下,均可以使用list Customer customer = (Customer) criteria.uniqueResult();
System.out.println(customer); session.getTransaction().commit(); session.close();
} |
【HQL和QBC支持的各类运算和对应关系】:
【示例】
按照id对客户信息进行排序。
//按照id对客户信息进行排序。 @Test public void testQueryByOrder(){
Session session = HibernateUtils.openSession(); session.beginTransaction(); //hql :都是面向对象 asc:升序 (默认值) desc:降序 // List<Customer> list = session.createQuery("from Customer order by id desc").list();
//sql // List<Customer> list = session.createSQLQuery("select * from t_customer order by id desc") // .addEntity(Customer.class) // .list();
//qbc List<Customer> list = session.createCriteria(Customer.class) .addOrder(org.hibernate.criterion.Order.desc("id"))//排序 org.hibernate.criterion.Order.desc("id") 降序 .list();
System.out.println(list); session.getTransaction().commit(); session.close();
}
|
【示例】
将订单进行分页查询,每页10条记录,如今须要显示第二页的数据。
//将订单进行分页查询,每页10条记录,如今须要显示第二页的数据。 @Test public void testQueryByPage(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //准备两个变量 int page = 2; int pageCount = 10 ; //起始数:hibernate也是从0开始计数,因此起始条数不须要+1 int fromIndex = (page-1)*10;
//hql:分页查询方式,适用全部的数据库 // List<Customer> list = session.createQuery("from Order") // //设置起始索引 // .setFirstResult(fromIndex) // //设置每页查询的条数 // .setMaxResults(pageCount) // .list();
//sql:注意区分数据库:mysql的分页使用limit关键,oracle的分页至关复杂 // List list = session.createSQLQuery("select * from t_order limit ?,?") // .addEntity(Order.class) // .setInteger(0, fromIndex) // .setInteger(1, pageCount) // .list();
//qbc List<Order> list = session.createCriteria(Order.class) //起始索引 .setFirstResult(fromIndex) //每页的条数 .setMaxResults(pageCount) .list();
System.out.println(list); session.getTransaction().commit(); session.close(); } |
【扩展oracle的sql语句的写法】
//oracle:写的技巧:先在sql编辑器中写好,再复制进来改一改就好了。 List<Order> list2 = session.createSQLQuery("SELECT * FROM (SELECT t.*,ROWNUM r FROM t_order t WHERE ROWNUM<="+(firstResult+maxResults)+") t2 WHERE t2.r>="+(firstResult+1)).addEntity(Order.class).list(); System.out.println(list2); |
注意:若是用Hibernate技术,分页推荐使用hql或qbc,由于能够自动适应数据库。
什么是投影查询?
投影查询就是查询结果仅包含实体的部分属性,即只查询表中的部分指定字段的值,不查询所有。如:
select t.a,t.b,t.c from t;或者select count(*) from table; (是一种特殊的投影查询)
投影的实现:
criteria.setProjection(须要查询的属性)
【示例】
查询客户的id和姓名。
//查询客户的id和姓名。 @Test public void testQueryByProjection(){ Session session = HibernateUtils.openSession(); session.beginTransaction();
//hql:投影查询返回是一个数组,不在一个是封装好的对象, //在hibernate中,若是返回的是Object[]的话,那么这个对象是不会存在于一级缓存的, // 是一个非受管对象(不受session管理) //List集合的长度是3: // 0 [1,'rose'] // 1 [2,'lucy'] // 2 [3,'jack'] // List<Object[]> list = session.createQuery("select c.id,c.name from Customer c").list(); //适用hql投影查询的结果能够封装成一个对象,可是仍是一个非受管对象 //步奏 //1 去po中添加构造方法:空参构造+带参构造 //2 从新编写hql语句 // List<Customer> list = session.createQuery("select new Customer(c.id,c.name) from Customer c").list(); //sql // List<Object[]> list = session.createSQLQuery("select id,name from t_customer").list(); //qbc
List<Object[]> list = session.createCriteria(Customer.class) //设置投影,参数就是须要投影的属性 .setProjection( //投影可能须要投影多个列,因此将多个列加入list集合,list集合是有序的 Projections.projectionList() //向projectionList中添加须要查询的列 .add(Property.forName("id")) .add(Property.forName("name")) //疯狂的追加投影的列 ).list();
for(Object[] obj:list) { System.out.println(obj[0]+":"+obj[1]); }
// System.out.println(list); session.getTransaction().commit(); session.close(); } |
【注意】
通过投影查询的结果,默认都不会封装到实体类型中,而是根据实际查询的结果自动封装(object[]),如查询id和name,返回的object[]的list集合。
最大的坏处:一级缓存不存放该对象。没法使用hibernate的一些特性,好比快照等等。
【注意】查询以后封装到Object[]数组中的这些数据,称之为散装数据,不会存放于一级缓存,因此将来须要用的时候,还要查询,尽可能少用
【应用提示】
实际hibernate开发中,通常较少使用投影查询(除了统计).通常咱们都查询出全部字段,让其自动封装到实体类中就好了.
【扩展阅读】(了解)
投影查询也能够封装到实体类中。(感兴趣的同窗可查看课后文档)
实体类:
qbc:
.setResultTransformer(Transformers.aliasToBean(Customer.class))
最终代码:
//查询用户的id和姓名。 @Test //投影查询:只查询部分属性的值 public void queryByProjection(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //hql //结果集是根据返回的数据,自动封装为Object[],没有封装为实体对象 // List<Object[]> list = session.createQuery("select id,name from Customer").list(); //若是要封装为实体对象,须要提供一个投影属性的构造方法,不会再调用默认的构造器 //尽管被封装为实体对象,但该对象,是个非受管对象。不是被session管理 // List list = session.createQuery("select new Customer(id,name) from Customer").list(); // System.out.println(list); // for (Object[] obj : list) { // System.out.println(obj[1]); // }
//sql //结果集也是根据返回的数据的结果自动封装为Object[] List list2 = session.createSQLQuery("select id,name from t_customer") //设置结果集封装策略 //相似于dbutil中的beanhandler,自动经过反射机制,自动将结果集封装到指定的类型中 // .setResultTransformer(new AliasToBeanResultTransformer(Customer.class)) //官方提供了一个工具类,简化代码编写 .setResultTransformer(Transformers.aliasToBean(Customer.class)) .list(); // ResultTransformer
System.out.println(list2);
//qbc List list3 = session.createCriteria(Customer.class) //设置投影列表 .setProjection(Projections.projectionList() //给属性起别名 .add(Property.forName("id").as("id")) .add(Property.forName("name").as("name"))) //添加结果集的封装策略 //发现了,该结果集封装策略,是根据字段的别名来自动封装 //解决方案:增长别名 .setResultTransformer(Transformers.aliasToBean(Customer.class)) .list(); // Projection // Property System.out.println(list3);
session.getTransaction().commit(); session.close();
}
|
小结:hibernate开发的状况下,通常,不使用投影。,由于查询出来的对象不被hibernate管理,它是是一个非受管对象。
没法使用到hibernate的一些特性,好比快照更新等。
还有一种状况,必须使用投影!统计的时候!(即便是统计的时候,投影也不是惟一的查询方式)
举例:查询customer#2的订单数量:customer.getOrders().size();
统计是一种特殊的投影查询,因此结果也没法封装到实体,而是直接返回了统计后的结果值。
实现方式:
HQL和SQL使用统计函数/聚合函数,以下几种:
QBC统计时是在投影方法参数中,使用Projections.rowCount()或者Projections.count(字段名)
sql语句中:
Count(*)
count(birthday):若是birthday=null,就不会做为总的结果
【示例】
查询客户的总数
//查询客户的总数 @Test public void testQueryByCount(){ Session session = HibernateUtils.openSession(); session.beginTransaction();
//hql:hql返回的结果集类型是Long // Object result = session.createQuery("select count(c) from Customer c").uniqueResult(); // long result = (Long) session.createQuery("select count(c) from Customer c").uniqueResult();
//sql:返回是BigInteger // Object result = session.createSQLQuery("select count(*) from t_customer").uniqueResult(); // BigInteger result = (BigInteger) session.createSQLQuery("select count(*) from t_customer").uniqueResult(); //qbc:返回Long类型 Object result = session.createCriteria(Customer.class) //rowCount:读取全部的行数 // .setProjection(Projections.rowCount()) //读取指定列的行数 ,这种读取方式,当city为null的时候,就不算一条记录 .setProjection(Projections.count("city")) .uniqueResult();
System.out.println(result); session.getTransaction().commit(); session.close(); } |
【提示】
若是数据库是oracle的话,sql方式返回的是BigDecimal
【示例】
查询一下客户编号为1的客户的订单数量,要求只统计订单的金额要大于等于30。(提示:综合了条件查询和统计查询(投影))
@Test public void testPractise(){ //查询一下客户编号为1的客户的订单数量,要求只统计订单的金额要大于等于30 Session session = HibernateUtils.openSession(); session.beginTransaction(); // HQL:面向对象的查询方式 // Object result = session.createQuery("select count(o) from Order o where o.price>=? and o.customer.id = ? ") // .setParameter(0, 30d) // .setParameter(1, 1) // .uniqueResult(); //不用投影,不用统计查询 // int result = session.createQuery("from Order o where o.price>=? and o.customer.id = ? ") // .setParameter(0, 30d) // .setParameter(1, 1) // .list().size();
// SQL // Object result = session.createSQLQuery("select count(*) from t_order where price >=? and cid = ?") // .setParameter(0, 30d) // .setParameter(1, 1) // .uniqueResult();
// QBC:彻底的面向对象的方式操做数据库
Customer customer = new Customer(); customer.setId(1); //不采用投影的方式 // int result = session.createCriteria(Order.class) // .add(Restrictions.ge("price", 30d)) // .add(Restrictions.eq("customer", customer)) // .list().size();
//采用投影的方式 Object result = session.createCriteria(Order.class) .add(Restrictions.ge("price", 30d))//设置条件 .add(Restrictions.eq("customer", customer))//设置条件 .setProjection(Projections.rowCount())//投影:只要统计结果的行数 .uniqueResult();
System.out.println(result); session.getTransaction().commit(); session.close();
} |
什么是命名查询?
命名查询(NamedQuery),是指将sql或hql语句写入配置文件中,为该语句起个名字,在程序中经过名字来访问sql或hql语句。
优势:便于维护。
命名查询的实现步骤:
第一步:在hbm中配置命名查询的名字和语句(支持HQL或SQL)。
第二步:在程序中经过session.getNamedQuery(命名查询的名字)来直接获取Query或SQLQuery对象,进而进行查询操做。
【示例】
查询客户的全部信息
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> <!-- 配置java类与表之间的对应关系 --> <!-- name:类名:类对应的完整的包路径 table:表名 --> <class name="cn.itcast.a_onetomany.Customer" table="t_customer"> <!-- 配置主键 name:java类中的属性 column:表中的字段,列名,当name和column一致的时候,column能够省略 --> <id name="id" column="id"> <!-- 主键生成策略 mysql的自增加:identity --> <generator class="native"></generator> </id> <!-- 其余属性 name:java中的属性 column:表中字段名 当name和column一致的时候,column能够省略 --> <property name="name" column="name"></property> <!-- age :--> <property name="city"></property>
<!-- 配置集合 --> <set name="orders"> <!-- column:外键 --> <key column="cid"></key> <!-- 配置关系 class:集合中装载对象的原型 --> <one-to-many class="cn.itcast.a_onetomany.Order"/> </set> <!-- hql :注意语句结束不能加";",不然报错--> <query name="query1"> from Customer </query> <!-- sql --> <sql-query name="query2"> select * from t_customer </sql-query> </class>
<!-- name尽可能具备实际意义 --> <!-- hql :注意语句结束不能加";",不然报错--> <query name="Customer.hql.queryall"> from Customer </query> <!-- sql --> <sql-query name="Customer.sql.queryall"> select * from t_customer </sql-query>
</hibernate-mapping>
|
testNameQuery方法的编写:
@Test public void testNamedQuery(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //hql //写在class里面,得经过包路径调用 // List<Customer> list = session.getNamedQuery("cn.itcast.a_onetomany.Customer.query1").list(); //写在class外面,就直接经过name调用 // List<Customer> list = session.getNamedQuery("query3").list();
//sql //class里面 // SQLQuery sqlQuery = (SQLQuery) session.getNamedQuery("cn.itcast.a_onetomany.Customer.query2"); // List<Customer> list = sqlQuery.addEntity(Customer.class).list(); //写在class外面 List<Customer> list = ((SQLQuery)session.getNamedQuery("query4")).addEntity(Customer.class).list();
System.out.println(list); session.getTransaction().commit(); session.close(); }
|
【提示】
命名查询写在<class>元素的内外是有区别的:
业务开发场景(阅读):
在项目中:CRUD操做,查询使用量最多,在实际中,都是根据条件查询
条件不同,那么咱们后台是否是要写不少查询方法
根据城市查询:queryByCity
根据用户名查询:queryByName
根据年龄查询:queryByAge
-----dao中方法很重复
离线查询:他容许在业务层(service)去拼装条件,而后直接将条件传入dao层的方法,运行,
这时候,dao层只须要一个方法,就能够完成查询
在常规的Web编程中,有大量的动态条件查询,即用户在网页上面自由选择某些条件,程序根据用户的选择条件,动态生成SQL语句,进行查询。 针对这种需求,对于分层应用程序来讲,Web层须要传递一个查询的条件列表给业务层对象,业务层对象得到这个条件列表以后,而后依次取出条件,构造查询语句。这里的一个难点是条件列表用什么来构造?传统上使用Map,可是这种方式缺陷很大,Map能够传递的信息很是有限,只能传递name和value,没法传递究竟要作怎样的条件运算,到底是大于,小于,like,仍是其它的什么,业务层对象必须确切掌握每条entry的隐含条件。所以一旦隐含条件改变,业务层对象的查询构造算法必须相应修改,可是这种查询条件的改变是隐式约定的,而不是程序代码约束的,所以很是容易出错。 DetachedCriteria能够解决这个问题,即在web层,程序员使用DetachedCriteria来构造查询条件,而后将这个DetachedCriteria做为方法调用参数传递给业务层对象。而业务层对象得到DetachedCriteria以后,能够在session范围内直接构造Criteria,进行查询。就此, WEB层只须要添加条件,不须要考虑查询语句如何编写,而业务层则只负责完成持久化和查询的封装便可,与查询条件构造彻底解耦,很是完美! 最大的意义在于,业务层或dao层代码是固定不变的,全部查询条件的构造都在web层完成,业务层只负责在session内执行之。这样代码就可放之四海而皆准,都无须修改了。 |
【区别】:
Criteria:在线查询方式:依赖session,有了session以后,才能去建立Criteria对象,而后才能够添加条件
DetachedCriteria:离线查询方式:不依赖session建立,自身内部含有建立方式,能够在没有session的状况下,自由的组装各类条件,
而后在发送给session执行
API的查看:
经过API分析,获得编程关键点:
DetachedCriteria是Criteria的子实现,经过静态方法DetachedCriteria.forClass(PO.class)来实例化,它能够像Criteria的对象同样增长各类查询条件,经过detachedCriteria.getExecutableCriteria(session)方法与session关联,变成在线Criteria对象,最后经过criteria.list()方法获得数据。
【示例】
查询id值大等于2且城市是杭州的客户信息。
@Test public void testDetachedCriteria(){ //查询id值大等于2且城市是杭州的客户信息。 //模拟service层 DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Customer.class); //拼命的加条件 // detachedCriteria.add(Restrictions.ge("id", 2)); // detachedCriteria.add(Restrictions.eq("city", "杭州"));
//查询名字带有c的人员的信息 detachedCriteria.add(Restrictions.like("name", "%c%"));
//模拟dao层:dao层就固定写法 Session session = HibernateUtils.openSession(); session.beginTransaction(); //执行离线查询,传入session Criteria criteria = detachedCriteria.getExecutableCriteria(session); List<Customer> list = criteria.list();
System.out.println(list);
session.getTransaction().commit(); session.close(); }
|
【Criteria和DetachedCriteria的区别】
Criteria和DetachedCriteria 的主要区别在于建立的形式不同, Criteria 是在线的,因此它是由 Hibernate Session 进行建立的;而 DetachedCriteria 是离线的,建立时无需Session,DetachedCriteria 提供了 2 个静态方法 forClass(Class) 或 forEntityName(Name)进行DetachedCriteria 实例的建立。使用getExecutableCriteria(session)方法转换成在线可执行的Criteria
多表关联的分类:
三种链接方式的sql语句和结果:
[内链接]: select * from customer t1, order t2 where t1.id=t2.customer_id;--并非标准-隐式的内链接 select * from customer t1 inner join order t2 on t1.id=t2.customer_id;--sql99标准语法-显示内链接 查询结果: 1 Rose 1001 电视机 1
[左外链接]:以左表为基表,右表来关联左表,若是右表没有与左表匹配的数据,则右表显示null select * from customer t1 left outer join order t2 on t1.id =t2.customer_id;//左外链接
查询结果: 1 Rose 1001 电视机 1 2 Jack null null null
[右链接] select * from customer t1 right join order t2 on t1.id=t2.customer_id;//右链接 查询结果: 1 Rose 1001 电视机 1 null null 1002 空调 null |
#内链接:项目中使用最多的链接方式 #方式一:表链接的工做就是先进性笛卡尔乘积,而后在筛选 SELECT c.*,o.* FROM t_customer c,t_order o WHERE c.id=o.cid; #方式二 SELECT c.* ,o.* FROM t_customer c INNER JOIN t_order o ON c.id = o.cid;
# 左外链接(左链接):以左表为基表,右表来匹配左表,左表数据所有显示,当右表没有与之匹配的数据的时候,直接用null代替 SELECT c.*,o.* FROM t_customer c LEFT JOIN t_order o ON c.id = o.cid; #左链接实际应用场景:查询历来没有买过东西的客户信息 SELECT c.*,o.* FROM t_customer c LEFT JOIN t_order o ON c.id = o.cid WHERE o.id IS NULL;
# 右外链接(右链接):以右表为基表,左表来匹配右表,右表数据所有显示,当左表没有与之匹配的数据的时候,直接用null代替 SELECT o.*,c.* FROM t_order o RIGHT JOIN t_customer c ON c.id = o.cid; |
【提示】
HQL支持普通链接(内链接、左外链接),但也支持迫切链接(迫切内链接、迫切左外链接)。
QBC和SQL都只支持普通链接(内链接、左外链接)。
【学习目标提醒】
主要目标是学习链接和迫切链接的异同。
回顾导航查询的缺点:
会先查询主po对象,发出一条语句,再访问关联属性的时候,再发出一条语句,须要两次查询。
若是,我想一次性拿到客户和关联的订单,我就可使用多表关联查询。仅使用一条sql语句,就能够获得两个表(对象)的数据,效率比两次查询高!
【示例】
查询全部客户信息和对应的全部订单信息,要求一条语句就将两张表的结果查询出来(提示:内链接或迫切内链接)。
//查询全部客户信息和对应的全部订单信息,要求一条语句就将两张表的结果查询出来 @Test public void testQueryForManyTable(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); // HQL:采用HQL进行链接,不须要加条件,由于条件已经在hbm.xml中定义好了 //内链接返回的是数组对象 (数组是一种散装对象,不会存入session缓存,不具有快照特性) // List list = session.createQuery("from Customer c inner join c.orders").list(); //迫切链接将返回的结果封装为实体对象 // List<Customer> list = session.createQuery("from Customer c inner join fetch c.orders").list(); //经过观察,咱们发现,结果重复,接下来去除重复:滤重 //distinct:去处重复的关键字(hibernate,mysql和oracle都用这个关键字去重) List<Customer> list = session.createQuery("select distinct(c) from Customer c inner join fetch c.orders").list();
System.out.println(list); session.getTransaction().commit(); session.close(); } |
【结果】
【分析内链接和迫切内链接的异同】
【扩展了解】
和SQL语句同样,内链接或迫切内链接的语句中的inner关键字能够省略。
【问题】
迫切内链接返回的结果是重复的,可以使用distinct关键字滤重。
【示例】
一次性查询出全部客户信息以及其所下的订单的信息,要求结果被封装到客户的实体对象中,而且返回的对象不要重复。
List<Customer> list13 = session.createQuery("select distinct (c) from Customer c join fetch c.orders").list();
|
【提示】
若是要用迫切链接查询的话,结果须要去除重复的。
左外链接(左链接)和迫切左外链接:
//左外链接:返回的结果是一个Object[]的数组对象 // List list15 = session.createQuery("from Customer c left join c.orders").list(); //迫切左外链接:返回的结果是一个封装好的Customer对象 List list15 = session.createQuery("select distinct c from Customer c left join fetch c.orders").list(); System.out.println(list15); |
【示例】封装以后的数据,只能是Object[]类型的数组,是一个非受管对象
一次性查询出客户信息和其下的订单信息。(提示:没法实现实体的彻底封装)
//内链接--没有迫切一说 //sql //普通的内链接 List list = session.createSQLQuery("select * from t_customer t1 inner join t_order t2 on t1.id =t2.cid") // .addEntity(Customer.class)//封装到实体 .addEntity(Order.class)//封装到实体,发现封装后会丢失数据 .list(); |
【提示:】
SQL只有内链接查询,没有迫切内链接查询。所以,没法实现一次性将主对象和关联对象一次性查询出来的需求。
Criteria接口提供createCriteria和createAlias两组方法用于完成多表关联查询
提示:qbc没有迫切查询
QBC采用createCriteria()很是容易的在互相关联的实体间创建链接关系。
从名字上看,貌似是建立一个子的creaiteria,可是生成的语句能够是内链接或左链接的.
【示例】
一次性查询出全部用户及其所下的订单信息。
一次性查询出某用户(id=1)所下订单的信息,而且要求订单价格大于50元。
//一次性查询出全部用户及其所下的订单信息。 @Test public void testQueryForManyTableByCriteria1(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //主查询 Criteria criteria = session.createCriteria(Customer.class); //链接对象:默认是内链接 criteria.createCriteria("orders"); // 因为查询结果是重复的,因此在list方法执行以前必定要滤重, //设置重复结果过滤 criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); //进行查询 //返回的结果到底封装成什么类型,主要看主查询的参数 List<Customer> list = criteria.list();
System.out.println(list); session.getTransaction().commit(); session.close();
}
//一次性查询出某用户(id=1)所下订单的信息,而且要求订单价格大于50元。 @Test public void testQueryForManyTableByCriteria2(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //主查询:订单 Criteria criteria = session.createCriteria(Order.class); // /添加条件 criteria.add(Restrictions.gt("price", 50d)); //子查询:默认是内链接 Criteria childCriteria = criteria.createCriteria("customer"); //添加条件 childCriteria.add(Restrictions.eq("id", 1));
//执行查询 List<Order> list = criteria.list();
System.out.println(list); session.getTransaction().commit(); session.close();
}
|
【控制台结果】
【QBC的优点】
条件越多,编码起来相对越简单一些,只须要在criteria上加条件便可,而不须要关心语句该怎么写。
[扩展:更改qbc的结果集的重复封装的问题]
Hibernate推荐使用HQL和QBC,二者区别:
但企业开发中,若是为了sql语句的性能,会直接采用SQL进行开发。若是为了封装方便(好比离线查询条件封装),也会采用QBC。具体根据项目架构来决定。
可是这不是Query主要职责,他的只要职责仍是查询,并且他作增长的时候,还有缺陷:
它不能执行 insert into table(,,,) values(,,)
只支持INSERT INTO ... SELECT ...形式
Query接口也能够接受insert、update、delete语句的执行。
//Query也能够执行insert,update,delete //场景,不根据id来更新,不根据id删除,想建立一张表 @Test //query对象的使用扩展 public void queryObjExtend(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //hql //根据名称更新客户->query不止是能够查询,也能够执行任何的语句 //该方法更新,不走一级缓存,直接操做数据库了,至关于之前connection了 // Query query = session.createQuery("update Customer set city='海南岛' where name='xiaohong'"); // //执行query // int count = query.executeUpdate(); // System.out.println(count);
//sql session.createSQLQuery("create table t_test (name varchar(30))").executeUpdate();
session.getTransaction().commit(); session.close(); } |
但insert只支持:hql 只支持INSERT INTO ... SELECT ...形式, 不支持INSERT INTO ... VALUES ...形式.
原理是:query仍是以查询为基础的
抓取策略的官方定义:
简单的说:Hibernate抓取策略(fetching strategy)是指:在检索一个对象,或者在对持久态对象经过对象导航方式来获取关联对象属性的数据时,Hibernate的相关检索策略。抓取策略能够在hbm映射文件中配置声明,也能够在HQL语句中进行覆盖(即前面写的迫切左外语句,缺点代码耦合太强,可配置性差)。
根据数据的抓取方式分为:
类级别的抓取策略就两种:当即检索和延迟检索
当即检索:get(第二次查询会从一级缓存中查询),createQuery(hql).list(每次都查询,每次都发查询语句)
延迟检索:load(第二次查询会从一级缓存中查询)
建立包cn.itcast.c_fetchingstrategy,建立类TestStrategy,而后测试
//加载customer信息 //get:默认当即加载 Customer c1 = (Customer)session.get(Customer.class, 1); System.out.println(c1); //load:默认延迟加载 Customer c2 = (Customer)session.load(Customer.class, 2); System.out.println(c2); |
load默认返回目标类的代理对象的子类对象,没有发送sql(即没有初始化),只有当访问的时候才初始化。
load延迟加载是否能够改变呢?
经过hbm文件的<class>元素的lazy属性进行设置(默认值是true)
再次测试上面的例子。
发现load也变成当即加载了。
结论:lazy=false的时候,类采用当即加载策略,load和get效果同样了。
状况一:当访问代理对象id以外的属性的时候
//load:默认延迟加载,什么时候被初始化呢? Customer c2 = (Customer)session.load(Customer.class, 2); System.out.println(c2.getId());//访问id的时候不会初始化 System.out.println(c2);//当访问其余属性的时候,自动初始化 |
状况二:使用Hibernate工具类的initialize方法强制初始化代理对象--了解
Customer c2 = (Customer)session.load(Customer.class, 2); Hibernate.initialize(c2);//强制初始化 |
小结:若是真要用强制初始化。,那还不如直接用get进行查询
当访问对象的延迟加载时,底层也是调用Hibernate工具类的initialize方法
若是使用HQL进行查询,即便配置了延迟加载,也无效
【示例】
采用createQuery查询一个对象,无懒加载特色。(即便配置了懒加载也无效)
Customer c3=(Customer)session.createQuery("from Customer where id =2").uniqueResult(); System.out.println(c3); |
【提示】
这里能够看出,Query对象的查询都是当即加载,并当即发出用户定义好的SQL,并且必定会发出(不从一级缓存中获取)。
HQL的两个特色:马上加载 ; 不走一级缓存
【解释说明】在一对多的关系中,在一方,配置了集合,咱们研究集合的初始化的时机,在默认状况下,集合在你须要使用的时候,才会初始化,不使用,就不会初始化。固然,对于这个默认的结果,是能够改变的,如何改变呢?
主要使用:
<set> 元素提供fetch属性和lazy属性 用于设置 集合 抓取策略
关于fetch和lazy的做用:
语言精简一下,记住:
fetch是控制sql语句的生成方式,(1 表链接、2 子查询、3须要的时候查询)
lazy是控制数据初始化的时间。
一对多或多对多方向关联的检索策略表: (配置在set标签上的)
【提示】
通过分析发现Fetch:属性的值有3个,Lazy属性的值也有3个,这两个属性是要同时配置的,有9种组合。
为方便学习,咱们将根据fetch的值的状况,将其分为三类组合:
第一类组合:
fetch |
语句形式 |
lazy |
数据初始化的时间 |
select |
多条简单SQL语句 |
true |
延迟加载(默认值) |
false |
当即加载 |
||
extra |
加强的延迟加载(极其懒惰) |
lazy=extra的说明:当程序调用orders 属性的 size(), contains() 和 isEmpty() 方法时, Hibernate 不会初始化orders集合类中全部子对象的实例,
仅经过特定的 select 语句查询必要的信息, 不会检索全部的 Order 对象。
设置方法:
在采用<one-to-many>元素的父元素(如set)中设置fetch和lazy属性的值。
在customer.hbm.xml中设置一下默认属性的值:
【测试示例代码】
@Test public void testFetchAndLazy(){ Session session = HibernateUtils.openSession(); session.beginTransaction();
Customer customer = (Customer)session.get(Customer.class, 1);
System.out.println(customer.getOrders().size());
session.getTransaction().commit(); session.close(); } |
第二类组合:
fetch |
语句形式 |
lazy |
数据初始化的时间 |
join |
迫切左外链接SQL语句 |
true |
所有忽略失效。 |
false |
|||
extra |
【进一步】
经过策略列表发现,只要是fetch是join就是迫切左外链接,而迫切左外链接就会当即加载其属性, lazy属性被忽略. (如Customer left join fetch orders当即查询客户和订单数据)
【示例】
@Test public void testFetchAndLazy(){ Session session = HibernateUtils.openSession(); session.beginTransaction();
Customer customer = (Customer)session.get(Customer.class, 1);
System.out.println(customer.getOrders().size());
session.getTransaction().commit(); session.close(); } 控制打印的语句: |
第三种组合:(了解)
fetch |
语句形式 |
lazy |
数据初始化的时间 |
subselect |
子查询的sql语句(效率偏低,通常不采用) |
true |
所有忽略失效。 |
subselect |
【示例】
List<Customer> list = session.createQuery("from Customer").list(); for (Customer customer : list) { System.out.println(customer.getOrders().size()); } |
使用createQuery自定义HQL查询语句时,fetch就会被直接忽略(失效),而lazy会根据语句的编写状况能够有效,也能够无效。
也就是说,语句的格式已经定死了,fetch没法改变了,就会失效。而语句若是是采用多表链接查询,那么lazy也会无效;但如果语句只是查询一个对象,那么其关联属性的lazy依然有效(由于是通常的导航查询)
采用HQL的时候,fetch直接失效
Lazy看状况:当hql语句只是查询单个简单的对象,lazy依然有效
当hql是进行多表查询的时候,lazy也会失效
【示例】
//fetch确定是失效,lazy有效 session.createQuery("from Customer"); //fetch和lazy都失效 session.createQuery("from Customer c inner join c.orders"); |
【总结】
多对一抓取策略:经过多的一方(Order)来导航查询一的一方(Customer)的策略。
抓取一方的时机
关系元素(<many-to-one>)中提供fetch属性和lazy属性 用于设置 抓取策略,如:
在Order.hbm.xml <many-to-one>中配置。
咱们将根据fetch的值的状况,将其分为两类组合:
【测试示例】
查询某订单,而且要显示其所属的客户信息。
分析:查询主体是订单
//查询1号订单,而且要显示其所属的客户信息。 Order o =(Order)session.get(Order.class, 1); System.out.println(o); System.out.println(o.getCustomer()); |
第一类组合:
fetch |
语句形式 |
lazy |
数据初始化的时间 |
select |
多条简单SQL语句 |
Proxy (分状况讨论) |
根据关联对象的类级别抓取策略来决定是否延迟加载(默认值) <class name="..Customer" lazy=true >:延迟加载 <class name="..Customer" lazy=false>:当即加载 |
false |
当即加载 |
第二类组合:
fetch |
语句形式 |
lazy |
数据初始化的时间 |
join |
迫切左外链接SQL语句 |
proxy |
所有忽略失效。 |
false |
【示例】
@Test public void testFetchAndLazy_manyToOne(){ //查询某订单,而且要显示其所属的客户信息。 Session session = HibernateUtils.openSession(); session.beginTransaction();
Order order = (Order) session.get(Order.class, 1); System.out.println(order); System.out.println(order.getCustomer());
session.getTransaction().commit(); session.close(); } |
使用createQuery自定义HQL查询语句时,fetch就会被直接忽略(失效),而lazy会根据语句的编写状况能够有效,也能够无效。
也就是说,语句的格式已经定死了,fetch没法改变了,就会失效。而语句若是是采用多表链接查询,那么lazy也会无效;但若是语句只是查询一个对象,那么其关联属性的lazy依然有效(由于是通常的导航查询)
【示例】
若是采用Query查询,不会自动生成左外链接(query是本身写的语句),fetch=join 被忽略,lazy能够生效
//fetch无效,lazy有效 session.createQuery("from Order where id = 1"); //fetch和lazy都无效 session.createQuery("from Order o inner join o.customer"); |
【总结】当使用HQL的时候,不少的配置会直接失效
1.设置类级别抓取策略 ,能够经过修改 hbm文件 <class>元素 lazy属性来实现,值能够是:true延迟,false 当即:
2.设置关联级别抓取策略:
延迟加载的好处是:没有当即加载数据,当须要的时候再加载,提升了内存的使用率,优化了程序的效率!
所以,在通常状况下,能延迟加载的尽可能延迟,默认状况下都是延迟的。这也是框架默认的。
默认的处理方式已是很是优秀了,不多须要改。
但,还要根据具体业务开发中的须要,若是这些数据就是须要当即展现,那么就优先使用fetch=join迫切左外链接查询加载数据。
Fetch+lazy的组合
Select+lazy:效果较多
Join+lazy:lazy失效,直接表链接
下面几道题目用来理解抓取策略:
问题:发出了几条sql语句
//上述案例的验证核心代码:
Order order =(Order)session.get(Order.class, 1); Customer customer=order.getCustomer(); customer.getName ();
|
分析:
分析:
【由此看出】
sql语句是由类级别抓取策略和关联集合的抓取策略共同决定的
批量抓取(Batch fetching):对查询抓取的优化方案,经过指定一个主键或外键列表,Hibernate使用单条的select语句获取一批对象实例或集合的策略.
批量抓取的目的:为了解决
.(或称之为1+N)的问题。(主要是针对导航查询)
什么是N+1?请看下面的示例。(下面咱们也将分为一对多和多对一两个方向进行讲解。)
【需求】
查询全部客户和其所下的订单的数量状况
//查询全部客户和其订单的数量状况 List<Customer> list = session.createQuery("from Customer").list();
for(Customer c:list) { System.out.println(c.getName()+":"+c.getOrders().size()); } |
思考:会产生多少条语句呢?
3个客户,4条语句。
先查客户一条+3次订单查询。
这就是N+1
并且从打印的语句看后面4条都差很少。
问题:若是是1w个用户呢?会发送10001条语句。(每一个用户查询订单 SQL语句格式是相同的)
优化方案:
设置:
问题:那这个值如何设定呢?
值的设定根据你的需求(你前台的页面来了)来的,好比你的条件就每次查询10条,那就配置为10。
【需求】
查询全部订单信息,并打印对应的客户姓名
List<Order> list = session.createQuery("from Order").list();
for(Order o:list) { System.out.println(o.getName()+":"+o.getCustomer().getName()); } |
思考:会产生多少条语句呢?
4条语句------>查询订单一条+查询对应的客户3条(一级缓存致使不是30条)
优化方案:
注意,仍是在customer.hbm.xml中配置:
仍是在一的一方的class标签上配置的。
优化后变成2条
小结:batch-size到底设置多少?根据你的页面显示的数量来调整。好比你页面每次就10条,那么你能够将该值设置为10。
该值,也不能太大,太大可能会致使内存溢出,要根据实际状况来设置。
Fetch=join:直接采用表链接
【做业一】
完成全天课程练习。
【做业二】
次日的课前练习(未完成的部分)