Hibernate3 第四天
【第一天】三个准备七个步骤
【第二天】一级缓存、一级缓存快照、一对多和多对多配置
【第三天内容回顾】
1、各种查询
- 对象导航查询:配置信息不能出错,
- 根据OID查询:get,load
- HQL:用是也是非常多的
- SQL:
- QBC:完全的面向对象
2、查询优化:默认的hibernate的优化属性基本上都是最优值,当然,你可以根据需求进行改变
【今天学习内容】
- Hibernate事务支持(事务的隔离级别)
- session管理-(手动opensession,本地线程session)
- 二级缓存(基本二级缓存):结构、配置、使用(ehcache)
- 查询缓存的配置使用
- 性能监测—了解—看看二级缓存到底命中率是什么样的。(了解)
- JPA注解
- 其他:hbm反转生成等(自动生成一对多、对多多、一对多的hbm映射文件)
学习目标:
- Hibernate的二级缓存的配置和使用
- JPA注解的编写
-
Hibernate事务支持
-
事务隔离性引发的问题和解决方案
什么是数据库事务?
-
Tom---1500--->jack
Tom -1500
Jack +1500
事务的4个特性
事务隔离性引发的问题?
跟隔离级别有关,隔离级别不够高的话,可能会引起各种问题
丢失更新:一个事务将另外一个事务提交的数据覆盖了
脏读:一个事务读取另外一个事务还没有提交的数据(读未提交)
小王:今天发工资,一天心不在焉,不断的拿着手机银行查工资,查出来了,一看5000
经理在提交 数据发工资的 过程中,小王的,当工资流程还没有处理结束的时候,经理发现
小王工资算错了(请假的钱没扣),经理回滚了发工资操作 ,扣钱
不可重复读:同一个事务两次拿到的数据是不一致的
小王看到了工资很开心,一直拿着手机银行不停的看,(经理将工资重新计算完成之后,重新下发),
此时小王突然工资少了3000,(备注,请假两天扣3000),小王心里很郁闷
幻读:读已提交
事务的隔离级别
如何选择隔离级别呢?
在使用数据库时候,隔离级别越高,安全性越高 ,性能越低
实际开发中,不会选择最高或者最低隔离级别,选择 READ_COMMITTED(oracle 默认)、REPEATABLE_READ (mysql默认)
-
Hibernate设置隔离级别
Hibernate如何设置隔离级别?
测试隔离级别—脏读(当前测试仅能在mysql中测试,咋们的隔离级别是1)
创建项目:
图书信息的保存和查询
基本步骤:搭建环境-建表-设置不同的隔离级别
创建包:cn.itcast.a_isolation
第一步:编写实体类、HBM、映射添加、建表测试
第二步:设置隔离级别
第三步:编码测试:---模拟脏读(读未提交)
构建两个方法(可通过junit来调试)来模拟两条事务:一个保存,一个查询。分别执行:
注意:Oracle不支持read uncommited isolation的隔离级别,仅可以在mysql下可以测试。
@Test public 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 Session session = HibernateUtils.openSession(); session.beginTransaction(); List<Book> list = session.createQuery("from Book").list(); System.out.println(list); session.getTransaction().commit(); session.close(); } |
-
Session管理
如何管理session
session管理的方法
对应配置说明:
简单的说:在单系统/单机的情况下,管理session的方式就是两种:程序(手动)管理和hibernate框架(线程)管理。
线程管理和程序管理的区别
目标:了解opensession和getCurrentSession的区别
要使用线程管理session,需要在配置文件中显式的打开
【编写代码】
@Test public { // /拿到两个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 // 程序中手动管理,也是默认的管理方式 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会自动关闭!
-
二级缓存
-
二级缓存的相关概念
什么是缓存
-
javaweb应用的缓存一般分两种:页面缓存和数据缓存。我们这里的缓存是指数据缓存。
数据缓存的作用:缓存位于程序和数据库之间,可减少程序访问数据库频率。
什么是Hibernate的二级缓存(与一级缓存对比)
也称为sessionFactory级别的缓存,也称为session工厂缓存
Hibernate中提供了两个级别的缓存:
一级缓存 是session级别的缓存,它是属于事务范围的缓存,生命周期是session的生命周期, (一个线程 绑定一个Session, 对应一份一级缓存, 一级缓存无法实现多用户之间数据共享),它是hibernate的内置缓存,由hibernate管理的,一般情况下无需进行干预.
二级缓存 是sessionFactory 级别的缓存,它属于进程级别的缓存 (一个项目 只会对应一个SessionFactory对象, sessionFactory缓存数据 实现多用户之间共享 ),二级缓存是可插拔的。(解耦合思想)
SessionFactory 级别的缓存分类
内置二级缓存缓存的是cfg.xml的数据和hbm.xml的数据
外置二级缓存默认是关闭的,是需要在cfg.xml文件中手动开启,否则无法使用,而且外置二级缓存是一个可插拔的配件
二级缓存的结构
Hibernate缓存的结构:英文版:
二级缓存一般分为"普通"二级缓存策略和查询缓存策略两种.但通常大家说的二级缓存主要是指普通的二级缓存策略.
内存存储区域:类级别的缓存区域,集合级别的缓存区域,更新时间戳,查询缓存的区域.
中文版:
二级缓存的并发策略
多读少改
从概念上说:
read-write策略:缓存数据既能读也能写(比如"经常"更新的数据)
read-only策略:缓存数据一般只用来读。(比如系统参数,地区的分类),并发效率高!
二级缓存的适用和不适用场景
缓存的适用场景就是多查少改。
二级缓存提供商
我们课程采用ehcache。
ehcache基本介绍
Memcache:在集群环境中,使用非常多的
二级缓存的基本配置--使用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 进行配置
局部的配置会覆盖全局的配置。
-
二级缓存的使用
测试二级缓存的存在性
准备数据(手动插入数据):
测试:Book先toString
//如果二级缓存生效,那么再次跨session查询的时候,就不需要发出sql查询数据库。
@Test public /** * 证明二级缓存是存在的。 * 回顾:一级缓存的存在性是如何测试的? * 答:在同一个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(); } |
查看输出结果。
二级缓存可以自动同步到一级缓存
结论:二级缓存可以自动同步到一级缓存,而且会自动引用散装数据的地址作为一级缓存的数据的地址.
任何的查询,都会往一级缓存放,一级缓存是hibernate的操作的核心缓存。
一级缓存自动同步到二级缓存(前提二级缓存打开)
@Test //一级缓存自动同步到二级缓存 public 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(); } |
缓存同步的顺序是:默认先放一级缓存,然后再从一级缓存同步到二级缓存。二级缓存的数据是可以来自于一级缓存的。
更新时间戳区域—了解
功能:保证二级缓存的数据是最新的
作用和运行过程:
换个说法:
作用:记录hibernate对表中数据操作最后更新时间 。当有风吹草动的时候,用来判断要不要更新二级缓存中的数据。
运行过程:
1、 第一次查询,将结果放入二级缓存 ,查询时间 t1
2、 hibernate通过update 语句对表中数据 更新,同时更新时间戳区域,记录更新时间t2
3、 第二次查询, hibernate会自动去比较(只有发现是更改数据的那个时间戳的时候才会去自动比较)t1和t2的时间,如果t2>t1 ,在缓存后,数据被更新过,缓存的数据不是最新的,重新发送SQL进行查询,更新二级缓存区域
代码:
主要看有没有更新的操作,保证数据的及时性。
/** * t1时刻,第一次发起查询,book的数据,会放入一级缓存、二级缓存 * t2时刻,更新数据库 * t3时刻,再次查询数据库,这时候拿到的是t1时刻的数据还是t2更新过后的数据呢? */ @Test public 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自动维护的,你不需要维护。
类级别缓存区域的存储特性-散装数据
散装数据:不是一个完整对象,是个Object[],当查询出来的时候,临时组装为对象(new PO().setter)。意味着,每次从二级缓存查询的数据的物理地址都不一样。
修改代码(要求:多个session)
@Test public /** * 证明:二级缓存的类缓存区域存放的是散装数据,是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[]的目的是为了存放不同的数据,为了通用!
当再次从二级缓存查询数据的时候。临时组装对象。
类级别的二级缓存的读写方式
get,load可读写,但Query.list只有写的功能没有读取的功能(可以用get来读取),
原因:二级缓存类级别的缓存区域的数据的读取必须是"通过#id"进行查询
@Test public /** * 证明: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(); } |
Query接口Iterator方法
Iterator与list的区别:
Query的list 方法,只能写入二级缓存,不能读取
Query的Iterator方法,返回迭代器,是可以读取二级缓存的
iterate方法--->查询出来集合(Iterator<对象>),但对象只有id,没其他属性,(像load),变量获取数据的时候,像load一样,优先从一级缓存、二级缓存获取数据。
将列表 变成一个个小的load操作了。
list方法和iterator方法有什么不一样的呢?请看下面的示例:
@Test public /** * 证明: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取出来
关联集合级别的缓存区域存储特性
特性:关联集合级别的缓存区域只会缓存OID ,具体数据会保存类级别缓冲区中
数据准备:将上次课的Customer和order拿过来。放入新创建的cn.itcast.c_cache包中
配置:两种方法:
第一种:在hibernate.cfg.xml配置集合级别缓存(推荐)
第二种:在hbm中配置
测试代码:
@Test public /** * 证明:在关联集合缓存区域可以缓存集合对象 * 证明步骤: * 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 /** * 证明:关联集合缓存区域缓存的数据是散装数据 * 证明思路:创建三个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。
-
ehcache缓存的详细配置使用
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 |
缓存数据持久化到硬盘
目标:两个:自定义缓存区域设置,另外一个就是持久化到硬盘的参数配置。
为什么要自定义缓存呢?因为业务需要,可能不同的实体需要不同的缓存策略。
自定义的方式:使用<cache> 自定义缓存区域 , 通过name属性 标识缓存区域名称。
自定义echcache配置文件,并测试内存缓存区存满后,自动将数据持久化到硬盘:
修改ehcache.xml:
@Test public { 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,很容易清除更改缓存的内容,无需重启系统。更便于维护。
查询缓存
上面的二级缓存只能通过load.get,query.iterate来获取.query.list是只能存,不能取.
什么是查询缓存
查询缓存是基本二级缓存的补充,也是二级缓存的一部分,是一种特殊的二级缓存。
主要用来保存经常查询的sql语句和"结果"。
查询缓存是一种特殊的二级缓存,有人称之为三级缓存。(根本没有三级缓存).
基本二级缓存和查询缓存的区别:(查询条件不同,返回结果不同)
基本二级缓存,查询条件是记录的id ,返回整条记录散装数据 --- 可封装为实体对象;
查询缓存,查询条件 使用任何sql语句,返回任何sql执行结果,缓存的结果更加灵活. (可解决query.list的无法读取二级缓存的缺陷)
但查询缓存有个缺点:每次必须用代码手动激活开启查询缓存.
使用查询缓存:不光要在hbm.xml中开启查询缓存,还需要在代码中每次手动激活查询缓存
查询缓存的应用场合
多查少改的场景
使用步骤
【注意】查询缓存依赖类缓存区域
-
设置二级缓存提供商
扩展:一般情况下,使用查询缓存的时候,我们会同时开启基本的二级缓存策略。
-
开启查询缓存
- 程序中激活使用:必须激活
@Test public /** * 证明:查询缓存是存在的,是可以缓存数据的 * 查询缓存主要是解决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(); } |
查询缓存原理分析
查询缓存的一般理解:
通过查询缓存来缓存的数据和读取原理(通过跟踪源码得到的结论):
作用:二级缓存不能缓存的内容,就可以在查询缓存中缓存。查询缓存又是二级缓存的补充。
但是,如果有一级缓存尽量用一级缓存,有普通二级缓存尽量用普通二级缓存,实在没办法了就用查询缓存.
- 实体查询:(目标:验证如果是查询结果可封装为实体的,从查询缓存中取出的是散装数据)
@Test //测试查询缓存对应的散装数据 public //使用查询缓存 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 ///放入 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(); } |
查询缓存小结:
- 查询缓存区存放的是键值对,key是sql语句,value是"结果"(如果不是实体,就直接存放最终结果,如果是实体,则存放oid数组,具体实体数据存放在类缓存区(散装数据).)
- 从某种意义上说,查询缓存策略不完全依赖于基本二级缓存的查询策略:
【缓存使用小结】
- 一级缓存:在session中缓存数据,内置自带的缓存,无法关闭,还能更新数据(快照功能)。get load
- 二级缓存:跨session来缓存数据了,任何查询都可以将数据放入缓存,但是取:get ,load,query.iterator(根据id来获取)
- 查询缓存:上面都不能满足,直接缓存sql语句,和查询结果。取:相同的sql就能取了。
-
监测性能—了解
为什么要监测性能?
实际应用中,不是说配置了二级缓存就一定会有效果,需要通过某种途径查看缓存的使用率。
Hibernate SessionFactory 提供了一个统计功能(默认是关闭的):
如果这个功能,开启的话,系统在运行的时候,后台还要不断的统计,浪费效率,浪费资源
而且这个功能,正常是在测试的时候,开启功能
getStatistics() |
通过Statistics这个对象,对二级缓存和查询缓存 进行使用监控
采用会话工厂的统计方法SessionFactory.getStatistics(),包含二级缓存的统计
监测步骤
- 在hibernate.cfg.xml开启统计功能
<!-- 开启统计-监控--> <property |
- 程序中通过SessionFactory 获取监控数据
@Test //缓存使用率性能监控(前提是开启了性能监视) public 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(); } |
提示:平时系统正常运行的时候,不要开这个功能,消耗性能。
JPA注解开发
jpa是sun公司的一个ORM规范,只有接口和注解,没有具体实现。
jpa是EJB3中的。
Hibernate官网中有这么一句话:
hibernate中有两套注解规范:一套jpa,一套自己的;
使用注解开发,开发效率高!
单表常用注解
新建工程:Hibernate3_day04_jpa
导入jar、配置文件、工具类等
第一步:在cn.itcast.a_single包中建立Book实体
第二步:最简注解示例: (使用了注解的默认值)
【最最小化配置】:
第三步:Hibernate.cfg.xml配置映射:
建表测试
【推荐标准最小化配置】:
更多常用注解注解
实体和表本身相关:
主键相关的:
Auto相当于native,默认值
自定义主键策略(下面使用hibernate的实现):
自定义主键字段:
其他字段相关的:
属性字段官方参考配置:
【较完整配置】:
【补充】:
注解:可以放到属性上面设置,也可以在getter方法上设置,效果一样。但是:要么都放属性,要么都放getter,不能混着用。
-
多表常用注解
一对多
新建订单表实体类,并与客户表建立实体关系。
最小化配置:
推荐配置:
更多详细配置:
Customer.java
Order.java
映射文件加入到核心文件:
建表测试。。。--单表可能不报错,多表会报错
结果可能报错:
原因(包冲突了) javaee.jar 包含不完整JPA规范;解决:手动删除 javaee.jar中 javax.persistence 包
多对多
示例:学生和课程
建立实体类,并加上注解:
【最小化配置】:
【推荐配置】
【更多配置】:
在核心配置文件中配置映射:
建表测试:。。。。。
【注意】:
命名查询NamedQuery
@NamedQuery 定义了命名查询语句,可以通过session.getNamedQuery()获取使用
具体写法如下:
测试:
public 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的时候,需要导入统一的包中的资源
抓取策略
类抓取策略:
关联集合抓取策略:
jpa的:
Hibernate的:
一方:
多方:
缓存策略
一定要在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 @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>(); |
-
扩展
myeclipse工具反转生成hbm映射
hibernate初衷:通过设计po(domain领域模型设计),来映射实际表。
但企业级开发一般都是先设计表,在根据表来编写hbm映射。
对于已经存在的表,我们可以借助myeclipse的工具内置的Hibernate反转引擎,来根据表自动生成hbm、实体类等
第一步:在数据库视图中建立数据库连接
切换myeclipse视图到Database Explorer
建立数据库连接
第二步:建立存放生成Hibernate相关内容的web项目临时载体
新建web工程
对web项目添加Hibernate能力支持(目的是导入必要的jar,--是myeclipse给你导入的)
上面用来添加jar包的
自动生成了 依赖包 以及核心配置文件:
第三步:反转生成
切换回数据视图。
生成hbm:
建议输入包:你将来工程用的什么包,你这里就指定什么包。如果你不指定,则里面的内容需要修改。
结果:
生成JPA注解方式:
修改默认生成的类名:
自定义的类名要加上包名
生成结果:
最后,你就可以将,生成东东,拷贝到你的正式工程中了..
多对多注意:
小结和重点
- 缓存的概念自己体会
- 二级缓存的配置和使用
- 查询缓存的配置和使用
- ehcache
- jpa-主要熟悉单表,一对多
- 反转生成
整个Hibernate的核心内容:
- CRUD(save,update,快照更新,delete,get,load,query.list)
- 理论:一级缓存,快照原理,二级缓存、查询缓存。
- 性能优化:抓取策略、缓存的优化
- 多表:导航查询(当单表用)
- Jpa会配置