Hibernate学习汇总
一、Hibernate的基本原理
1.什么是Hibernate
Hibernate,翻译过来是冬眠的意思,正好现在已经进入秋季,世间万物开始准备冬眠了。其实对于对象来说就是持久化。
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的ORM框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架构中取代CMP(Container-Managed Persistence),完成数据持久化的重任。
2.持久化
持久化是将程序数据在持久状态和瞬时状态间转换的机制。通俗的讲,就是瞬时数据(比如内存中的数据,是不能永久保存的)持久化为持久数据(比如持久化至数据库中,能够长久保存)。
定义:持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等。
JDBC就是一种持久化机制。文件IO也是一种持久化机制。
日常持久化的方法:将食物冷藏,吃的时候再解冻的方法也是;水果做成罐头的方法也是。
3.Hibernate工作原理
只有处于Session管理下的POJO才具有持久化操作能力。当应用程序对于处于Session管理下的POJO实例执行操作时,Hibernate将这种面向对象的操作转换成了持久化操作能力。
通过上图能够发现Hibernate需要一个hibernate.properties文件,该文件用于配置Hibernate和数据库连接的信息。还需要一个XML文件,该映射文件确定了持久化类和数据表、数据列之间的想对应关系。
除了使用hibernate.properties文件,还可以采用另一种形式的配置文件:*.cfg.xml文件。在实际应用中,采用XML配置文件的方式更加广泛,两种配置文件的实质是一样的。
Hibernate的持久化解决方案将用户从赤裸裸的JDBC访问中释放出来,用户无需关注底层的JDBC操作,而是以面向对象的方式进行持久层操作。底层数据连接的获得、数据访问的实现、事务控制都无需用户关心。这是一种“全面解决”的体系结构方案,将应用层从底层的JDBC/JTA API中抽象出来。通过配置文件来管理底层的JDBC连接,让Hibernate解决持久化访问的实现。这种“全面解决”方案的体系结构图如图所示:
针对以上的Hibernate全面解决方案架构图:
(1)SessionFactory:这是Hibernate的关键对象,它是单个数据库映射关系经过编译后的内存镜像,它也是线程安全的。它是生成Session的工厂,本身要应用到ConnectionProvider,该对象可以在进程和集群的级别上,为那些事务之间可以重用的数据提供可选的二级缓存。
(2)Session:它是应用程序和持久存储层之间交互操作的一个单线程对象。它也是Hibernate持久化操作的关键对象,所有的持久化对象必须在Session的管理下才能够进行持久化操作。此对象的生存周期很短,其隐藏了JDBC连接,也是Transaction 的工厂。Session对象有一个一级缓存,现实执行Flush之前,所有的持久化操作的数据都在缓存中Session对象处。
(3)持久化对象:系统创建的POJO实例一旦与特定Session关联,并对应数据表的指定记录,那该对象就处于持久化状态,这一系列的对象都被称为持久化对象。程序中对持久化对象的修改,都将自动转换为持久层的修改。持久化对象完全可以是普通的Java Beans/POJO,唯一的特殊性是它们正与Session关联着。
(4)瞬态对象和脱管对象:系统进行new关键字进行创建的Java 实例,没有Session 相关联,此时处于瞬态。瞬态实例可能是在被应用程序实例化后,尚未进行持久化的对象。如果一个曾今持久化过的实例,但因为Session的关闭而转换为脱管状态。
(5)事务(Transaction):代表一次原子操作,它具有数据库事务的概念。但它通过抽象,将应用程序从底层的具体的JDBC、JTA和CORBA事务中隔离开。在某些情况下,一个Session 之内可能包含多个Transaction对象。虽然事务操作是可选的,但是所有的持久化操作都应该在事务管理下进行,即使是只读操作。
(6)连接提供者(ConnectionProvider):它是生成JDBC的连接的工厂,同时具备连接池的作用。他通过抽象将底层的DataSource和DriverManager隔离开。这个对象无需应用程序直接访问,仅在应用程序需要扩展时使用。
(7)事务工厂(TransactionFactory):它是生成Transaction对象实例的工厂。该对象也无需应用程序的直接访问。
Hibernate进行持久化操作离不开SessionFactory对象,这个对象是整个数据库映射关系经过编译后的内存镜像,该对象的openSession()方法可打开Session对象。SessionFactory对想是由Configuration对象产生。
每个Hibernate配置文件对应一个configuration对象。在极端情况下,不使用任何配置文件,也可以创建Configuration对象。
4.三个角度理解Hibernate
1、Hibernate是对JDBC进一步封装
原来没有使用Hiberante做持久层开发时,存在很多冗余,如:各种JDBC语句,connection的管理,所以出现了Hibernate把JDBC封装了一下,我们不用操作数据,直接操作它就行了。
2、我们再从分层的角度来看
我们知道非常典型的三层架构:表示层,业务层,还有持久层。Hiberante也是持久层的框架,而且持久层的框架还有很多,比如:IBatis,Nhibernate,JDO,OJB,EJB等等。
3、Hibernate是开源的一个ORM(对象关系映射)框架。
ORM,即Object-Relational Mapping,它的作用就是在关系型数据库和对象之间做了一个映射。从对象O映射到关系R,再从关系映射到对象。这样,我们在操作数据库的时候,不需要再去和复杂SQL打交道,只要像操作对象一样操作它就可以了(把关系数据库的字段在内存中映射成对象的属性)。
5.Hibernate的核心API
从上图中,我们可以看出Hibernate六大核心接口,两个主要配置文件,以及他们直接的关系。Hibernate的所有内容都在这了。那我们从上到下简单的认识一下,每个接口进行一句话总结。
1、Configuration接口:负责配置并启动Hibernate
2、SessionFactory接口:负责初始化Hibernate
3、Session接口:负责持久化对象的CRUD操作
4、Transaction接口:负责事务,对底层操作进行封装
5、Query接口和Criteria接口:负责执行各种数据库查询
注意:
Configuration实例是一个启动期间的对象,一旦SessionFactory创建完成它就被丢弃了。
SessionFactory 线程安全,重量级,对象创建代价很高,通常在应用程序启动时创建,应用退出时关闭。
Session 线程不安全,轻量级(创建和销毁不需要消耗太多资源),避免多个线程使用一个实例,通过SessionFactory打开,Session是hibernate的一级缓存。
Query接口包装了一个HQL查询语句
Criteria接口擅长于执行动态查询
6.Hibernate的优缺点
优点:
1、更加对象化:以对象化的思维操作关系型数据库,我们只需要操作对象就可以了,开发更加对象化。
2、移植性:因为Hibernate做了持久层的封装,你就不知道数据库,你写的所有的代码都具有可复用性。
3、Hibernate是一个没有侵入性的框架,没有侵入性的框架我们称为轻量级框架。
对比Struts的Action和ActionForm,都需要继承,离不开Struts。Hibernate不需要继承任何类,不需要实现任何接口。这样的对象叫POJO对象。
4、Hibernate代码测试方便。
5、提高效率,提高生产力。
缺点:
1、使用数据库特性的语句,将很难调优
2、对大批量数据更新存在问题
3、系统中存在大量的攻击查询功能
二、Hibernate搭建开发环境
参考另一篇博文 http://www.cnblogs.com/liuhaoyu/p/7103783.html
三、Hibernate基本映射
1.映射的概念
ORM(Object Relational Mapping),即对象关系映射。它的作用就是在关系型数据库和对象之间做了一个映射。从对象(Object)映射到关系(Relation),再从关系映射到对象。
这张图特别简单:原来,没有Hibernate时,我们需要通过JDBC+手动写SQL语句来操作数据库,现在,有了Hibernate,它将JDBC+SQL进行了高度封装,我们不需要再去和复杂SQL打交道,只要像操作对象一样操作数据库就可以了。
ORM的实现思想就是将数据库中表的数据映射成对象,Hibernate可以使我们采用对象化的思维操作关系型数据库。
2.映射文件
Hibernate在实现ORM功能的时候主要用到的文件有:
1、映射类(*.Java):它是描述数据库表的结构,表中的字段在类中被描述成属性,将来就可以实现把表中的记录映射成为该类的对象了。
2、映射文件(*.hbm.xml):它是指定数据库表和映射类之间的关系,包括映射类和数据库表的对应关系、表字段和类属性类型的对应关系以及表字段和类属性名称的对应关系等。
3、 hibernate核心配置文件(*.properties/*.cfg.xml):它指定hibernate的一些核心配置,包含与数据库连接时需要的连接信息,比如连接哪种数据库、登录数据库的用户名、登录密码以及连接字符串等。映射文件的地址信息也放在这里。
3.分类
上面的内容看上去挺多,其实特别少,基本映射很简单,我们主要学习关联关系映射,其他几种映射一般不会用,只需要了解即可,用的时候看一下相关资料会做就好。
有时间研究一下映射。
四、事务
1.事务的概念
事务是指一组相互信赖的操作行为,这些操作要么必须全部成功,要么必须全部失败,以保证数据的一致性和完整性。
事务的成功取决于工作单元的所有SQL语句都执行成功,它必须具备ACID特征,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和持久性(Durability),它们的含义是:
(1) 原子性:事务是应用中最小执行单位,不可再分割的逻辑执行体,所有操作执行成功事务才算成功
(2) 一致性:事务不能破坏数据的完整性和一致性(正确性)
(3) 隔离性:在并发环境中,事务是独立的,它不依赖其他事务也能完成任务
(4) 持久性:只要事务成功执行,数据永久保存下来
Hibernate不能跨事务
JTA(Java Transaction API)可以操作多个数据库
2.声明事务边界
1、数据库系统支持以下两种事务模式:
(1) 自动提交模式:每一个SQL语句都是一个独立的事务,如果执行成功就自动提交,否则自动回滚
(2) 手工提交模式:由程序显式指定事务边界
手工提交模式下运行事务,必须显式指定事务边界:
1. 开始事务:begin transaction
2. 提交事务:commit transaction
3. 回滚(撤销)事务:rollback transaction
2、通过JDBC API声明事务边界
java.sq.Connection类提供了以下用于控制事务的方法
(1) setAutoCommit(boolean autoCommit):设置是事自动提交事务
(2) commit():提交事务
(3) rollback():撤销事务
3、通过Hibernate API声明事务边界
Hibernate API封装了JDBC API和JTA API。应用程序可以绕过Hibernate API直接通过JDBC API和JTA API来声明事务,但是这不利于跨平台开发。
从SessionFactory中获得Session实例有两种方式:
方法一
Session session=sessionFactory.openSession();//从连接池中获得连接
方法二
Connection con=DriverManager.getConnection(url,user,pwd); //这种方式绕过Hibernate
con.setAutoCommit(false); //把连接设为手工提交事务模式
Session session=sessionFactory.openSession(con);
在Hibernate API中,Session和Transaction类提供了以下声明事务的方法:
Transaction tx=session.beginTransaction();//开始事务
tx.commit();//提交事务,调用flush()方法清理缓存,然后提交事务
tx.rollback();//撤销事务
要注意的内容:
1.尽量让一个Session对应一个事务,不管事务成功与否最后要关闭Sessin,让其清空缓存,释放占用的连接;如果事务仅包含只读(select)操作,也应在执行成功后提交事务,让数据库释放事务所占的资源。
Session session=sessionFactory.openSession();
Transaction tx;
try{
tx=session.beginTransaction();//开始一个事务
….//执行一些操作
tx.commit();//提交事务
}catch(Exception e){
tx.rollback();//撤销事务。这个语句也要捕获异常,代码略
}finally{
session.close();//撤销事务。这个语句也要捕获异常,代码略
}
2.一个Session可以对应多个事务,这种方式优点重用缓存中的持久化对象,如:
try{
tx1=session.beginTransaction();
….//执行一些操作
tx1.commit();//提交事务
session.desconnect();//释放数据连接
….//执行一些操作,这些操作不属于任何事务
session.reconnnect();//重新获得数据库连接
tx2=session.beginTranction();//开始第二个事务
….// 执行一些操作
tx2.commit();//提交第二个事务
}catch(Exception e){
if(tx1!=null)tx1.rollback();
if(tx2!=null)tx2.rollback();
}finally{
session.close();
}
在一个事务没提交之前,不可以开始第二个事务(不允许的);如果Session的一个事务出现了异常,就应关闭这个Session。如果仍然用它执行其他事务是不可取的。
3.多个事务并发引起的问题
多个事务同时访问数据库中相同的数据时,如果没有采取必要的隔离机制,就可能会发生如下并发问题:
第一类丢失更新:两个事务都更新同一个行,而另一个事务异常回滚,导致两处变化都丢失。这种问题是由于完全没有设置事务的隔离级别造成的。
脏读:一个事务读取到另一个事务尚未提交的更改数据。
不可重复读:一个事务两次读取同一行数据,两次的状态不同。A取数据,B更改数据,A再次取数据。
第二类丢失更新:一个事务覆盖另一个事务已经提交的数据。
幻读:一个事务前后执行一个查询两次,在第二个结果集中包括第一个结果集中不可见的行,或者包括已经删除的行时。跟不可重复读有什么区别??
4.事务隔离级别
为了解决多个事务并发会引发的问题,让用户根据需要在事务的隔离性和并发性之间做合理的权衡,数据库系统提供了四种事务隔离级别供用户选择:
Read Uncommitted(读未提交数据)
Read Committed(读已提交数据)
Repeatable Read(可重复读)
Serializable(串行化)
隔离级别依次为1,2,4,8,一般选隔离级别2。
Hibernate配置文件中可以显示地设置隔离级别。每一种隔离级别对应着一个正整数,在hibernate配置文件中设置:
<session-factory>
<property name="connection.isolation">2</property>
</session-factory>
5.乐观锁和悲观锁
当数据库系统采用Red Committed隔离级别时,会导致不可重复读和第二类更新丢失的并发问题,在可能出现这种问题的场合,可以在应用程序中采用乐观锁或者悲观锁来避免这类问题,在企业开发中:
Read committed+乐观锁 => Repeatable Read
1、乐观锁的原理和应用
乐观锁假定当前事务操纵数据资源时,不会有其他事务同时访问该数据资源,因此不作数据库层次上的锁定。
Hibernate在其数据库访问引擎中内置了乐观锁定实现,默认选择version方式作为Hibernate乐观锁定实现机制。在表中加一个version字段,比较版本号,数据更新时自动加一。
version交给Hibernate管理,类型可以为int,long,short等。
2、悲观锁的原理和应用
悲观锁,正如其名,他是对数据库而言的,数据库悲观了,他感觉每一个对他操作的程序都有可能产生并发。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。
悲观锁的实现,往往依靠数据库提供的锁机制。
在应用程序中显式采用数据库系统的独占锁来锁定数据资源。在如下几种方法时可能显示指定锁定模式为LockOptions.UPGRADE:
调用Session的get()或load()时
调用Session的lock()方法时
调用Query的setLockMode()方法
它会生成 select .... for update 这样的语句来显示指定采用独占锁来锁定查询的记录。
事务提交前,不可以对这条数据进行修改。
session.load(XXX.class, params, LockOptions.UPGRADE);
6.Session与事务
Hibernate的事务是通过Session的beginTransaction()方法显示打开,Hibernate自身并不提供事务控制行为,Hibernate底层直接使用JDBC连接、JTA资源或其他资源的事务。
Hibernate只是对底层事务进行了抽象,让应用程序可以直接面向Hibernate事务编程,从而将应用程序和JDBC连接、JTA资源或其他资源的事务隔离开了。
从编程角度来看,Hibernate的事务由Session对象开启;从底层实现来看,Hibernate的事务由JDBCTransactionFactory(针对JDBC局部事务环境的实现类)、JTATransactionFactory(针对JTA局部事务环境的实现类)。
应用程序编程无需手动操作TransactionFactory产生事务,这是因为SessionFactroy底层已经封装了TransactionFactory。
Hibernate的所有持久化访问都必须在Session管理下进行。
Hibernate建议采用每个请求对应一次Session的模式。
五、缓存机制
1.缓存简介
缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。缓存内的数据是对物理数据源中的数据的复制,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。
缓存的范围
1、事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的的对象形式。
2、进程范围:缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进程的生命周期,进程结束时,缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据,所以存放的介质可以是内存或硬盘。缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据形式。松散的对象数据形式有点类似于对象的序列化数据,但是对象分解为松散的算法比对象序列化的算法要求更快。
3、集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式。
2.Hibernate缓存
Hibernate是一个持久层框架,经常访问物理数据库。
Hibernate缓存是为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。
缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
只有处于Session管理下的POJO才具有持久化操作能力。当应用程序对于处于Session管理下的POJO实例执行操作时,Hibernate将这种面向对象的操作转换成了持久化操作能力。
Hibernate包括两个级别的缓存:
1、默认总是开启的Session级别的一级缓存
2、可选的SessionFactory级别的二级缓存
第一级别的Session级别的缓存,是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;
第二级别的SessionFactory级别的缓存,它是属于进程范围或集群范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。
Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存。
Hibernate中没有二级缓存,整合第三方插件,默认使用插件encache。
安装encache插件:
1、将hibernate-release-5.2.10.Final\lib\optional\ehcache下所有jar包拷到项目下
2、将hibernate-release-5.2.10.Final\project\etc下ehcache.xml文件拷到src下
3、打开文件hibernate-release-5.2.10.Final\project\etc\hibernate.properties,找到Second-level Cache 二级缓存参数:
hibernate.cache.use_second_level_cache true 启用二级缓存
hibernate.cache.region.factory_class org.hibernate.cache.encache.EhCacheRegionFactory启用二级缓存插件(包名可能会错,根据类实际所在的包改正)
4、在类hmb.xml文件中,class下或集合下配置<cache>
5、在ehcache.xml中配置实体类或者集合
3.一级缓存
Hibernate一级缓存又称之为”Session缓存”,”会话期缓存”,顾名思义就是在会话期才会执行的缓存机制。一种轻量级的实现。session销毁时跟随销毁。
缓存位置:session中
生命周期:一个事务中
缓存规格:{ ID:实体 }
默认开启
3.1何时数据会进入缓存:事务中加载过的数据,都会进入缓存,并以{ID:实体}存储在session中。
3.2何时可以检查缓存:以ID为条件的查询可以检查缓存。
session.get();//可以检查
Query.list();//不能检查
Query.uniqueResult();//不能检查
Query.iterate();//可以检查
细节:iterate() 运作流程
String hql="from User u where u.name=?";
Query.iterate(hql);
(1)保留查询条件,到数据库中查询ID,
select id from t_user where t_name=?
[1,2,3]
(2)通过查到的ID去检查缓存。如果有缓存可用,则不用再查询数据库。
但是,注意,如果没有缓存可用,则要再次发起对数据库的查询:
select * from t_user where t_id=3;
select * from t_user where t_id=2;
select * from t_user where t_id=1;
综上,再使用iterate()方法时,可能导致n+1次查询问题。n=满足条件的数据行数。
存在iterator的原因是,有可能会在一个session中查询两次数据,如果使用list每一次都会把所有的对象查询上来,而是要iterator仅仅只会查询id,此时所有的对象已经存储在一级缓存(session的缓存)中,可以直接获取
(3)使用:
Iterator it=query2.iterate();
while(it.hasNext()){
User user=(User)it.next();
System.out.println(user);
}
3.3相关的API:
- evict(obj):用于将某个对象从Session的一级缓存中清除
- clear():用于将一级缓存中的所有的对象全部清除
4.二级缓存
二级缓存的出现就是为了弥补一级缓存的生存期局限于session的生存期内,便于其他的session也能时用到缓存中的数据。即每个session都会共享的缓存,这就是Hibernate的二级缓存机制。
缓存位置:SessionFactory中
生命周期:全局可用
缓存规格:{ID:实体}
默认关闭:通过配置开启。
<!-- 开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 二级缓存类别:EhCache,OSCache,JbossCache -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
导包并引入ehcahe.xml
为要进入二级缓存的实体,增加权限。
//只读缓存权限
//@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
//读写缓存权限
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。
注意:我们通常使用二级缓存都是将其配置成 read-only ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。
4.1何时数据会进入缓存:事务中加载过的数据,都会进入缓存,并以{ID:实体}存储在session中。
4.2 何时可以检查缓存:以ID为条件的查询可以检查缓存。
get();
iterate();
5.查询缓存:依赖二级缓存
缓存位置:SessionFactory中
生命周期:全局可用,但不稳定,如果和缓存数据相关的表有任何的改动,则缓存数据失效,在一般的应用程序中,sessionfactory会以单例的形式存在,所以在整个应用程序的生命周期里,sessionfactory会一直存在。即二级缓存也一直存在直到关闭应用程序。
缓存规格: {hql:查询结果(字段)}
默认关闭:<property name="hibernate.cache.use_query_cache">true</property>
在查询前:query.setCacheable(true); //本次查询要使用查询缓存
5.1 何时数据会进入缓存:用hql查询字段的查询结果,都可以进入查询缓存:{HQL:结果(字段)}
5.2 何时可以检查缓存:只要再次用同样的hql查询,则可以检查查询缓存。
5.3 使用场景
什么样的数据适合存放到第二级缓存中?
1、很少被修改的数据
2、不是很重要的数据,允许出现偶尔并发的数据
3、不会被并发访问的数据
4、参考数据(字典表,码值表)
不适合存放到第二级缓存的数据?
1、经常被修改的数据
2、财务数据,绝对不允许出现并发
3、与其他应用共享的数据。
*细节:如果查询的实体,则查询缓存只能缓存:{HQL:实体的ID字段}
6.缓存总结
6.1 缓存规格:
一级缓存,二级缓存,缓存的是实体:{ID:实体}
session.get(User.class,1);
"from User u";
查询缓存:{HQL : 查询结果}
"select u.id,u.age from User u";
commit();
6.2 使用:
如果查询时是查询字段的话:select a,b,c,d from XXX; 查询缓存足矣。
如果查询时是查询实体的话:from User u; 二级缓存+查询缓存。
6.3 查询缓存和二级缓存的联合使用:
当我们如果需要查询出两次对象的时候,可以使用二级缓存来解决N+1的问题。
如果查询时是查询实体的话:from User u;
初次query.list(); 时,检查查询缓存,没有可用数据,则转向数据库,获得一个实体User,
将{HQL:User的ID}存入查询缓存,将{User的ID:User}存入二级缓存。
再次query.list(); 时,检查查询缓存,获得缓存数据:User的ID,通过UserID检查二级缓存,
如果有数据,则直接使用,否则以各个ID为条件分别发起查询。
6.4 一二级缓存对比
7.为什么这样设计
一般情况下,我们查询的数据一般是实时的,使用二级缓存肯定不行,使用一级缓存既利用了缓存又不会影响实时。使用二级缓存是为了存储一些比较稳定的数据,二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。
8.图解缓存
分区:是以全类名为单位分区
六、Hibernate的clear(),flush(),evict()详解
Session.flush( )方法的作用其实就是让session的缓存的数据(session就是一级缓存)刷入到数据库里面去,让数据库同步,可以更简单的理解成,强制让session的数据和数据库的数据同步,而不是什么清除缓存。清除缓存是session.clear()方法,在使用flush方法一般之前都是对一个对象进行CRUD的操作,然后调用flush方法,就及时的同步到数据库里面去,其实session.flush()方法用的最好的一块是在处理大量数据的时候我们可以控制数量,比如,我们要存储1万个对象,我们可以这样做:
if(i%20 == 0){
session.flush(); // 强制同步数据到数据库里面去
session.clear();清除缓存
}
这样提高工作性能。
1.clear 方法
把缓冲区内的全部对象清除,但不包括操作中的对象。
无论是Load 还是 Get 都会首先查找缓存(一级缓存) 如果没有,才会去数据库查找,调用Clear() 方法,可以强制清除Session缓存。
session.get(Teacher.class, 3);
//这里不clear只会执行一次sql语句,有clear会执行2次
//session.clear();
session.get(Teacher.class, 3);
这里虽然用了2 个 get 方法( get 方法会立即执行 sql 语句),但因为第一次执行后会缓存一个 ID 为 3 的实体,所以虽然有 2 个 get 方法只执行一次 SQL 语句。
2.flush方法
刷新一级缓存区的内容,使之与数据库数据保持同步。
flush方法是可以设置的,也就是 flush 什么时候执行是可以设置的
在session.beginTransaction 前设置 FlushMode
session.setFlushMode(FlushMode.Always|AUTO|COMMIT|NEVER|MANUAL)
FlushMode有 5 个值可选
Always:任何代码都会 Flush
AUTO:默认方式 – 自动
Commit:COMMIT时
Never:始终不
MANUAL:手动方式
1、NEVEL:已经废弃了,被MANUAL取代了
2、MANUAL:只能用flush来清理缓存,commit不会。
如果FlushMode是MANUAL或NEVEL,在操作过程中hibernate会将事务设置为readonly,所以在增加、删除或修改操作过程中会出现如下错误:
org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.NEVER)
- turn your Session into FlushMode.AUTO or remove 'readOnly' marker from transaction definition ;
解决办法:配置事务,spring会读取事务中的各种配置来覆盖hibernate的session中的FlushMode;
3、AUTO
设置成auto之后,当程序进行查询、提交事务或者调用session.flush()的时候,都会使缓存和数据库进行同步,也就是刷新数据库
4、COMMIT
提交事务或者session.flush()时,刷新数据库;查询不刷新。
5、ALWAYS:
每次进行查询、提交事务、session.flush()的时候都会刷数据库。
ALWAYS和AUTO的区别:当hibernate缓存中的对象被改动之后,会被标记为脏数据(即与数据库不同步了)。当 session设置为AUTO时,hibernate在进行查询的时候会判断缓存中的数据是否为脏数据,是则刷数据库,不是则不刷,而always是直接刷新,不进行任何判断。很显然auto比always要高效得多。
设置FlushMode有个好处是可以节省开销,比如默认session只做查询时,就可以不让他与数据库同步了。
3.evict方法
evict(Object obj) :将指定的持久化对象从一级缓存中清除。session.evict(obj);
evict(Class arg0, Serializable arg1):将某个类的指定ID的持久化对象从二级缓存中清除,释放对象所占用的资源。sessionFactory.evict(Customer.class, new Integer(1));
evict(Class arg0):将指定类的所有持久化对象从二级缓存中清除,释放其占用的内存资源。sessionFactory.evict(Customer.class);
evictCollection(String arg0) 将指定类的所有持久化对象的指定集合从二级缓存中清除,释放其占用的内存资源。sessionFactory.evictCollection("Customer.orders");
释放对象所占用的内存资源,指定对象从持久化状态变为脱管状态,从而成为游离对象。
4.contains方法
contains(Object obj) 判断指定的对象是否存在于一级缓存中。
5.Hibernate 执行的顺序如下:
(1) 生成一个事务的对象,并标记当前的 Session 处于事务状态(注:此时并未启动数据库级事务)。
(2) 应用使用 s.save 保存对象,这个时候 Session 将这个对象放入 entityEntries ,用来标记对象已经和当前的会话建立了关联,由于应用对对象做了保存的操作, Session 还要在 insertions 中登记应用的这个插入行为(行为包括:对象引用、对象 id 、 Session 、持久化处理类)。
(3)s.evict 将对象从 s 会话中拆离,这时 s 会从 entityEntries 中将这个对象移出。
(4) 事务提交,需要将所有缓存 flush 入数据库, Session 启动一个事务,并按照 insert,update,……,delete 的顺序提交所有之前登记的操作(注意:所有 insert 执行完毕后才会执行 update ,这里的特殊处理也可能会将你的程序搞得一团糟,如需要控制操作的执行顺序,要善于使用flush ),现在对象不在 entityEntries 中,但在执行 insert 的行为时只需要访问 insertions 就足够了,所以此时不会有任何的异常。异常出现在插入后通知 Session 该对象已经插入完毕这个步骤上,这个步骤中需要将 entityEntries 中对象的 existsInDatabase 标志置为 true ,由于对象并不存在于 entityEntries 中,此时 Hibernate 就认为 insertions 和 entityEntries 可能因为线程安全的问题产生了不同步(也不知道 Hibernate 的开发者是否考虑到例子中的处理方式,如果没有的话,这也许算是一个 bug 吧),于是一个 net.sf.hibernate.AssertionFailure 就被抛出,程序终止。
一般我们会错误的认为 s.save 会立即执行,而将对象过早的与 Session 拆离,造成了 Session 的 insertions 和entityEntries中内容的不同步。所以我们在做此类操作时一定要清楚Hibernate什么时候会将数据 flush 入数据库,在未flush之前不要将已进行操作的对象从Session上拆离。解决办法是在 save 之后,添加 session.flush。
七、主键生成策略
1.Assigned
由用户生成主键值,并且要在save()之前指定否则会抛出异常
特点:主键的生成值完全由用户决定,与底层数据库无关。用户需要维护主键值,在调用session.save()之前要指定主键值。
2.Hilo
Hilo使用高低位算法生成主键,高低位算法使用一个高位值和一个低位值,然后把算法得到的两个值拼接起来作为数据库中的唯一主键。Hilo方式需要额外的数据库表和字段提供高位值来源。默认情况下使用的表是hibernate_unique_key,默认字段叫作next_hi。next_hi必须有一条记录否则会出现错误。
特点:需要额外的数据库表的支持,能保证同一个数据库中主键的唯一性,但不能保证多个数据库之间主键的唯一性。Hilo主键生成方式由Hibernate 维护,所以Hilo方式与底层数据库无关,但不应该手动修改hilo算法使用的表的值,否则会引起主键重复的异常。
3.Increment
Increment方式对主键值采取自动增长的方式生成新的主键值,但要求底层数据库的主键类型为long,int等数值型。主键按数值顺序递增,增量为1。
特点:由Hibernate本身维护,适用于所有的数据库,不适合多进程并发更新数据库,适合单一进程访问数据库。不能用于群集环境。
4.Identity
Identity方式根据底层数据库,来支持自动增长,不同的数据库用不同的主键增长方式。
特点:与底层数据库有关,要求数据库支持Identity,如MySQl中是auto_increment, SQL Server 中是Identity,支持的数据库有MySql、SQL Server、DB2、Sybase和HypersonicSQL。 Identity无需Hibernate和用户的干涉,使用较为方便,但不便于在不同的数据库之间移植程序。
5.Sequence
Sequence需要底层数据库支持Sequence方式,例如Oracle数据库等
特点:需要底层数据库的支持序列,支持序列的数据库有DB2、PostgreSql、Oracle、SAPDb等在不同数据库之间移植程序,特别从支持序列的数据库移植到不支持序列的数据库需要修改配置文件。
6.Native
Native主键生成方式会根据不同的底层数据库自动选择Identity、Sequence、Hilo主键生成方式
特点:根据不同的底层数据库采用不同的主键生成方式。由于Hibernate会根据底层数据库采用不同的映射方式,因此便于程序移植,项目中如果用到多个数据库时,可以使用这种方式。
7.UUID
UUID使用128位UUID算法生成主键,能够保证网络环境下的主键唯一性,也就能够保证在不同数据库及不同服务器下主键的唯一性。
特点:能够保证数据库中的主键唯一性,生成的主键占用比较多的存贮空间
8.Foreign GUID
Foreign用于一对一关系中。GUID主键生成方式使用了一种特殊算法,保证生成主键的唯一性,支持SQL Server和MySQL。
八、Hibernate Session
1.HttpSession与Hibernate Session
HttpSession是severlet中的会话机制,也是jsp的内置对象,可以简单看做是个存储对象的一个作用域。
而Hibernate Session是把JDBC的Connection和Transaction接口进行了简单的封装后的一个接口,即此Session主要用来管理对象的增删改查和事务,还有只要持久化类的实例对象与Session关联了,那此对象就不只是简单的在内存中了,而是可以通过Session对象去管理它了,所以也称此对象在Session缓存中。
2.Session的延迟加载
Session的延迟加载实现要解决两个问题:正常关闭连接和确保请求中访问的是同一个session。
Hibernate session就是java.sql.Connection的一层高级封装,一个session对应了一个Connection。
http请求结束后正确的关闭session(过滤器实现了session的正常关闭);延迟加载必须保证是同一个session(session绑定在ThreadLocal)。
九、创建Configuration对象
org.hibernate.cfg.Configuration实例代表一个应用程序到SQL数据库的映射配置,Configuration提供了一个buildSessionFactory()方法,该方法可以产生一个不可变的SessionFactory对象。
可以直接实例化Configuration来获取一个实例,并为它指定一个Hibernate映射文件,如果映射文件在类加载路径中,则可以使用addResource()方法来添加映射定义文件。那么现在的问题就是如何创建Configuration对象呢?
随着Hibernate 所使用的配置文件的不同,创建Configuration对象的方式也不相同。通常有几种配置Hibernate的方式:
第一种是使用hibernate.properties文件作为配置文件。
第二种是使用hibernate.cfg.xml文件作为配置文件。
第三种是不使用任何的配置文件,以编码方式来创建Configuration对象。
请注意:Configuration对象的唯一作用就是创建SessionFactory实例,所以它才被设计成为启动期间对象,而一旦SessionFactory对象创建完成,它就被丢弃了。
1. 使用hibernateproperties作为配置文件
对于hibernate.properties作为配置文件的方式,比较适合于初学者。因为初学者往往很难记住该配置文件的格式,以及需要配置哪些属性。在Hibernate发布包的etc路径下,提供了一个hibernate.properties文件,该文件列出了Hibernate 的所有属性。每个配置段都给出了大致的注释,用户只要取消所需配置段的注释,就可以快速配置Hibernate和数据库的链接此处给出使用hibernate.properties文件创建Configuration对象的方法。
//实例化configuration对象
Configuration cfg = new Configuration()
//多次调用addResource()方法,添加映射文件
.addResource("Item.hbm.xml")
.addResource("Bid.hbm.xml");
查看hibernate.properties文件发现,该文件没有提供Hibernate映射文件的方式。因此使用hibernate.properties文件来作为配置文件时,必须使用Configuration的.addResource()方法,使用该方法来添加映射文件。
注意:正如上面的代码所示,使用hibernate.properties文件配置Hibernate的属性固然简单,但是因为要手动添加映射文件,当映射文件极其多时,这是一件非常催人泪下的事情。这也就是在实际开发中,不常使用hibernate.properties文件作为配置文件的原因。
当然还有另一种添加配置文件的策略,因为映射文件和持久化类是一一对应的,可以通过Configuration对象来添加持久化类,让Hibernate自己来搜索映射文件。
//实例化configuration对象
Configuration cfg = new Configuration)
//多次调用addClass()方法,直接添加持久化类
.addClass(ppp.Item.class)
.addClass(ppp.BId.class);
2. 使用hibernate.cfg.xml作为配置文件
前面已经看到使用hibernate.properties作为配置文件的情形。因为hibernate.cfg.xml中已经添加了hibernate的映射文件,采用这种配置文件创建configuration对象实例由以下代码实现:
//实例化configuration对象
Configuration cfg = new Configuration().configure() ;//configure()方法将会负责加载hibernate.cfg.xml文件
需要注意的是:在通过new关键字创建Configuration对象之后,不要忘记调用configure()方法。
3. hibernate.properties和hiberntae.cfg.xml文件
如果使用etc路径下的hibernate.properties文件作为配置文件的模板,修改此模板文件作为Hibernate配置文件,这种方式的确是快速进入Hibernate开发的方法。但是对于实际开发,通常会使用hibernate.cfg.xml文件作为配置文件。
深入对比hibernate.properties和hibernate.cfg.xml文件后看如下的hibernate.properties的一个配置属性:
//指定数据库的方言
hibernate.dialect org.hibernate.dialect.MySQLDialect
上面的一行代码是典型的Properties文件的的格式,前面的key为hibernate.dialect , 后面的value是为org.hibernate.dialect.MySQLDialect。
接下来我们再来查看hibernate.cfg.xml文件中的相对应的配置代码:
<property name = "dialect">org.hibernate.dialect.MySQLDialect</property>
同样指定了Hibernate的Dialect 属性是org.hibernate.dialect.MySQLDialect 。对比两种格式的文件,可以发现虽然格式不同但其实质完全一样。
十、HQL优化
初用Hibernate的人也许都遇到过性能问题,实现同一功能,用Hibernate与用JDBC性能相差十几倍很正常,如果不及早调整,很可能影响整个项目的进度。
1.Hibernate性能调优
大体上,对于Hibernate性能调优的主要考虑点如下:
◆数据库设计调整
◆HQL优化
◆API的正确使用(如根据不同的业务类型选用不同的集合及查询API)
◆主配置参数(日志,查询缓存,fetch_size, batch_size等)
◆映射文件优化(ID生成策略,二级缓存,延迟加载,关联优化)
◆一级缓存的管理
◆针对二级缓存,还有许多特有的策略
◆事务控制策略。
2.数据库设计
◆降低关联的复杂性
◆尽量不使用联合主键
◆ID的生成机制,不同的数据库所提供的机制并不完全一样
◆适当的冗余数据,不过分追求高范式
3.Hibernate HQL优化
HQL如果抛开它同Hibernate本身一些缓存机制的关联,Hibernate HQL优化技巧同普通的SQL优化技巧一样,可以很容易在网上找到一些经验之谈。
4.主配置
查询缓存,同下面讲的缓存不太一样,它是针对HQL语句的缓存,即完全一样的语句再次执行时可以利用缓存数据。但是,查询缓存在一个交易系统(数据变更频繁,查询条件相同的机率并不大)中可能会起反作用:它会白白耗费大量的系统资源但却难以派上用场。
fetch_size:同JDBC的相关参数作用类似,参数并不是越大越好,而应根据业务特征去设置
batch_size:同上。
生产系统中,切记要关掉SQL语句打印。
说明:本帖旨在将学习过程中遇到的知识点汇总在一起,方便以后学习和查阅,内容仅作参考。
引用网址:http://blog.csdn.net/jiuqiyuliang/article/details/39380465
http://www.oschina.net/question/565065_86506
http://www.cnblogs.com/liuconglin/p/5693846.html
http://www.cnblogs.com/wean/archive/2012/05/16/2502724.html
http://www.cnblogs.com/wean/archive/2012/05/16/2502724.html