1.Hibernate是如今最流行的开源对象关系映射(ORM)持久化框架,SSH框架组合是很多JavaEE工程的首选,java持久化框架(JPA)的设计师是Hibernate的作者,因此对于Hibernate的一些基本知识在JPA学习笔记总结中具体总结。本篇只总结一些Hibernate使用中的一些小技巧。
2.实体对象的3中状态:
实体对象的生命周期是Hibernate中的一个关键概念,实体对象生命周期中有以下3种状态:
(1).Transient(自由状态):有人也叫透明状态,即在内存中自由存在的对象,与数据库中的记录无关。
(2).Persisent(持久态):也叫受管态,即实体对象处于由Hibernate持久化框架所管理的状态,这种状态下实体对象的引用被Hibernate实体容器管理,处于该状态的对象,其变更将被Hibernate持久化到数据库中。
(3).Datached(游离态):处于持久态的实体对象,其对应的Session实例关闭之后该对象就处于游离态。处于游离态的对象可以通过update再次变为持久态。
注意:自由状态和游离态的区别在于:自由状态在数据库中没有对于该对象的记录,而游离态对象在数据库中有对应的记录,只是脱离了Hibernate持久化框架的管理,其状态变化无法更新到数据库表中对应的记录。
3.值对象(VO)、持久化对象(PO)和数据传输对象(DTO):
(1). 值对象(VO):顾名思义是表示值的对象,是相对独立的对象,处于非管理状态。
(2). 持久化对象(PO):是由持久化框架所管理的对象,代表了与数据库中某条记录对应的实体,其变化在事务提交时将被更新到实际数据库中。
(3). 数据传输对象(DTO):持久化和业务逻辑层或者视图层交互时,持久化对象(PO)会首先通过构造变成一个值对象(VO),然后在将这个值对象(VO)传递给所需的层,这个充当数据传递媒介的值对象(VO)此时就被称为数据传输对象(DTO)。
4.Hibernate中get和load的区别:
在Hibernate中获取对象有两种方法:get方法和load方法,但是这两个方法有一些区别,初学者可能不太熟悉,有可能使用不当会出错,它们的区别如下:
(1).get方法是首先从内部缓存中查找,如果没有查找到则从数据库中直接取数据,若数据库中无相应的值时,get方法会返回null。
(2).load方法如果从内部缓存查找不到时,不是直接从数据库中取数据,而是还将查询二级缓存,可能返回对象代理,若数据库中无相应值时,load方法会ObjectNotFoundException抛异常。
5.Hibernate中save和persist方法的区别:
在Hibernate中对象持久化也有两种方法:save和persist,这两个方法是将游离态的对象保存到数据库中变为受管态的对象,但是这两个方法也有如下区别:
(1).若无事务时,persist方法不执行。
(2).若无事务时,save方法先在数据库中插入记录,然后又回滚数据库。
6.Hibernate的查询方式:
Hibernate支持两种查询方式:Hibernate查询语言(HQL)和Criteria查询。
(1).HQL:是Hibernate提供的面向对象查询语言,主要通过Query接口完成,和SQL写法类似,只是在HQL中不再对表和表中的列进行操作,而是对对象和对象的属性进行操作。如查询给定name的Person对象的HQL写法为:Query.createQuery(“Select p from Person p where p.name = :name”).setParameter(“name”,name);
(2).Criteria:一种比HQL更加面向对象的查询方式,号称是完全面向对象方式的查询方式,和SQL写法相差比较大。和(1)中的例子一样,Criteria的写法如下:
- Criteria criteria = Session.createCriteria(Person.class);
- //添加查询条件
- criteria.add(Restrictions.eq(“name”,name));
对于HQL和Criteria仁者见仁,智者见智,根据个人的习惯选择使用,它们在运行时,Hibernate都会将它们转换成对应的本地的SQL语句。
7.Hibernate延迟加载问题:
在查询一个对象时,如果该对象还引用了其他对象,在查询时会将其引用的所有对象也一同查询出来,这样在大批量数据查询时性能会非常差,为了解决这个问题Hibernate使用了延迟加载(Lazy),即默认只查询要查询的对象本身,该对象所引用的对象只有当真正使用到时再从数据库中查询,这样就可以做到按需查询,大大提高性能。但是延迟加载也会带来其他的问题,比如在一个页面上查询出一个对象,而在其他地方又需要使用该对象的引用对象时,就会出现因为Hibernate会话(Session)关闭对象丢失无法查找所需对象的异常。解决延迟加载的常用办法:
(1).有人提供了一个过滤器,针对Hibernate的叫做:OpenSessionInView的过滤器,针对JPA的叫做OpenEntityManagerInView的过滤器。该过滤器作用是当页面在打开时,Hibernate会话(Session)和JPA的实体管理器(EntityManager)保存打开,这样当真正需要引用对象值时再从数据库中查找就不会出现异常。
(2).查出一个对象后使用Hibernate.initialize(对象)将该对象初始化,这样做延迟加载的机制就不再起作用。
8.线程局部变量ThreadLocal:
ThreadLocal是java的JDK中提供的一个叫做线程局部变量的类,该类的作用是针对共享数据每个线程在其本身保存一份拷贝,避免线程间相互影响。其简单用法如下:
- ThreadLocal session = new ThreadLocal();
- public static getThreadLocal(){
- //以当前线程作为key来获取线程局部变量的值
- Session s = (Session)session.get();
- if(s == null){
- s = this.getSession();
- session.set(s);
- }
- }
线程局部变量(ThreadLocal)相当于一个Map,其Key是当前的线程,只在一个线程内有效。
在Hibernate中用线程局部变量(ThreadLocal)来管理Hibernate会话(Session),可以实现多个操作共享一个Hibernate会话(Session),从而可以避免反复获取同一个session。
9.Hibernate批量操作:
Hibernate中Session是线程不安全的,而SessionFactory是线程安全的。
Hibernate执行flush刷新方法时,会将一级缓存中的数据与数据库同步。
Hibernate操作大批量数据时可能会造成内存溢出,解决办法:
(1).清除Session中的数据,如:
- for(int i = 0; i < 1000000; i++){
- session.save(obj);
- //每100条记录刷新一次session缓存
- if(i % 100 == 0){
- session.flush();
- session.clear();
- }
- }
(2).使用无状态会话接口StatelessSession,不和一级、二级缓存交互,不触发事件,通过该接口的所有操作会立刻发送给数据库。
(3).使用Hibernate中的Query.executeUpdate()执行批量更新时,会清除相关的二级缓存。
10.Hibernate中创建动态离线查询:
- DatachedCriteria dc = DatecheCriteria.forClass(要查询的类.class);
- //添加查询条件
- dc. add(Restrictions.eq(“name”,name));
- 使用动态离线查询:
- Criteria criteria = dc.getExecutable(Session对象);
11.Hibernate中的SQL查询和命名查询(NamedQuery):
(1).SQL查询时在hibernate创建query对象时可以直接使用SQL语句,如下:
- session.createSQLQuery(SQL语句);
SQL查询常用于一些对于性能要求比较高的地方,但是一般不推荐使用,因为涉及到SQL语句的兼容性,会影响到程序的可移植性。
(2)命名查询(NamedQuery)类似于jdbc中的prepareStatement对象,是一种预编译的查询,可以大大提高效率,创建方法如下:
- session.createNamedQuery(HQL查询语句);
12.Hibernate中使用集合时的一个小问题:
在Hibernate和JPA中使用集合对象时,如果使用的是List,在操作时经常会报一个MultiBagException的异常,解决方法为:
(1).最简单最方便的方法:使用Set代替List。
(2).在使用List集合的字段上添加@Cloumn或者@OrderCloumn注解,即给集合指定一个列索引。
13.Hibernate的缓存:
Hibernate中维持了两级缓存:
(1).内部缓存,即一级缓存,属于事务级缓存,在Session对象生命周期中,每个session都维护了一个独立的缓存,为当前session实例所独享。内部缓存有Hibernate自动维护,如需手动干预可以使用以下两个方法:
a.Session.evict:将某个特定的对象从内部缓存中清除。
b.Session.clear:清空内部缓存。
(2).二级缓存,由当前的SessionFactory创建的多个Session实例所共享,在Hibernate中二级缓存涵盖了应用级缓存和分布式缓存。
Hibernate中缓存在以下情况中发挥作用:
a. 通过Id(主键)加载数据时。
b. 延迟加载。
14.ibernate的二级缓存:
(1).引入Hibernate二级缓存的情况:
应用部署在集群环境中。
(2).使用二级缓存的条件:
a.数据不会被第三方修改。
b.数据大小在可接受范围之内。
c.数据的读取大于修改,即数据的更新频率比较低。
d.非关键的数据,可以容忍无效的数据。
(3).使用第三方提供的二级缓存实现:
Hibernate自身提供了二级缓存实现,但是功能比较有限,仅用于在开发调试时使用。Hibernate提供了面向第三方的二级缓存实现接口,常用的二级缓存如下:
a.JCS。 b. EHCache。 c. OSCache。 d. JBoss Cache。e. SwarmCache。
(4).缓存的同步策略:
缓存同步策略决定了数据对象在缓存中的存取规则,为了使得缓存调动遵循正确的应用级事务隔离机制,在使用缓存时必须为每个实体指定相应的缓存同步策略,Hibernate中提供了4中缓存同步策略:
a. read-only:只读,针对不会发生改变的数据。
b. nonstrict-read-write:非严格读写,针对于并非同步要求不是很严格,且数据更新频率很低的数据。
c. read-write:严格可读性缓存,基于时间戳判断机制,实现了read committed事务隔离等级,用于对数据同步严格情况,但是该策略不支持分布式缓存,也是实际应用中使用最多的缓存策略。
d. transactional:事务型缓存,必须运行在JTA环境,该策略实现了repeatable read事务隔离等级,适用于对关键性数据的缓存。
15.一个Hibernate使用二级缓存的小例子:
(1).在hibernate中启用二级缓存:
在hibernate的配置文件hibernate.cfg.xml中添加以下参数(以EHCache为例):
- <hibernate-configuration>
- <session-factory>
- <!--指定缓存提供者-->
- <property name=”hibernate.cache.provide_class”>
- net.sf.ehcache.hibernate.Provider
- </property>
- ……
- </session-factory>
- ……
- </hibernate-configuration>
(2).对象EHCache实现本身进行配置:
默认在类路径下加入ehcache.xml文件,添加如下配置:
- <ehcache>
- <diskStore path=”指定缓存存放目录”>
- <defaultCache
- mexElementsInMemory=”10000” //cache中最大允许保存的数据对象数量
- eternal=”false” //cache中数据是否为常量
- timeToIdleSeconds=”120” //缓存数据钝化时间
- timeToLiveSeconds=”120” //缓存数据的存活时间
- overflowToDisk=”true” //内存不足时,是否启用硬盘缓存
- />
- </ehcache>
(3).指定缓存同步策略:
在各个实体映射文件中指定缓存同步策略,添加如下:
- <class name=”hibernate实体类全路径”>
- <cache usage=”read-write”/>
- ……
- <set name=”……”>.
- <cache usage=”read-only”/>
- </set>
- </class>
注意:缓存同步策略既可用于实体类,也可以用于集合属性。
16. Hibernate的适用和不适用场景:
由于像Hibernate这样的对象关系映射(ORM)在对象和关系型数据库之间操作时,需要将面向对象的操作转化为本地数据库的SQL语句,存在一些性能上的劣势,另外如果对Hibernate熟悉还可以会出现N+1查询和延迟加载的一些问题,所以Hibernate有其适合和不适合的场景。
(1).不适用场景:
a.联机分析处理(OLAP):以大量的查询为主。
b.海里数据,同时性能要求苛刻的系统。
(2).适用场景:
联机事务处理(OLTP)。