Hibernate的几种查询方式和二级缓存
一. Hibernate的查询方式
主要包括以下几种
1) Get/load主键查询
2) 对象导航查询
3) HQL查询, Hibernate Query language hibernate 提供的面向对象的查询语言。
4) Criteria 查询, 完全面向对象的查询(Query By Criteria ,QBC)
5) SQLQuery, 本地SQL查询
缺点:不能跨数据库平台: 如果修改了数据库,sql语句有肯能要改
使用场景: 对于复杂sql,hql实现不了的情况,可以使用本地sql查询。
1.1 HQL查询,先创建两个实体类,并且配置好映射
Dept.java
package com.mrq.entity;
import java.util.HashSet;
import java.util.Set;
public class Dept {
private Integer deptId;
private String deptName;
private Set<Employee> emps = new HashSet<>();//一对多:一个部门对应多个员工
public Integer getDeptId() {
return deptId;
}
public void setDeptId(Integer deptId) {
this.deptId = deptId;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public Set<Employee> getEmps() {
return emps;
}
public void setEmps(Set<Employee> emps) {
this.emps = emps;
}
@Override
public String toString() {
return "Dept [deptId=" + deptId + ", deptName=" + deptName + ", emps=" + emps + "]";
}
public Dept() {
// TODO Auto-generated constructor stub
}
public Dept(Integer deptId, String deptName) {
super();
this.deptId = deptId;
this.deptName = deptName;
}
}
Employee.java
package com.mrq.entity;
public class Employee {
private Integer empId;
private String empName;
private Double salary;
private Dept dept; //多对一:多个员工对应一个部门
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Employee [empId=" + empId + ", empName=" + empName + "]";
}
}
映射文件
Dept.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 package="com.mrq.entity" auto-import="true">
<class name="Dept" table="t_dept">
<id name="deptId">
<generator class="native"></generator>
</id>
<property name="deptName" type="string"></property>
<!--
key:Employee对应的外键名称
-->
<set name="emps"><!-- table="t_employee"可省略 -->
<key column="dept_id"></key>
<one-to-many class="Employee" />
</set>
</class>
<!-- 存放sql语句 -->
<query name="getAll">
<![CDATA[
from Dept d where deptId < ?
]]>
</query>
</hibernate-mapping>
Employee.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 package="com.mrq.entity">
<class name="Employee" table="t_employee">
<id name="empId">
<generator class="native"></generator>
</id>
<property name="empName" type="string"></property>
<property name="salary" type="double"></property>
<many-to-one name="dept" class="Dept" column="dept_id"></many-to-one>
</class>
</hibernate-mapping>
- 各种查询对应的格式和要点
App.java
/*
* 1) Get/load主键查询
2) 对象导航查询
3) HQL查询, Hibernate Query language hibernate 提供的面向对象的查询语言。
4) Criteria 查询, 完全面向对象的查询(Query By Criteria ,QBC)
5) SQLQuery, 本地SQL查询
*/
//1.HQL查询
@Test
public void testQuery() {
Session session = sf.openSession();
session.beginTransaction();
//1.主键查询
Dept dept = (Dept) session.get(Dept.class, 1);
System.out.println(dept);
/*
* 2.对象导航查询:通过一个对象查询另外的对象的信息,比如通过部门,查询该部门下的员工和员工信息
* */
Dept dept = (Dept)session.get(Dept.class, 1);
System.out.println(dept.getEmps());
//3.HQL查询
//方式一:
Query query = session.createQuery("from Dept");
System.out.println(query.list());
//方式二
Query query = session.createQuery("select d from Dept d");
System.out.println(query.list());
//查询指定的column
//1.返回特定对象数组Object[]
Query query = session.createQuery("select d.deptId,d.deptName from Dept d");
System.out.println(query.list());
//2.返回封装的对象:要提供对应的构造函数
Query query = session.createQuery("select new Dept(d.deptId,d.deptName) from Dept d");
System.out.println(query.list());
//条件查询:一个条件/多个条件and or/between and/模糊查询
//1.使用?占位符
Query query = session.createQuery("from Dept d where deptName=?");
query.setParameter(0, "技术部");
System.out.println(query.list());
//2.使用命名参数:command
Query query = session.createQuery("from Dept d where deptId= :id or deptName= :name");
query.setParameter("id", 1);
query.setParameter("name", "技术部");
System.out.println(query.list());
//范围查询 between
Query query = session.createQuery("from Dept d where deptId between ? and ?");
query.setParameter(0, 1);
query.setParameter(1, 5);
System.out.println(query.list());
//模糊查询 like
Query query = session.createQuery("from Dept d where deptName like ?");
query.setParameter(0, "%部%");
System.out.println(query.list());
//聚合函数查询
Query query = session.createQuery("select count(*) from Dept");
Long count = (Long) query.uniqueResult();
System.out.println(count);
//分组查询:查找相同部门的员工
Query query = session.createQuery("select e.dept,count(*) from Employee"
+ " e group by e.dept");
System.out.println(query.list());
session.getTransaction().commit();
session.close();
}
- 常见的连接查询
@Test
public void join() {
Session session = sf.openSession();
session.beginTransaction();
//1.内连接
Query query = session.createQuery("from Dept d inner join d.emps");
System.out.println(query.list());
//2.左外连接
Query query = session.createQuery("from Dept d left join d.emps");
System.out.println(query.list());
//3.右外链接
Query query = session.createQuery("from Dept d right join d.emps");
System.out.println(query.list());
session.getTransaction().commit();
session.close();
}
- 迫切连接fetch查询
public void fetch() {
Session session = sf.openSession();
session.beginTransaction();
//1.迫切内连接,使用fetch,会把右表的数据填充到左表数据中
// Query query = session.createQuery("from Dept d inner join fetch d.emps");
// System.out.println(query.list());
//2.迫切左外连接
Query query = session.createQuery("from Dept d left join fetch d.emps");
System.out.println(query.list());
session.getTransaction().commit();
session.close();
}
- 从配置文件写HQL语句
在Dept.hbm.xml进行配置,加入query节点
<!-- 存放sql语句 -->
<query name="getAll">
<![CDATA[
from Dept d where deptId < ?
]]>
</query>
测试:
@Test
public void hqlQuery() {
Session session = sf.openSession();
session.beginTransaction();
Query query = session.getNamedQuery("getAll");
query.setParameter(0, 10);
System.out.println(query.list());
session.getTransaction().commit();
session.close();
}
- Criteria查询使用基本方式
//Criteria 查询
@Test
public void criteriaQuery() {
Session session = sf.openSession();
session.beginTransaction();
Criteria criteria = session.createCriteria(Employee.class);
//条件约束
//criteria.add(Restrictions.eq("empId", 12));//empId=12的对象
criteria.add(Restrictions.idEq(12));//也可以使用ID查询
System.out.println(criteria.list());
session.getTransaction().commit();
session.close();
}
- HQL分页查询
//分页查询
@Test
public void pageQuery() {
Session session = sf.openSession();
session.beginTransaction();
Query query = session.createQuery("from Employee");
ScrollableResults scrollableResults = query.scroll();//得到结果集
scrollableResults.last();//滚动到最后一行
int totalCount = scrollableResults.getRowNumber()+1;//得到滚动的记录数
//设置分页参数
query.setFirstResult(0);
query.setMaxResults(5);
System.out.println(query.list());
System.out.println("总的记录数 "+totalCount);
session.getTransaction().commit();
session.close();
}
- Hibernate支持原生SQL语句的使用
//支持原生的SQL查询
@Test
public void sql() {
Session session = sf.openSession();
session.beginTransaction();
SQLQuery q = session.createSQLQuery("SELECT * FROM t_Dept limit 4;")
.addEntity(Dept.class); // 也可以自动封装对象
System.out.println(q.list());
session.getTransaction().commit();
session.close();
}
二. Hibernate的二级缓存
前面了解了Hibernate的一级缓存:session缓存
Hibernate提供的缓存
有一级缓存、二级缓存。 目的是为了减少对数据库的访问次数,提升程序执行效率!
一级缓存:
基于Session的缓存,缓存内容只在当前session有效,session关闭,缓存内容失效!
特点:
作用范围较小! 缓存的事件短。
缓存效果不明显。
二级缓存:
Hibernate提供了基于应用程序级别的缓存, 可以跨多个session,即不同的session都可以访问缓存数据。 这个换存也叫二级缓存。
Hibernate提供的二级缓存有默认的实现,且是一种可插配的缓存框架!如果用户想用二级缓存,只需要在hibernate.cfg.xml中配置即可; 不想用,直接移除,不影响代码。
如果用户觉得hibernate提供的框架框架不好用,自己可以换其他的缓存框架或自己实现缓存框架都可以。
- 使用二级缓存进行的配置
#hibernate.cache.use_second_level_cache false【二级缓存默认不开启,需要手动开启】
#hibernate.cache.use_query_cache true 【开启查询缓存】
## choose a cache implementation 【二级缓存框架的实现】
#hibernate.cache.provider_class org.hibernate.cache.EhCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.EmptyCacheProvider
hibernate.cache.provider_class org.hibernate.cache.HashtableCacheProvider 默认实现
#hibernate.cache.provider_class org.hibernate.cache.TreeCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.OSCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.SwarmCacheProvider
二级缓存,使用步骤
1) 开启二级缓存
2)指定缓存框架
3)指定那些类加入二级缓存
4)测试
测试二级缓存!
缓存策略
<class-cache usage="read-only"/> 放入二级缓存的对象,只读;
<class-cache usage="nonstrict-read-write"/> 非严格的读写
<class-cache usage="read-write"/> 读写; 放入二级缓存的对象可以读、写;
<class-cache usage="transactional"/> (基于事务的策略)
在hibernate.cfg.xml中配置二级缓存
<!--****************** 【二级缓存配置】****************** -->
<!-- a. 开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- b. 指定使用哪一个缓存框架(默认提供的) -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
<!-- 开启查询缓存 -->
<property name="hibernate.cache.use_query_cache">true</property>
<!-- c. 指定哪一些类,需要加入二级缓存 -->
<class-cache usage="read-write" class="com.mrq.entity.Dept"/>
<class-cache usage="read-only" class="com.mrq.entity.Employee"/>
<!-- 集合缓存[集合缓存的元素对象,也加加入二级缓存] -->
<collection-cache usage="read-write" collection="com.mrq.entity.Dept.emps"/>
测试二级缓存:
@Test
public void testCache() {
Session session1 = sf.openSession();
session1.beginTransaction();
//进行一次查询
Dept dept = (Dept)session1.get(Dept.class,5);
dept.getEmps().size();
System.out.println(dept);
session1.getTransaction().commit();
session1.close();
System.out.println("开启第二次查询");
Session session2 = sf.openSession();
session2.beginTransaction();
dept = (Dept)session2.get(Dept.class,5);
dept.getEmps().size();
System.out.println(dept);
session2.getTransaction().commit();
session2.close();
}
不使用二级缓存的输出:可以得出两次查询都是同样的操作:向数据库查询
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept dept0_ where dept0_.deptId=?
Hibernate: select emps0_.dept_id as dept4_0_1_, emps0_.empId as empId1_, emps0_.empId as empId1_0_, emps0_.empName as empName1_0_, emps0_.salary as salary1_0_, emps0_.dept_id as dept4_1_0_ from t_employee emps0_ where emps0_.dept_id=?
Dept [deptId=5, deptName=技术部3, emps=[Employee [empId=10, empName=李四3], Employee [empId=9, empName=张三3]]]
开启第二次查询
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept dept0_ where dept0_.deptId=?
Hibernate: select emps0_.dept_id as dept4_0_1_, emps0_.empId as empId1_, emps0_.empId as empId1_0_, emps0_.empName as empName1_0_, emps0_.salary as salary1_0_, emps0_.dept_id as dept4_1_0_ from t_employee emps0_ where emps0_.dept_id=?
Dept [deptId=5, deptName=技术部3, emps=[Employee [empId=9, empName=张三3], Employee [empId=10, empName=李四3]]]
开启二级缓存的输出:第一次查询向数据库发送了SQL语句,第二次查询没有向数据库查询,而是从缓存中获取
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept dept0_ where dept0_.deptId=?
Hibernate: select emps0_.dept_id as dept4_0_1_, emps0_.empId as empId1_, emps0_.empId as empId1_0_, emps0_.empName as empName1_0_, emps0_.salary as salary1_0_, emps0_.dept_id as dept4_1_0_ from t_employee emps0_ where emps0_.dept_id=?
Dept [deptId=5, deptName=技术部3, emps=[Employee [empId=9, empName=张三3], Employee [empId=10, empName=李四3]]]
开启第二次查询
Dept [deptId=5, deptName=技术部3, emps=[Employee [empId=9, empName=张三3], Employee [empId=10, empName=李四3]]]
2.1 list二级缓存策略,可以使用setCacheable(true)显示指定从二级缓存获取或者放入二级缓存中
Session session1 = sf.openSession();
session1.beginTransaction();
//setCacheable 指定从二级缓存找,或者放入二级缓存
Query query = session1.createQuery("from Dept").setCacheable(true);
System.out.println(query.list());
session1.getTransaction().commit();
session1.close();
System.out.println("第二次查询");
Session session2 = sf.openSession();
session2.beginTransaction();
query = session2.createQuery("from Dept").setCacheable(true);
System.out.println(query.list());
session2.getTransaction().commit();
session2.close();
四. 项目中session的创建方式
session.openSession();方法每次都会创建一个新的session
session.getCurrentSession();方法则是从当前线程获取session或者创建session放入线程中,使用这种方式,需要在配置文件进行配置:
<property name="hibernate.current_session_context_class">thread</property>
测试代码
@Test
public void testSession() {
//openSession: 创建Session, 每次都会创建一个新的session
Session session1 = sf.openSession();
Session session2 = sf.openSession();
System.out.println(session1 == session2);
session1.close();
session2.close();
//getCurrentSession 创建或者获取session
// 线程的方式创建session
// 一定要配置:<property name="hibernate.current_session_context_class">thread</property>
Session session3 = sf.getCurrentSession();// 创建session,绑定到线程
Session session4 = sf.getCurrentSession();// 从当前访问线程获取session
System.out.println(session3 == session4);
// 关闭 【以线程方式创建的session,可以不用关闭; 线程结束session自动关闭】
//session3.close();
//session4.close(); 报错,因为同一个session已经关闭了!
}
结果输出:false true