hibernate(2)
Hibernate中的缓存机制
缓存的好处:
(1)减少访问数据库的频率。应用程序从内存中读取持久化对象的速度显然比到数据库中查询数据的速度快多了,因此Session的缓存可以提高数据访问的性能。
(2)保证缓存中的对象与数据库中的相关记录保持同步。当缓存中持久化对象的状态发生了变化,Session并不会立即执行相关的SQL语句,这使得Session能够把几条相关的SQL语句合并为一条SQL语句,以便减少访问数据库的次数,从而提高应用程序的性能。
一级缓存
基于sessoin的缓存!
特点:
1. 在短时间内多次操作数据库情况下,缓存效果比较明显!
2. session关闭后,就不能使用缓存内容!
不同的Session,能否共享一级缓存数据?
不能!
验证:
@Test
//如果产生两条sql,说明在不同的session中不能共享一级缓存、
public void test2(){
Session s1 = factory.openSession();
Session s2 = factory.openSession();
Object obj = s1.get(Person.class, 5);//一条
Object obj2 = s1.get(Person.class, 5);
Object obj3 = s2.get(Person.class, 5);//一条
}
总结:
缓存只在当前session有效,缓存时间短、作用范围小、总体来看缓存效果不明显。
一级缓存的管理
evict(Object obj) 将指定的持久化对象从一级缓存中清除,释放对象所占用的内存资源,指定对象从持久化状态变为脱管状态,从而成为游离对象。
clear() 将一级缓存中的所有持久化对象清除,释放其占用的内存资源。
contains(Object obj) 判断指定的对象是否存在于一级缓存中。
flush() 刷新一级缓存区的内容,使之与数据库数据保持同步。
一级缓存应用
- save()。当session对象调用save()方法保存一个对象后,该对象会被放入到session的缓存中。
- get()和load()。当session对象调用get()或load()方法从数据库取出一个对象后,该对象也会被放入到session的缓存中。 使用HQL和QBC等从数据库中查询数据。
二级缓存
基于应用程序的缓存、基于sessionFactory级别的缓存!
缓存数据可以被多个session共享! 但需要指定哪些对象要放入二级缓存中!
放入二级缓存中对象的特点:
- 经常使用
- 不会被经常修改!
二级缓存的数据在不同的session中可以共享,一级缓存不能共享。二级缓存以缓存框架的方式提供,需要自己配置,即其是可插配的缓存框架。
Hibernate提供的二级缓存是以缓存框架形式提供,hibernate提供了二级缓存框架默认的实现; 也支持其他二级缓存框架,如果要更换缓存,只要更换配置中具体的二级缓存框架使用的核心类即可! 可插配的缓存框架!
配置
Hibernate提供的二级缓存配置, hibenrate.properties配置文件
## disable the second-level cache 【二级缓存默认为关闭】
#hibernate.cache.use_second_level_cache false
## enable the query cache 【是否开启查询缓存】
#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
步骤
- 开启二级缓存
- 指定二级缓存具体实现框架
- 那些类如要加入二级缓存
测试
- 2个session
session1.get(1); 查询数据库
Session1.get(); (没有发送查询sql)session2.get(1); 从一级缓存中获取,没有,再找二级缓存,找到后就返回!
(没有发送查询sql, 说明应用成功!)@Test public void test2(){ Object o = null; Session s1 = factory.openSession(); Transaction tx1 = s1.beginTransaction(); o = s1.get(Person.class, 5);//发送一条sql System.out.println(o); tx1.commit(); s1.close(); Session s2 = factory.openSession(); Transaction tx2 = s2.beginTransaction(); o = s2.get(Person.class, 5);//没有发送sql System.out.println(o); tx2.commit(); s2.close(); }
1 开启二级缓存
hibernate.cfg.xml中配置property。
2 指定具体实现框架
2.1 开启 查询缓存
3 指定加入二级缓存的类
<!-- 三、二级缓存配置 -->
1) 开启二级缓存
<property name="hibernate.cache.use_second_level_cache">true</property>
2) 指定使用哪一种二级缓存具体实现框架
<property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
开启查询缓存
<property name="hibernate.cache.use_query_cache">true</property>
3) 加入二级缓存的类
<class-cache usage="read-write" class="cn.ustb.b_second_cache.Dept"/>
<class-cache usage="read-write" class="cn.ustb.b_second_cache.Employee"/>
集合缓存 (集合缓存,集合元素也要放入二级缓存)
<collection-cache usage="read-only" collection="cn.ustb.b_second_cache.Dept.employees"/>
缓存策略:
usage=”read-write” 二级缓存的数据可以读、写
usage=”read-only” 二级缓存的数据只读
usage=”nonstrict-read-write” 非严格读取
usage=”transactional” 基于事务的策略
更新数据,会不会通知一级缓存、二级缓存?
不会通知一级缓存,会通知二级缓存!
验证:
1. 对象放入session缓存
2. 修改对象
3. 再看一级缓存数据有没有改变!
已经知道,list()查询不会从一级缓存中查询,那么二级缓存呢?
默认也是不会的,query.setCacheable(true);
设置放入二级缓存或从二级缓存获取。
public class App {
private static SessionFactory sf;
static {
sf = new Configuration()
.configure()
.addClass(Dept.class)
.addClass(Employee.class)
.buildSessionFactory();
}
// 不能的Session,能否共享二级缓存数据!
@Test
public void test_session_cache() {
Dept dept = null;
/*
* Session1:
*/
Session session1 = sf.openSession();
session1.beginTransaction();
// 先查询
dept = (Dept) session1.get(Dept.class, 4);
System.out.println(dept.getEmployees());
session1.getTransaction().commit();
session1.close();
System.out.println("===============================");
/*
* Session2:
*/
Session session2 = sf.openSession();
session2.beginTransaction();
// 又查询
dept = (Dept) session2.get(Dept.class, 4); // 没有发送sql,说明从二级缓存获取数据成功!
System.out.println(dept.getEmployees());
session2.getTransaction().commit();
session2.close();
}
// hql 查询缓存
@Test
public void test_Query_cache() {
Dept dept = null;
/*
* Session1:
*/
Session session1 = sf.openSession();
session1.beginTransaction();
// hql 查询
Query q = session1.createQuery("from Dept").setCacheable(true);// 放入二级缓存或者从二级缓存中获取
q.list();
session1.getTransaction().commit();
session1.close();
System.out.println("===============================");
/*
* Session2:
*/
Session session2 = sf.openSession();
session2.beginTransaction();
// hql 查询
q = session2.createQuery("from Dept").setCacheable(true); // 放入二级缓存或者从二级缓存中获取
q.list();
session2.getTransaction().commit();
session2.close();
}
/*
* 更新数据,会不会通知一级缓存、二级缓存
* 不会通知一级缓存
*/
@Test
public void test_demo() {
Dept dept = null;
/*
* Session1:
*/
Session session1 = sf.openSession();
session1.beginTransaction();
//1 对象加入session缓存
dept = (Dept) session1.get(Dept.class , 3);
//2 修改
session1.createQuery("update Dept set deptName=? where id=?")
.setParameter(0, "HR")
.setParameter(1, 3)
.executeUpdate(); // 执行更新
dept = (Dept) session1.get(Dept.class , 3);
System.out.println(dept.getDeptName()); // 财务部
session1.getTransaction().commit();
session1.close();
}
/*
* 更新数据,会不会通知一级缓存、二级缓存
* 更新数据,二级缓存会监测到最新的结果!
* 更改数据,会通知二级缓存!
*/
@Test
public void test_demo2() {
Dept dept = null;
/*
* Session1:
*/
Session session1 = sf.openSession();
session1.beginTransaction();
//1 对象加入session缓存
dept = (Dept) session1.get(Dept.class , 3);
//2 修改
session1.createQuery("update Dept set deptName=? where id=?")
.setParameter(0, "45656+")
.setParameter(1, 3)
.executeUpdate(); // 执行更新
session1.getTransaction().commit();
session1.close();
System.out.println("===================");
/******另外的session*******/
session1 = sf.openSession();
session1.beginTransaction();
dept = (Dept) session1.get(Dept.class , 3); // HR
System.out.println(dept.getDeptName());
session1.getTransaction().commit();
session1.close();
}
}
HQL查询
连接查询+迫切关联查询
public class App_1_hql {
private static SessionFactory sf;
static {
sf = new Configuration()
.configure()
.addClass(Dept.class)
.addClass(Employee.class)
.buildSessionFactory();
}
//1. 1. 统计每个部门人数
@Test9
public void test_demo1() {
Session session = sf.openSession();
session.beginTransaction();
Query q = session.createQuery("select e.dept, count(*) from Employee e group by e.dept having count(*) > 1");
List<Object[]> list = q.list(); // 返回的每一行记录封装在Object[]数据中
// 数据中第一个元素: dept对象
// 数据中第二个元素: 统计的记录结果
session.getTransaction().commit();
session.close();
}
// 2. 统计部门及部门下的员工
@Test
public void test_dempo2() {
Session session = sf.openSession();
session.beginTransaction();
// 内连接
// Query q = session.createQuery("from Employee e inner join e.dept");// 多对一
// 或者,
// Query q = session.createQuery("from Dept d inner join d.employees");// 一对多
// 做外连接,
// Query q = session.createQuery("from Employee e left join e.dept");// 多对一
// 右外连接
Query q = session.createQuery("from Dept d right join d.employees");// 多对一
List<Object[]> list = q.list();
// 迭代: 显示部门名称与员工名称
for (int i = 0; i<list.size(); i++) {
Object[] values = list.get(i);
// 获取数组第一个元素
Employee e = (Employee) values[0];
// 获取数组第二个元素
Dept d = (Dept) values[1];
// 测试
System.out.println(e.getName() + "," + d.getDeptName());
}
session.getTransaction().commit();
session.close();
}
// 3. 迫切关联查询,始终会把“右表”数据填充到“左表”中!
@Test
public void test_dempo3() {
Session session = sf.openSession();
session.beginTransaction();
// 迫切内连接, 会自动封装数据
Query q = session.createQuery("from Employee e inner join fetch e.dept");
q.list();
// 迫切左外连接,
q = session.createQuery("from Employee e left join fetch e.dept");
q.list();
/*
* 如何解决jsp页面访问懒加载报错问题? 如:显示员工,同时显示员工关联的部门名称!
* 连接查询!
*
* 懒加载:
* 1) 先用一下数据, 会先查询出来
* 2) 强迫代理对象初始化
* 3) session不关闭
* 4)需要哪些数据,先查询出来! ---> hql 连接查询
* 总结:
* 要在session关闭前,把懒加载的数据拿出来!
*
*
*/
session.getTransaction().commit();
session.close();
}
@Test
public void test_dempo4() {
Session session = sf.openSession();
session.beginTransaction();
// N + 1
// 显示员工,以及员工的部门
Query q = session.createQuery("from Employee ");
List<Employee> list = q.list();
for (Employee e : list) {
System.out.println(e.getName() + "," + e.getDept().getDeptName());
}
System.out.println("========");
// N+1, 如何解决?
q = session.createQuery("from Employee e left join fetch e.dept ");
list = q.list();
for (Employee e : list) {
System.out.println(e.getName() + "," + e.getDept().getDeptName());
}
session.getTransaction().commit();
session.close();
}
//更行数据会不会通知一级缓存实验
public void test(){
org.hibernate.classic.Session s1 = sf.openSession();
}
}
QBC查询
public class App_2_criteria {
private static SessionFactory sf;
static {
sf = new Configuration()
.configure()
.addClass(Dept.class)
.addClass(Employee.class)
.buildSessionFactory();
}
//1. QBC 查询
@Test
public void test_criteria() {
Session session = sf.openSession();
session.beginTransaction();
/*Query接口中方法
Query q = session.createQuery("");
q.list();
q.uniqueResult();
q.iterate();
q.executeUpdate();
q.setFirstResult(0);
q.setMaxResults(0); */
// 获取Criteria查询接口
Criteria c = session.createCriteria(Dept.class);
// 1. 主键条件
//c.add(Restrictions.idEq(1));
// 2. 等值条件查询
//c.add(Restrictions.eq("deptName", "hr"));
// 3. 模糊查询
//c.add(Restrictions.like("deptName", "%h%"));
// 4. map 作为条件,map的key就是bean的属性
// Map<String,Object> map = new HashMap<String,Object>();
// map.put("deptName", "hr");
// map.put("id", 100);
// c.add(Restrictions.allEq(map));
// 5. example 查询
Dept dept = new Dept();
//dept.setId(1000); // 不起作用
dept.setDeptName("hr");
// 把对象的非主键属性作为条件 (自动把对象的非空属性值作为查询条件)
c.add(Example.create(dept));
// 查询全部
List<Dept> list = c.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
}
SQL原生查询
本地sql查询,session.createSQLQuery("");
,Hibernate除了支持面向对象的查询之外还支持原生态的sql语句查询。
优缺点:
1. 对于一些比较复杂的查询,本地sql可以作为补充。
2. 不能跨越数据库平台。
@Test
public void test_criteria() {
Session session = sf.openSession();
session.beginTransaction();
// 本地sql查询
// SQLQuery q = session.createSQLQuery("select * from t_dept");
// List<Object[]> list = q.list(); // 把每一行数据封装为Object[] ,再添加到list集合
// System.out.println(list);
// 把查询到的结果,自动封装为对象 (对象必须有映射文件)
SQLQuery q = session.createSQLQuery("select * from t_dept");
q.addEntity(Dept.class);
// 会自动封装对象
List<Dept> list = q.list();
System.out.println(list);
//session.getNamedQuery("");
session.getTransaction().commit();
session.close();
}
Hibernate对连接池的支持:C3P0
连接池:
管理连接,提高连接的使用效率!
C3P0:
开放的连接池组件。
Hibernate对连接的支持:
查看hibernate.properties
#################################
### Hibernate Connection Pool ###
#################################
hibernate.connection.pool_size 1 hibernate自带的连接池,只有一个连接!
###########################
### C3P0 Connection Pool### hibernate对C3p0连接池的支持
###########################
#hibernate.c3p0.max_size 2
#hibernate.c3p0.min_size 2
#hibernate.c3p0.timeout 5000
#hibernate.c3p0.max_statements 100
#hibernate.c3p0.idle_test_period 3000
#hibernate.c3p0.acquire_increment 2
#hibernate.c3p0.validate false
C3p0连接池驱动类
#hibernate.connection.provider_class org.hibernate.connection.C3P0ConnectionProvider
hibernate项目中使用c3p0连接池:
0. 引入c3p0驱动包
/c3p0-0.9.1.2.jar
/mysql-connector-java-5.1.7-bin.jar
1. 配置c3p0驱动类<session-factory>
中配置
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
2. 连接池参数配置<session-factory>
中配置
<!--
hibernate对连接池的支持
-->
<!-- C3p0连接池支持类, -->
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<!-- 最大连接数 -->
<property name="hibernate.c3p0.max_size">6</property>
<!-- 最小连接数 -->
<property name="hibernate.c3p0.min_size">4</property>
<!-- 当连接不够用时候每次的增量 -->
<property name="hibernate.c3p0.acquire_increment">2</property>
<!-- 最多执行的命令的个数 -->
<property name="hibernate.c3p0.max_statements">100</property>
<!-- 连接空闲测试时间 -->
<property name="hibernate.c3p0.idle_test_period">3000</property>
创建Session的两种方式
- openSession:非线程
- getCurrentSession():线程方式。需要在
<session-factory>
中配置线程方式创建session
<!-- 配置session的创建方式,线程方式创建session -->
<property name="hibernate.current_session_context_class">thread</property>
// 2种方式创建session
@Test
public void testSession() throws Exception {
System.out.println(sf);
// 方式1: sf.openSession() 每次都创建一个新的session!
Session session1 = sf.openSession();
Session session2 = sf.openSession();
System.out.println(session1 == session2); // false
session1.close();
session2.close();
// 方式2: 线程的方式创建session
// getCurrentSession() 先从当前线程获取session,没有获取到,就创建新的session
// 创建完成后,再绑定到当前线程!
Session session3 = sf.getCurrentSession();
Session session4 = sf.getCurrentSession();
System.out.println(session3 == session4); // true
session3.close();
//session4.close(); // 不能重复关闭
}
Hibernate事务管理
Hibernate本身不具备事务管理能力,委托给了底层的JDBC。
首先,事务具有以下性质:ACID
- 原子性(Atomicity)
- 一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序串行执行的一致。
- 隔离性(Isolation):事务的执行不受其他事务的影响,事务执行的中间结果对其他事务是透明的。
- 持久性(Durability):已经提交的事务,数据不会改变。
由于隔离不完全带来的问题有:
1. 脏读(dirty read):一个事务读到另一个事务还没提交的数据。
2. 不可重读(non-repeatable read):一次事务中,两次读取结果不一样,即一个事务中读取到了另一个事务修改并提交的数据。
3. 幻读(phantom read):幻读严格说来是不可重复读的一种,但是幻读指的是由于其他事务所做的插入操作,导致每次查询返回不同的结果集,指的是有数据被添加。而不可重读是指数据被更新或者减少了。
事务的隔离级别和并发程度时相互矛盾的:
隔离程度越好,并发性越差。
隔离程度越低,并发性越好。
关于缓存机制reference:
http://blog.csdn.net/dongnan591172113/article/details/7943652
http://itindex.net/detail/40541-hibernate-%E7%BC%93%E5%AD%98
关于session和sessinFctory:
http://blog.csdn.net/jiangxindu1/article/details/48037731
关于Hibernate及别的框架的一些总结:
http://blog.csdn.net/qhwc2009/article/details/46227201
关于Hibernnate的一些问题,应该好好看看的:
http://itindex.net/detail/40384-hibernate-%E9%9D%A2%E8%AF%95
http://blog.jobbole.com/40758/
http://blog.csdn.net/hi_kevin/article/details/7284155
温故而知新,谨记!