Hibernate3 第四天

【第一天】三个准备七个步骤

【第二天】一级缓存、一级缓存快照、一对多和多对多配置

【第三天内容回顾】

1、各种查询

  • 对象导航查询:配置信息不能出错,
  • 根据OID查询:get,load
  • HQL:用是也是非常多的
  • SQL:
  • QBC:完全的面向对象

2、查询优化:默认的hibernate的优化属性基本上都是最优值,当然,你可以根据需求进行改变

【今天学习内容】

  1. Hibernate事务支持(事务的隔离级别)
  2. session管理-(手动opensession,本地线程session)
  3. 二级缓存(基本二级缓存):结构、配置、使用(ehcache)
  4. 查询缓存的配置使用
  5. 性能监测—了解—看看二级缓存到底命中率是什么样的。(了解)
  6. JPA注解
  7. 其他:hbm反转生成等(自动生成一对多、对多多、一对多的hbm映射文件)

学习目标:

  1. Hibernate的二级缓存的配置和使用
  2. JPA注解的编写
  1. Hibernate事务支持

    1. 事务隔离性引发的问题和解决方案

      1. 什么是数据库事务?

Tom---1500--->jack

Tom -1500

Jack +1500

  1. 事务的4个特性

  1. 事务隔离性引发的问题?

跟隔离级别有关,隔离级别不够高的话,可能会引起各种问题

丢失更新:一个事务将另外一个事务提交的数据覆盖了

脏读:一个事务读取另外一个事务还没有提交的数据(读未提交)

小王:今天发工资,一天心不在焉,不断的拿着手机银行查工资,查出来了,一看5000

经理在提交 数据发工资的 过程中,小王的,当工资流程还没有处理结束的时候,经理发现

小王工资算错了(请假的钱没扣),经理回滚了发工资操作 ,扣钱

不可重复读:同一个事务两次拿到的数据是不一致的

小王看到了工资很开心,一直拿着手机银行不停的看,(经理将工资重新计算完成之后,重新下发),

此时小王突然工资少了3000,(备注,请假两天扣3000),小王心里很郁闷

幻读:读已提交

  1. 事务的隔离级别

如何选择隔离级别呢?

在使用数据库时候,隔离级别越高,安全性越高 ,性能越低

实际开发中,不会选择最高或者最低隔离级别,选择 READ_COMMITTED(oracle 默认)、REPEATABLE_READ (mysql默认)

  1. Hibernate设置隔离级别

    1. Hibernate如何设置隔离级别?

  1. 测试隔离级别—脏读(当前测试仅能在mysql中测试,咋们的隔离级别是1)

创建项目:

图书信息的保存和查询

基本步骤:搭建环境-建表-设置不同的隔离级别

创建包:cn.itcast.a_isolation

第一步:编写实体类、HBM、映射添加、建表测试

第二步:设置隔离级别

第三步:编码测试:---模拟脏读(读未提交)

构建两个方法(可通过junit来调试)来模拟两条事务:一个保存,一个查询。分别执行:

注意:Oracle不支持read uncommited isolation的隔离级别,仅可以在mysql下可以测试。

@Test

public
void testSave(){

Session session = HibernateUtils.openSession();

session.beginTransaction();

Book book = new Book();

book.setName("辟邪剑谱");

book.setPrice(9.9d);

//保存

session.save(book);

session.getTransaction().commit();

session.close();

}

@Test

public
void testQuery(){

Session session = HibernateUtils.openSession();

session.beginTransaction();

List<Book> list = session.createQuery("from Book").list();

System.out.println(list);

session.getTransaction().commit();

session.close();

}

  1. Session管理

    1. 如何管理session

  1. session管理的方法

对应配置说明:

简单的说:在单系统/单机的情况下,管理session的方式就是两种:程序(手动)管理和hibernate框架(线程)管理。

  1. 线程管理和程序管理的区别

目标:了解opensession和getCurrentSession的区别

要使用线程管理session,需要在配置文件中显式的打开

【编写代码】

@Test

public
void testSession()

{

//        /拿到两个session

Session session1 = HibernateUtils.openSession();

Session session2 = HibernateUtils.openSession();

//这两个对象是同一个对象吗?

System.out.println(session1.hashCode());

System.out.println(session2.hashCode());

session1.close();

session2.close();

/*********************************/

SessionFactory sessionFactory = HibernateUtils.getSessionFactory();

//报错:

//hibernate默认的管理session的方式是managed,这种方式不支持getCurrentSession方法的

//所以需要修改配置文件

//

Session session3 = sessionFactory.getCurrentSession();

Session session4 = sessionFactory.getCurrentSession();

//比较此时的两个session是否是同一个对象?

System.out.println(session3.hashCode());

System.out.println(session4.hashCode());

session3.close();

session4.close();

}

上述代码报错,

原因:

如何解决这个问题呢?

修改session管理的方式:

目标:测试两种线程管理的session对象的区别:

@Test

public
void testSession(){

// 程序中手动管理,也是默认的管理方式

Session session1 = HibernateUtils.openSession();

Session session2 = HibernateUtils.openSession();

//通过运行观察,我们发现,session的值是不一样的

//通过源码的查看,其实每次openSession都是重新打开了一个新的session

System.out.println(session1.hashCode());

System.out.println(session2.hashCode());

//        /关闭

session1.close();

session2.close();

//通过本地线程管理session

SessionFactory sessionFactory = HibernateUtils.getSessionFactory();

Session session3 = sessionFactory.getCurrentSession();

Session session4 = sessionFactory.getCurrentSession();

System.out.println(session3.hashCode());

System.out.println(session4.hashCode());

//由于是线程管理的方式,每次系统运行的时候,都会默认先去查找当前线程中,是否已经创建了一个session

//如果已经存在了,就直接返回session

//如果不存在,则new一个session

//session对象完全交由ThreadLocal管理,所以你没有必要手动去关闭session,直接还是交给Threadload去管理

//        /关闭代码可以省略

//        session3.close();

//        session4.close();

}

工具类的改造:(注意点:一定不要忘了在xml修改sessin管理的配置)

当线程关闭的时候,session会自动关闭!

  1. 二级缓存

    1. 二级缓存的相关概念

      1. 什么是缓存

javaweb应用的缓存一般分两种:页面缓存和数据缓存。我们这里的缓存是指数据缓存。

数据缓存的作用:缓存位于程序和数据库之间,可减少程序访问数据库频率。

  1. 什么是Hibernate的二级缓存(与一级缓存对比)

也称为sessionFactory级别的缓存,也称为session工厂缓存

Hibernate中提供了两个级别的缓存:

一级缓存 是session级别的缓存,它是属于事务范围的缓存,生命周期是session的生命周期, (一个线程 绑定一个Session, 对应一份一级缓存, 一级缓存无法实现多用户之间数据共享),它是hibernate的内置缓存,由hibernate管理的,一般情况下无需进行干预.

二级缓存 是sessionFactory 级别的缓存,它属于进程级别的缓存 (一个项目 只会对应一个SessionFactory对象, sessionFactory缓存数据 实现多用户之间共享 ),二级缓存是可插拔的。(解耦合思想)

  1. SessionFactory 级别的缓存分类

内置二级缓存缓存的是cfg.xml的数据和hbm.xml的数据

外置二级缓存默认是关闭的,是需要在cfg.xml文件中手动开启,否则无法使用,而且外置二级缓存是一个可插拔的配件

  1. 二级缓存的结构

Hibernate缓存的结构:英文版:

二级缓存一般分为"普通"二级缓存策略和查询缓存策略两种.但通常大家说的二级缓存主要是指普通的二级缓存策略.

内存存储区域:类级别的缓存区域,集合级别的缓存区域,更新时间戳,查询缓存的区域.

中文版:

  1. 二级缓存的并发策略

多读少改

从概念上说:

read-write策略:缓存数据既能读也能写(比如"经常"更新的数据)

read-only策略:缓存数据一般只用来读。(比如系统参数,地区的分类),并发效率高!

  1. 二级缓存的适用和不适用场景

缓存的适用场景就是多查少改。

  1. 二级缓存提供商

我们课程采用ehcache。

  1. ehcache基本介绍

Memcache:在集群环境中,使用非常多的

  1. 二级缓存的基本配置--使用EHCache进行示例

几件事情:

1、将ehcache的jar导入+核心文件配置

2、开启二级缓存,

3、配置二级缓存提供商(cfg.xml中配置)

步骤:

【第一步】 :导入ehcache的jar包(3个)

ehcache依赖 backport-util-concurrent 和 commons-logging

提示:本课程使用的是ehcache的1.x的版本,该版本需要依赖其他jar包,所以,你在导入jar的时候,别忘了其他两个。但2.x之后的版本,依赖jar都已经集成到jar中,所以不需要额外的第三方jar。

【第二步】:配置ehcache默认的核心配置文件ehcache.xml(名字固定)(放在类路径下)

解压 ehcache的jar ,将根目录 ehcache-failsafe.xml 改名为 ehcache.xml 复制 src

去掉注释:

【第三步】:配置Hibernate核心配置文件:启用二级缓存

在hibernate.cfg.xml中配置

【第四步】:配置二级缓存提供商

<!-- 指定二级缓存产品的提供商 -->

【第五步】:配置要缓存的目标数据(类)和并发策略(你要缓存哪些数据-对象)

这里可以有两种方法配置:

方法一:统一配置: 在hibernate.cfg.xml 进行配置(推荐)

方法二: 局部配置:在XXX.hbm.xml 进行配置

局部的配置会覆盖全局的配置。

  1. 二级缓存的使用

    1. 测试二级缓存的存在性

准备数据(手动插入数据):

测试:Book先toString

//如果二级缓存生效,那么再次跨session查询的时候,就不需要发出sql查询数据库。

@Test

public
void testSecondCacheExist(){

/**

* 证明二级缓存是存在的。

* 回顾:一级缓存的存在性是如何测试的?

* 答:在同一个session中查询两次,我们发现,第二次没有发出sql语句,

* 从而证明一级缓存是存在的

*

* 二级缓存的证明思路:

* 二级缓存是进程级别的缓存,一个程序只会开启一个sessionFactory,

* 任何一个用户获取的sessionFactory都是同一个sessionFactory,

* 所以使用二级缓存可以跨session共享数据

*

* 证明步骤:

* 1 开启一个session1,获取数据, 放入二级缓存,关闭一级缓存

* 2 开启session2,获取数据,看是否发出sql语句,如果不发出,表明是从二级缓存获取的数据

*

*/

/**********第一次查询*********/

Session session1 = HibernateUtils.openSession();

session1.beginTransaction();

/**

* 当开启了二级缓存之后,get干了三件时间

* 1 将数据放入一级缓存

* 2 将数据放入一级缓存的快照

* 3 将数据自动同步到二级缓存中

*/

Book book = (Book) session1.get(Book.class, 1);

System.out.println(book);

session1.getTransaction().commit();

session1.close();

/************第二次查询***************/

Session session2 = HibernateUtils.openSession();

session2.beginTransaction();

//由于开启了二级缓存,所以此处不会发sql语句,直接从二级缓存中查询数据

Book book2 = (Book) session2.get(Book.class, 1);

System.out.println(book2);

session2.getTransaction().commit();

session2.close();

}

查看输出结果。

  1. 二级缓存可以自动同步到一级缓存

结论:二级缓存可以自动同步到一级缓存,而且会自动引用散装数据的地址作为一级缓存的数据的地址.

任何的查询,都会往一级缓存放,一级缓存是hibernate的操作的核心缓存。

  1. 一级缓存自动同步到二级缓存(前提二级缓存打开)

@Test

//一级缓存自动同步到二级缓存

public
void testFirstCacheAutoFromSecondSession(){

Session session = HibernateUtils.openSession();

session.beginTransaction();

//数据放入一级缓存,同时也放入二级缓存

Book book1 =(Book)session.get(Book.class, 1);

System.out.println(book1.getName());

session.getTransaction().commit();

//关闭session,清空一级缓存

session.close();

Session session2 = HibernateUtils.openSession();

session2.beginTransaction();

//直接从二级缓存查询数据,不再发送sql语句

Book book3 =(Book)session2.get(Book.class, 1);

System.out.println(book3.getName());

session2.getTransaction().commit();

session2.close();

}

缓存同步的顺序是:默认先放一级缓存,然后再从一级缓存同步到二级缓存。二级缓存的数据是可以来自于一级缓存的。

  1. 更新时间戳区域—了解

功能:保证二级缓存的数据是最新的

作用和运行过程:

换个说法:

作用:记录hibernate对表中数据操作最后更新时间 。当有风吹草动的时候,用来判断要不要更新二级缓存中的数据。

运行过程:

1、 第一次查询,将结果放入二级缓存 ,查询时间 t1

2、 hibernate通过update 语句对表中数据 更新,同时更新时间戳区域,记录更新时间t2

3、 第二次查询, hibernate会自动去比较(只有发现是更改数据的那个时间戳的时候才会去自动比较)t1和t2的时间,如果t2>t1 ,在缓存后,数据被更新过,缓存的数据不是最新的,重新发送SQL进行查询,更新二级缓存区域

代码:

主要看有没有更新的操作,保证数据的及时性。

/**

* t1时刻,第一次发起查询,book的数据,会放入一级缓存、二级缓存

* t2时刻,更新数据库

* t3时刻,再次查询数据库,这时候拿到的是t1时刻的数据还是t2更新过后的数据呢?

*/

@Test

public
void testUpdateTimestamps(){

Session session = HibernateUtils.openSession();

session.beginTransaction();

//t1 时刻

Book book = (Book) session.get(Book.class, 1);

System.out.println(book);

//t2 时刻

//        想办法绕过缓存,直接更新数据库,Query都会发起对数据库的操作

//HQL:

session.createQuery("update Book set name ='菊花宝典' where id = 1 ").executeUpdate();

session.getTransaction().commit();

session.close();

Session session2 = HibernateUtils.openSession();

session2.beginTransaction();

//当此时去二级缓存那数据源的时候,hibernate会比较时间戳区域中的t1和t2时刻

//如果t2时刻在t1时刻之前,表示t1查出数据之后,数据库一直没有更新,那就直接返回二级缓存中的数据

//如果发现t2时刻在t1时刻之后,那表示查询出来之后,数据发生了改变,系统会自动再次发起查询,获取最新的数据

Book book2 = (Book) session2.get(Book.class, 1);

System.out.println(book2);

session2.getTransaction().commit();

session2.close();

}

目的:保证二级缓存最新。

注意:更新时间戳是有hibernate自动维护的,你不需要维护。

  1. 类级别缓存区域的存储特性-散装数据

散装数据:不是一个完整对象,是个Object[],当查询出来的时候,临时组装为对象(new PO().setter)。意味着,每次从二级缓存查询的数据的物理地址都不一样。

修改代码(要求:多个session)

@Test

public
void testSecondCacheSanZhuangdata(){

/**

* 证明:二级缓存的类缓存区域存放的是散装数据,是object[]类型的数组对象

* 证明思路:多次从二级缓存取数据,你会发现这些数据的hashcode是不一样的(值一样)

* 证明步骤:

* 1 开启session1,查询数据放入二级缓存,关闭session1

* 2 开启session2,从二级缓存取数据,打印hashcode,关闭session2

* 3 开启session3,从二级缓存取数据,打印hashcode,关闭session3

* 比较两次打印的hashcode是否一致

* 答案:不一致,表明二级缓存存放的是散装数据

*/

/************第一次查询*************/

Session session1 = HibernateUtils.openSession();

session1.beginTransaction();

//1 发sql语句 2 数据会被放入一级缓存和二级缓存

//此处运行的流程是:先放入一级缓存,再放入二级缓存

Book book1 = (Book) session1.get(Book.class, 1);

System.out.println(book1.hashCode());

//问题:book1和book2是同一个对象吗?

//答:是,因为book2是直接从一级缓存获取的

Book book2 = (Book) session1.get(Book.class, 1);

System.out.println(book2.hashCode());

session1.getTransaction().commit();

session1.close();

/************第二次查询*************/

Session session2 = HibernateUtils.openSession();

session2.beginTransaction();

//book3是从二级缓存取的数据

//取完之后,将这个数据放入了一级缓存和快照

Book book3 = (Book) session2.get(Book.class, 1);

System.out.println(book3.hashCode());

//book4是从一级缓存取的数据

//book3和book4是同一个对象吗?

//答:是同一个对象!!!

Book book4 = (Book) session2.get(Book.class, 1);

System.out.println(book4.hashCode());

session2.getTransaction().commit();

session2.close();

/************第三次查询*************/

Session session3 = HibernateUtils.openSession();

session3.beginTransaction();

//book5是从二级缓存取数据

//还会将数据存入一级缓存和快照

Book book5 = (Book) session3.get(Book.class, 1);

System.out.println(book5.hashCode());

//book6是从一级缓存取数据库

Book book6 = (Book) session3.get(Book.class, 1);

System.out.println(book6.hashCode());

session3.getTransaction().commit();

session3.close();

//最终是要证明 二级缓存存放的是散装数据

//注意比较book3和book5的hashcode是否一致

//答: 不一致,表明二级缓存存放的是散装数据

}

输出结果地址不同:

结论:每次从二级缓存获取数据,对象地址是不同的 !

原因:类级别的缓存区域存放的是散装数据,每次从二级缓存读取的时候,会重新创建新的对象。

理论图解

散装数据是Object[]的目的是为了存放不同的数据,为了通用!

当再次从二级缓存查询数据的时候。临时组装对象。

  1. 类级别的二级缓存的读写方式

get,load可读写,但Query.list只有写的功能没有读取的功能(可以用get来读取),

原因:二级缓存类级别的缓存区域的数据的读取必须是"通过#id"进行查询

@Test

public
void testSecondCacheWriteRead(){

/**

* 证明:query.list()不走二级缓存[Query对象不走缓存]

* 证明步骤:

* 1 开启session1,通过query.list方式获取数据,关闭session1(销毁一级缓存)

* 2 开启session2,继续通过query.list方式获取数据,观察是否发出sql语句

* 如果发出,表明query.list不走二级缓存

*/

/*************第一次query.list***********/

Session session1 = HibernateUtils.openSession();

session1.beginTransaction();

//发出sql语句,从数据库中查出所有的书

List<Book> list = session1.createQuery("from Book").list();

System.out.println(list);

//如果下面通过get的方式获取到数据,只能证明一级缓存有数据,不能证明二级缓存有数据

Book book = (Book) session1.get(Book.class, 1);

System.out.println(book);

session1.getTransaction().commit();

session1.close();

/*************第二次query.list***********/

Session session2 = HibernateUtils.openSession();

session2.beginTransaction();

//先证明一下二级缓存是有数据的

Book book2 = (Book) session2.get(Book.class, 1);

System.out.println(book2);

//        此时观察控制台是否发出sql语句,如果发出,表明即使二级缓存有数据,query.list也不走二级缓存

List<Book> list2 = session2.createQuery("from Book").list();

System.out.println(list2);

session2.getTransaction().commit();

session2.close();

}

  1. Query接口Iterator方法

Iterator与list的区别:

Query的list 方法,只能写入二级缓存,不能读取

Query的Iterator方法,返回迭代器,是可以读取二级缓存的

iterate方法--->查询出来集合(Iterator<对象>),但对象只有id,没其他属性,(像load),变量获取数据的时候,像load一样,优先从一级缓存、二级缓存获取数据。

将列表 变成一个个小的load操作了。

list方法和iterator方法有什么不一样的呢?请看下面的示例:

@Test

public
void testSecondCacheQueryIterate(){

/**

* 证明:query.iterate走二级缓存

* 证明步骤:

* 1 开启session1,将数据放入二级缓存

* 2 开启session2,通过query.iterate获取,观察是否发出sql语句,如果不发出,表明是从二级缓存取的数据

*/

/*******第一次查询******/

Session session1 = HibernateUtils.openSession();

session1.beginTransaction();

//先将数据放入二级缓存

List<Book> list = session1.createQuery("from Book").list();

System.out.println(list);

session1.getTransaction().commit();

session1.close();

/*********第二次查询************/

Session session2 = HibernateUtils.openSession();

session2.beginTransaction();

//通过iterate方法查询数据

Iterator<Book> iterate = session2.createQuery("from Book").iterate();

while(iterate.hasNext()){

System.out.println(iterate.next());

}

session2.getTransaction().commit();

session2.close();

}

结论:

Query.list()发出的语句是直接查询出所有字段的数据.

Query.iterate()发出的语句是仅查询出主键id的字段的数据

iterate.next()发出的语句是根据id来查询的,如果二级缓存中有对应记录,则不发出sql语句.

【小结】在实际开发中,对列表数据进行二级缓存操作,一般是list存进去,iterate取出来

  1. 关联集合级别的缓存区域存储特性

特性:关联集合级别的缓存区域只会缓存OID ,具体数据会保存类级别缓冲区中

数据准备:将上次课的Customer和order拿过来。放入新创建的cn.itcast.c_cache包中

配置:两种方法:

第一种:在hibernate.cfg.xml配置集合级别缓存(推荐)

第二种:在hbm中配置

测试代码:

@Test

public
void testCollectionCache(){

/**

* 证明:在关联集合缓存区域可以缓存集合对象

* 证明步骤:

* 1 开启session1,查询集合数据,保存到二级缓存中

* 2 开启session2,继续查询集合数据,观察是否发出sql语句,如果不发出,表明是从集合缓存区域获取的数据

*/

/***********第一次查询**********/

Session session1 = HibernateUtils.openSession();

session1.beginTransaction();

Customer customer1 = (Customer) session1.get(Customer.class, 1);

//此时会将集合的数据放入集合缓存区域

System.out.println(customer1.getOrders());

session1.getTransaction().commit();

session1.close();

/***********第二次查询**********/

Session session2 = HibernateUtils.openSession();

session2.beginTransaction();

//不发sql,直接从二级缓存获取

Customer customer2 = (Customer) session2.get(Customer.class, 1);

//此处发sql吗?答:不发,直接从集合缓存区域获取数据

System.out.println(customer2.getOrders());

session2.getTransaction().commit();

session2.close();

}

@Test

public
void
testCollectionSanzhuangData(){

/**

* 证明:关联集合缓存区域缓存的数据是散装数据

* 证明思路:创建三个session,比较session2和session3获取的集合的hashcode是否一致

* 证明步骤:

* 1 开启session1,查询数据,将数据放入二级缓存

* 2 开启session2,获取集合数据,打印hashcode

* 3 开启session3,获取集合数据,打印hashcode

* 比较第二次和第三次打印的hashcode是否一致,如果不一致,表明关联集合缓存区域存放的是散装数据

*/

/**********第一次查询*********/

Session session1 = HibernateUtils.openSession();

session1.beginTransaction();

Customer customer = (Customer) session1.get(Customer.class, 1);

//此时将数据存入二级缓存的集合缓存区域

System.out.println(customer.getOrders());

session1.getTransaction().commit();

session1.close();

/**********第二次查询*********/

Session session2 = HibernateUtils.openSession();

session2.beginTransaction();

Customer customer2 = (Customer) session2.get(Customer.class, 1);

//此时将数据存入二级缓存的集合缓存区域

System.out.println(customer2.getOrders().hashCode());

session2.getTransaction().commit();

session2.close();

/**********第三次查询*********/

Session session3 = HibernateUtils.openSession();

session3.beginTransaction();

Customer customer3 = (Customer) session3.get(Customer.class, 1);

//此时将数据存入二级缓存的集合缓存区域

System.out.println(customer3.getOrders().hashCode());

session3.getTransaction().commit();

session3.close();

}

图解:

通过分析,发现:

关联集合缓存区域只保存oid(关系),而且它必须依赖于类级别缓存区域来存储具体的po对象的数据。

缓存区的小结:

由于类缓存区域,缓存对象是散装数据(object[属性1,属性2,。。。。]),所以每次拿到的时候,都需要重新组装新的对象,然后将值放进去,返回,这时候我们发现,每次拿到的对象的hashcode都是不一样的。

集合缓存区必须依赖类缓存区,才能使用,他是类级别缓存策略的一个补充。缓存的属性OID。

  1. ehcache缓存的详细配置使用

    1. ehcache配置文件解读

使用ehcache 缓存框架,默认加载 src下 ehcache.xml 配置文件

  • <diskStore> 缓存数据在硬盘临时存储位置

默认查找系统环境变量 TEMP

  • <defaultCache> 默认缓存配置

JDK中垃圾回收机制的原理:(那些数据会被回收?)

1 失去引用的数据

2 当内存满了之后,回收那些不常用或者用的比较少的数据

LRU:最近最少使用:根据使用的频率来判断

A 元素 截至到一个时刻,它被访问了10次

B元素     截至到一个时刻,它被访问了9次

C元素     截至到一个时刻,它被访问了20次

问题:如果缓存满了的话,谁会被最新清除掉,答案是:B

LFU:最不常使用的:根据时间来判断

A 元素 最后一次访问的时间是00:10

B元素     最后一次访问的时间是00:09

C元素     最后一次访问的时间是00:20

问题:如果缓存满了的话,谁会被最新清除掉,,答案是:B

FIFO:先进先出--管道--根据顺序有关系

放入缓存的顺序:A---B---C

问题:如果缓存满了的话,谁会被最新清除掉,,答案是:A

  1. 缓存数据持久化到硬盘

目标:两个:自定义缓存区域设置,另外一个就是持久化到硬盘的参数配置。

为什么要自定义缓存呢?因为业务需要,可能不同的实体需要不同的缓存策略。

自定义的方式:使用<cache> 自定义缓存区域 , 通过name属性 标识缓存区域名称。

自定义echcache配置文件,并测试内存缓存区存满后,自动将数据持久化到硬盘:

修改ehcache.xml:

@Test

public
void
testOverflowToDisk()

{

Session session1 = HibernateUtils.openSession();

session1.beginTransaction();

//查询数据:绝对会放到缓存中

Customer customer = (Customer)session1.get(Customer.class, 1);

//获取集合,也会放入缓存

System.out.println(customer.getOrders().size());

session1.getTransaction().commit();

session1.close();

}

提示:由于sessionFactory在运行立刻关闭,缓存还没来得及写入到硬盘,这里可以使用断点暂停的方式来测试缓存写入硬盘。

提示:如果你在hibernate.cfg.xml中和hbm.xml中都配置了缓存的配置,那么会以hbm.xml为准。

一般会缓存:系统层面:参数,常量、业务层面:订单分类、经常查询的东西,人员信息。注意:缓存有容量。

会将数据存入数据库,然后当系统启动的时候,加载到缓存(内存)。spring会整合ehcache,很容易清除更改缓存的内容,无需重启系统。更便于维护。

  1. 查询缓存

上面的二级缓存只能通过load.get,query.iterate来获取.query.list是只能存,不能取.

  1. 什么是查询缓存

查询缓存是基本二级缓存的补充,也是二级缓存的一部分,是一种特殊的二级缓存。

主要用来保存经常查询的sql语句和"结果"。

查询缓存是一种特殊的二级缓存,有人称之为三级缓存。(根本没有三级缓存).

基本二级缓存和查询缓存的区别:(查询条件不同,返回结果不同)

基本二级缓存,查询条件是记录的id ,返回整条记录散装数据 --- 可封装为实体对象;

查询缓存,查询条件 使用任何sql语句,返回任何sql执行结果,缓存的结果更加灵活. (可解决query.list的无法读取二级缓存的缺陷)

但查询缓存有个缺点:每次必须用代码手动激活开启查询缓存.

使用查询缓存:不光要在hbm.xml中开启查询缓存,还需要在代码中每次手动激活查询缓存

  1. 查询缓存的应用场合

多查少改的场景

  1. 使用步骤

【注意】查询缓存依赖类缓存区域

  1. 设置二级缓存提供商

    扩展:一般情况下,使用查询缓存的时候,我们会同时开启基本的二级缓存策略。

  2. 开启查询缓存

  3. 程序中激活使用:必须激活

@Test

public
void testSelectCache(){

/**

* 证明:查询缓存是存在的,是可以缓存数据的

* 查询缓存主要是解决query.list不走二级缓存的缺陷

* 证明思路:

* 1 开启session1,将数据存入查询缓存

* 2 开启session2,调用query.list从查询缓存获取数据,不发sql语句

*

*/

/***********第一次查询**********/

Session session1 = HibernateUtils.openSession();

session1.beginTransaction();

//如果直接list,数据是不会存入查询缓存

List list = session1.createQuery("from Customer").setCacheable(true).list();

System.out.println(list);

session1.getTransaction().commit();

session1.close();

/***********第二次查询**********/

Session session2 = HibernateUtils.openSession();

session2.beginTransaction();

//直接从查询缓存获取数据,不会发sql语句

List list2 = session2.createQuery("from Customer").setCacheable(true).list();

System.out.println(list2);

session2.getTransaction().commit();

session2.close();

}

  1. 查询缓存原理分析

查询缓存的一般理解:

通过查询缓存来缓存的数据和读取原理(通过跟踪源码得到的结论):

作用:二级缓存不能缓存的内容,就可以在查询缓存中缓存。查询缓存又是二级缓存的补充。

但是,如果有一级缓存尽量用一级缓存,有普通二级缓存尽量用普通二级缓存,实在没办法了就用查询缓存.

  • 实体查询:(目标:验证如果是查询结果可封装为实体的,从查询缓存中取出的是散装数据)

@Test

//测试查询缓存对应的散装数据

public
void
testQueryCacheSanZhuang(){

//使用查询缓存

Session session = HibernateUtils.openSession();

session.beginTransaction();

//查询订单,进行缓存

Query query=session.createQuery("from Book");

//必须手动打开查询缓存:你放入查询缓存的时候需要手动打开

query.setCacheable(true);

List<Book> list=query.list();

System.out.println(list.get(0).hashCode());//一级缓存有,二级缓存有,查询缓存有。

session.getTransaction().commit();

session.close();

//------------------------------------

//一级缓存没了,二级缓存还有,查询缓存还有

Session session2 = HibernateUtils.openSession();

session2.beginTransaction();

//使用查询缓存

Query query2=session2.createQuery("from Book");

//你从查询缓存中取出的时候还要手动打开

query2.setCacheable(true);

List<Book> list2=query2.list();

System.out.println(list2.get(0).hashCode());

session2.getTransaction().commit();

session2.close();

//------------------------------------

//一级缓存没了,二级缓存还有,查询缓存还有

Session session3 = HibernateUtils.openSession();

session3.beginTransaction();

//使用查询缓存

Query query3=session3.createQuery("from Book");

//你从查询缓存中取出的时候还要手动打开

query3.setCacheable(true);

List<Book> list3=query3.list();

System.out.println(list3.get(0).hashCode());

session3.getTransaction().commit();

session3.close();

}

  • 投影查询(目标:验证如果是查询结果不可以封装为实体的,从查询缓存中取出的是同一个对象)

@Test

//测试查询缓存

public
void
testQueryCache(){

///放入

Session session = HibernateUtils.openSession();

session.beginTransaction();

//将查询结果放入查询缓存(一级缓存,基本二级缓存)

//        Query query = session.createQuery("select name from Book");

Query query = session.createQuery("select count(b) from Book b");

//手动开启查询缓存

query.setCacheable(true);//开启查询缓存的大门了,如果查询,则会将语句缓存到查询缓存。

//        List list =query.list();

//        System.out.println(list.get(0).hashCode());

long count1 =(Long) query.uniqueResult();

System.out.println(count1);

session.getTransaction().commit();

session.close();

//取出

Session session2 = HibernateUtils.openSession();

session2.beginTransaction();

//query.list来取二级缓存,普通的二级缓存是不能取到数据(by id),

//        Query query2 = session2.createQuery("select name from Book");

Query query2 = session2.createQuery("select count(b) from Book b");

query2.setCacheable(true);////开启查询缓存的大门了,就可以从查询缓存中查找需要的数据

//        List list2 = query2.list();

//        System.out.println(list2.get(0).hashCode());

long count2 =(Long) query2.uniqueResult();

System.out.println(count2);

session2.getTransaction().commit();

session2.close();

}

查询缓存小结:

  1. 查询缓存区存放的是键值对,key是sql语句,value是"结果"(如果不是实体,就直接存放最终结果,如果是实体,则存放oid数组,具体实体数据存放在类缓存区(散装数据).)
  2. 从某种意义上说,查询缓存策略不完全依赖于基本二级缓存的查询策略:

【缓存使用小结】

  • 一级缓存:在session中缓存数据,内置自带的缓存,无法关闭,还能更新数据(快照功能)。get load
  • 二级缓存:跨session来缓存数据了,任何查询都可以将数据放入缓存,但是取:get ,load,query.iterator(根据id来获取)
  • 查询缓存:上面都不能满足,直接缓存sql语句,和查询结果。取:相同的sql就能取了。
  1. 监测性能—了解

    1. 为什么要监测性能?

实际应用中,不是说配置了二级缓存就一定会有效果,需要通过某种途径查看缓存的使用率。

Hibernate SessionFactory 提供了一个统计功能(默认是关闭的):

如果这个功能,开启的话,系统在运行的时候,后台还要不断的统计,浪费效率,浪费资源

而且这个功能,正常是在测试的时候,开启功能

Statistics

getStatistics()
          Get the statistics for this session factory

通过Statistics这个对象,对二级缓存和查询缓存 进行使用监控

采用会话工厂的统计方法SessionFactory.getStatistics(),包含二级缓存的统计

  1. 监测步骤

  1. 在hibernate.cfg.xml开启统计功能

<!-- 开启统计-监控-->

<property
name="hibernate.generate_statistics">true</property>

  1. 程序中通过SessionFactory 获取监控数据

@Test

//缓存使用率性能监控(前提是开启了性能监视)

public
void testStatistics(){

SessionFactory sessionFactory = HibernateUtils.getSessionFactory();

//通过会话工厂,获取统计对象

Statistics statistics = sessionFactory.getStatistics();

System.out.println("二级缓存的命中次数:"+statistics.getSecondLevelCacheHitCount());

System.out.println("二级缓存的丢失次数:"+statistics.getSecondLevelCacheMissCount());

Session session1 = sessionFactory.openSession();

//原理讲解

条订单信息,放入二级缓存

session1.createQuery("from Book where id<=2").list();

session1.close();

System.out.println("-换一个session-------------------------");

Session session2 = sessionFactory.openSession();

号订单

Book book1 = (Book) session2.get(Book.class, 1);

System.out.println(book1);

Book book2 = (Book) session2.get(Book.class, 2);

System.out.println(book2);

System.out.println("二级缓存的命中次数:"+statistics.getSecondLevelCacheHitCount());

System.out.println("二级缓存的丢失次数:"+statistics.getSecondLevelCacheMissCount());

System.out.println("-------------------------");

号订单

Book book4 = (Book) session2.get(Book.class, 3);

System.out.println(book4);

System.out.println("二级缓存的命中次数:"+statistics.getSecondLevelCacheHitCount());

System.out.println("二级缓存的丢失次数:"+statistics.getSecondLevelCacheMissCount());

session2.close();

}

提示:平时系统正常运行的时候,不要开这个功能,消耗性能。

  1. JPA注解开发

jpa是sun公司的一个ORM规范,只有接口和注解,没有具体实现。

jpa是EJB3中的。

Hibernate官网中有这么一句话:

hibernate中有两套注解规范:一套jpa,一套自己的;

使用注解开发,开发效率高!

  1. 单表常用注解

新建工程:Hibernate3_day04_jpa

导入jar、配置文件、工具类等

第一步:在cn.itcast.a_single包中建立Book实体

第二步:最简注解示例: (使用了注解的默认值)

【最最小化配置】:

第三步:Hibernate.cfg.xml配置映射:

建表测试

【推荐标准最小化配置】:

更多常用注解注解

实体和表本身相关:

主键相关的:

Auto相当于native,默认值

自定义主键策略(下面使用hibernate的实现):

自定义主键字段:

其他字段相关的:

属性字段官方参考配置:

【较完整配置】:

【补充】:

注解:可以放到属性上面设置,也可以在getter方法上设置,效果一样。但是:要么都放属性,要么都放getter,不能混着用。

  1. 多表常用注解

    1. 一对多

新建订单表实体类,并与客户表建立实体关系。

最小化配置:

推荐配置:

更多详细配置:

Customer.java

Order.java

映射文件加入到核心文件:

建表测试。。。--单表可能不报错,多表会报错

结果可能报错:

原因(包冲突了) javaee.jar 包含不完整JPA规范;解决:手动删除 javaee.jar中 javax.persistence 包

  1. 多对多

示例:学生和课程

建立实体类,并加上注解:

【最小化配置】:

【推荐配置】

【更多配置】:

在核心配置文件中配置映射:

建表测试:。。。。。

【注意】:

  1. 命名查询NamedQuery

@NamedQuery 定义了命名查询语句,可以通过session.getNamedQuery()获取使用

具体写法如下:

测试:

public
void test02(){

Session session= HibernateUtils.getCurrentSession();

session.beginTransaction();

//save

//        Customer c =new Customer();

//        c.setName("xiaoming");

//

//        Order o = new Order();

//        o.setName("电视机");

//        o.setPrice(998d);

//        Order o2 = new Order();

//        o2.setName("电视机2");

//        o2.setPrice(9981d);

//

//        c.getOrders().add(o);

//

//        session.save(c);

//query

List<Customer> list = session.getNamedQuery("Customer.findAll").list();

System.out.println(list.get(0).getOrders());

Customer c =(Customer)session.get(Customer.class, 1);

System.out.println(c.getOrders().size());

session.getTransaction().commit();

//session.close();

}

【说明】发现在javax.persistence.* 与org.hibernate.annotations.*包中,都有NamedQuery包,导入哪个包都一样,但是在使用NamedQuery和NamedQueries的时候,需要导入统一的包中的资源

  1. 抓取策略

类抓取策略:

关联集合抓取策略:

jpa的:

Hibernate的:

一方:

多方:

  1. 缓存策略

一定要在Hibernate的灵魂文件中配置二级缓存,否则注解是无效,而且运行会报错

在实体上打开二级缓存策略:

也可以在核心文件配置,但建议是在实体上配置。

类级别的二级缓存策略

集合级别的二级缓存

最终:

//客户:一方

@Entity

@Table(name="t_customer")

//hql

@NamedQuery(name="Customer.findAll" ,query="from Customer")//配置一个命名查询

@NamedQueries({@NamedQuery(name="Customer.findAll" ,query="from Customer"),@NamedQuery(name="Customer.findAll2" ,query="from Customer")})

//sql

@NamedNativeQuery(name="Customer.findcount",query="select count(*) from t_customer")//配置一个

@NamedNativeQueries({@NamedNativeQuery(name="Customer.findcount",query="select count(*) from t_customer"),@NamedNativeQuery(name="Customer.findcount2",query="select count(*) from t_customer")})

@Cacheable(true)//jpa打开了缓存

@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)

public
class Customer {

@Id

@GeneratedValue

private Integer id;//oid属性

private String name;

private String city;

//关联集合属性

@OneToMany(mappedBy="customer"//自己在关联的对象中的属性(告诉jpa,两个对象怎么关联的,一方的id关联的)

,cascade=CascadeType.ALL//级联配置

,fetch=FetchType.LAZY//是否懒加载,默认是懒加载

,targetEntity=cn.itcast.hibernate.a_singlepo.Order.class//orders里面的元素对应的实体类的类型

//实体类可以有接口或抽象类,这里配置的实现类

)

@Fetch(FetchMode.SELECT)//hibernate的抓取策略

@LazyCollection(LazyCollectionOption.TRUE)//hiernate

@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)

private Set<Order> orders = new HashSet<Order>();

  1. 扩展

    1. myeclipse工具反转生成hbm映射

hibernate初衷:通过设计po(domain领域模型设计),来映射实际表。

但企业级开发一般都是先设计表,在根据表来编写hbm映射。

对于已经存在的表,我们可以借助myeclipse的工具内置的Hibernate反转引擎,来根据表自动生成hbm、实体类等

第一步:在数据库视图中建立数据库连接

切换myeclipse视图到Database Explorer

建立数据库连接

第二步:建立存放生成Hibernate相关内容的web项目临时载体

新建web工程

对web项目添加Hibernate能力支持(目的是导入必要的jar,--是myeclipse给你导入的)

上面用来添加jar包的

自动生成了 依赖包 以及核心配置文件:

第三步:反转生成

切换回数据视图。

生成hbm:

建议输入包:你将来工程用的什么包,你这里就指定什么包。如果你不指定,则里面的内容需要修改。

结果:

生成JPA注解方式:

修改默认生成的类名:

自定义的类名要加上包名

生成结果:

最后,你就可以将,生成东东,拷贝到你的正式工程中了..

多对多注意:

  1. 小结和重点

  1. 缓存的概念自己体会
  2. 二级缓存的配置和使用
  3. 查询缓存的配置和使用
  4. ehcache
  5. jpa-主要熟悉单表,一对多
  6. 反转生成

整个Hibernate的核心内容:

  1. CRUD(save,update,快照更新,delete,get,load,query.list)
  2. 理论:一级缓存,快照原理,二级缓存、查询缓存。
  3. 性能优化:抓取策略、缓存的优化
  4. 多表:导航查询(当单表用)
  5. Jpa会配置

Hibernate3 第四天的更多相关文章

  1. 【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】

    一.JDBC编程特点 静态代码+动态变量=JDBC编程. 静态代码:比如所有的数据库连接池 都实现了DataSource接口,都实现了Connection接口. 动态变量:用户名.密码.连接的数据库. ...

  2. SSH整合(struts2.3.24+hibernate3.6.10+spring4.3.2+mysql5.5+myeclipse8.5+tomcat6+jdk1.6)

    终于开始了ssh的整合,虽然现在比较推崇的是,ssm(springmvc+spring+mybatis)这种框架搭配确实比ssh有吸引力,因为一方面springmvc本身就是遵循spring标准,所以 ...

  3. SSH:Struts2.2+Hibernate3.6+Spring3.1分页示例[转]

    参考资料 1 ssh分页(多个例子) http://useryouyou.iteye.com/blog/593954 2 ssh2分页例子 http://459104018-qq-com.iteye. ...

  4. Hibernate3.3.2 手动配置annotation环境

    简单记录Hibernate3.3.2如何快速配置环境 一.下载hibernate-distribution-3.3.2.GA-dist.zip文件,建立User libraries. 打开window ...

  5. Spring3 整合Hibernate3.5 动态切换SessionFactory (切换数据库方言)

    一.缘由 上一篇文章Spring3.3 整合 Hibernate3.MyBatis3.2 配置多数据源/动态切换数据源 方法介绍到了怎么样在Sping.MyBatis.Hibernate整合的应用中动 ...

  6. Jbpm4.4+hibernate3.5.4+spring3.0.4+struts2.1.8整合例子(附完整的请假流程例子,jbpm基础,常见问题解决)

    Jbpm4.4+hibernate3.5.4+spring3.0.4+struts2.1.8 整合例子(附完整的请假流程例子). 1.jbpm4.4 测试环境搭建 2.Jbpm4.4+hibernat ...

  7. [转]Hibernate3如何解决n+1 selects

    摘自: http://blog.chinaunix.net/uid-20586655-id-287959.html     Hibernate3中取得多层数据的所产生的n+1 selects问题的解决 ...

  8. spring2.5整合hibernate3.0整合Struts

    首先:这是spring framework+hibernate+struts集成,spring主要用于aop和ioc,hibernate主要是用于持久层,struts主要是用于mvc. 同时关于spr ...

  9. Struct2、Hibernate3、Spring3框架搭建实战(转)

    采用目前最新的struts-2.3.1.2.hibernate3.6.10.Final.spring-framework-3.1.1.RELEASE开发包,以及eclipse-jee-indigo-S ...

随机推荐

  1. 当我们在谈论kmeans(5)

    本系列意在长期连载分享,内容上可能也会有所删改: 因此如果转载,请务必保留源地址,非常感谢! 博客园:http://www.cnblogs.com/data-miner/(暂时公式显示有问题) 其他: ...

  2. Kattis - Peragrams

    Peragrams Photo by Ross Beresford Per recently learned about palindromes. Now he wants to tell us ab ...

  3. cocoaPods第三方库使用详解

    终端上安装了cocoapods后,打开终端输入下面命令: cd /Users/Sivek_lin/Desktop/AFNTest/AFNTest touch podfile pod search af ...

  4. netty(4)高级篇-Websocket协议开发

    一.HTTP协议的弊端 将HTTP协议的主要弊端总结如下: (1) 半双工协议:可以在客户端和服务端2个方向上传输,但是不能同时传输.同一时刻,只能在一个方向上传输. (2) HTTP消息冗长:相比于 ...

  5. LED的串联电阻值的计算

    与LED串联的电阻被用于控制该LED导通时的电流量.为了计算电阻值,你需要知道输入电源电压(Vs,一般为5V),LED的正向电压(Vf)和你需要流过LED的电源(/)的数值. 其电阻欧姆值的计算公式( ...

  6. 面试题-JDBC

    1.什么是JDBC? JDBC是允许用户在不同数据库之间做选择的一个抽象层.JDBC允许开发者用JAVA写数据库应用程序,而不需要关心底层特定数据库的细节. 2.解释下驱动(Driver)在JDBC中 ...

  7. iOS杂货

    iOS 导航栏TitleView居中的问题 titleVIew 默认情况下 是居中显示的,出现不居中的情况原因有两个:1,leftBarButtonItem,和rightBarButtonItem 留 ...

  8. android log4j日志管理的使用

    以下为log4j1的日志管理,在android 6.0 一下能正常使用,时候更加高级的胃log4j2,持续跟新 android中的log4j日志文件使用需要两个包,我们不需要进行配置文件的配置,一切都 ...

  9. PDF在线阅读 FlexPaper 惰性加载 ;

    关于PDF在线阅读问题,比较普遍的做法是转换成swf文件来浏览:由于项目需要,就用 flexpaper 来实现了下,功能比较简单:但是文件的惰性加载确实让笔者挠头了一把! 下面是笔者的方法: 采用流的 ...

  10. zabbix 布署实践【5 使用邮箱SMTP SSL推送告警邮件】

    由于传统的邮件推送脚本使用smtp 25端口,在各大邮箱提供商已不适用,已经向SSL过渡,这里以QQ邮箱为例,使用SSL 465端口 登录zabbix-server 进入 cd /usr/lib/za ...