5、Hibernate入门5

10、性能优化                                                                                            

Session: 一级缓存

sessionFactory: 二级缓存

查询缓存:  三级缓存

 

一级缓存缓存实体对象

 

Iterator 和 list的区别

       Iterator刚开始取主键,任何时候用到才取出来,所以Iterator利用缓存,不会发出查询实体的sql(from)

       List不会利用缓存,每次用到都会发出sql

 

Session消亡的时候,缓存消亡

 

查询缓存Query.setCacheable(true).list()

 

EhCache 配置文件hibernate-distribution-3.3.2.GA\project\etc\ehcache.xml

	<defaultCache
        maxElementsInMemory="10000"  //最多多少个对象
        eternal="false"      //内存对象的永久性
        timeToIdleSeconds="120"  //期限(对象不活动的期限)
        timeToLiveSeconds="120" //生存期
        overflowToDisk="true" />   // 缓存满时,放到硬盘<diskStore path="java.io.tmpdir"/>

EhCache.jar

              hibernate-distribution-3.3.2.GA\lib\optional\ehcache

XML:

        <property name=” hibernate.cache.use_second_level_cache”>true </property>

        <property name=” hibernate.cache.provider_class”> org.hibernate.cache.EhCacheProvider </property>

 

Annotation:

         在class设置@Cache(usage= CacheConcurrencyStrategy.READ_WRITE )

缓存算法

l  最佳(optimal)置换算法: 选择那些永不使用的,或者是在最长时间内不再被访问的页面置换出去.即要确定哪一个页面是未来最长时间内不再被访问

l  先进先出(FIFO): 利用队列,先进先出

l  最近最久未使用置换算法LRU (Least RecentlyUsed): 系统在每个页面设置一个访问字段,用以记录这个页面自上次被访问以来所经历的时间T,重复访问时重新设为0,选择T最大的页面淘汰。利用栈实现,栈底的T最大.

l  最近未用置换NUR(Not Used Recently): 为每个页面置一个访问位,将内存中的所有页面都通过链接指针链成一个循环队列。当某页被访问的时,若为‘1’,暂不换出此页,置为‘0’。如果是‘0’则淘汰。

11、事务并发处理(ACID)                                                                 

 原子性(Atomicity)

  整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

一致性(Consistency)

在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

  隔离性(Isolation)

  两个事务的执行是互不干扰的,一个事务不可能看到其他事务运行时,中间某一时刻的数据。

  持久性(Durability)

在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

事务隔离与隔离级别的关系

隔 离 级 别

脏读(Dirty Read)

不可重读(Unrepeatable read)

幻读(Phantom Read)

读操作未提交(Read Uncommitted)

可能

可能

可能

读操作已提交(Read Committed)

不可能

可能

可能

可重读(Repeatable Read)

不可能

不可能

可能

可串行化(Serializable)

不可能

不可能

不可能


1.读取未提交(Read Uncommitted)  写事务就是更新记录
         这是最低的事务隔离级别,写事务也不会阻塞读事务,但是会阻塞写事务。这样造成的一个结果就是当一个写事务没有提交的时候,读事务照样可以读取,那么造成了脏读的现象。
2.读取已提交(ReadCommitted)
         采用此种隔离界别的时候,写事务就会阻塞读事务和写事务,但是读事务不会阻塞读事务和写事务,这样因为写事务会阻塞读取事务,那么从而读取事务就不能读到脏数据,但是因为读事务不会阻塞其它的事务,这样还是会造成不可重复读的问题。
3.可重复读(RepeatableRead)
          采用此种隔离级别,读事务会阻塞写事务,但是读事务不会阻塞读事务。因为读事务阻塞了写事务,这样以来就不会造成不可重复读(事务A读取了已提交的数据。然后,事务B更新并提交该数据。如果事务A再次尝试读取该数据,则同一行事务A将获得不同的值)的问题,但是这样还是不能避免幻读(事务A读取与搜索条件相匹配的若干行。事务B以插入或删除行等方式来修改事务A的结果集,然后再提交。如果事务A重复该读取操作,则将获得不同的行集。采用这种方式所添加的行叫做幻影行)问题。
4.序列化(serializable)
         此种隔离级别是最严格的隔离级别,如果设置成这个级别,那么就不会出现以上所有的问题(脏读,不可重复读,幻影读)。但是这样以来会极大的影响到我们系统的性能,因此我们应该避免设置成为这种隔离级别,相反的,我们应该采用较低的隔离界别,然后再采用并发控制策略(用锁机制)来进行事务的并发访问控制)。

 

设置隔离级别

        1:读操作未提交(ReadUncommitted)

        2:读操作已提交(ReadCommitted)

        4:可重读(RepeatableRead)

        8:可串行化(Serializable)

因此,数字4表示“可重读”隔离级别。

hibernate.connection.isolation 4

也可以在配置文件hibernate.cfg.xml中加入以下代码:

//把隔离级别设置为4

<property name=”hibernate.connection.isolation”>4</property>

在开始一个事务之前,Hibernate从配置文件中获得隔离级别的值。

 

乐观锁

       @Version //也可以采用XML文件进行配置  <class optimistic-lock="none|version|dirty|all"/>

       Intversion

悲观锁

       需要使用select for update语句,还需要注意的一点就是每个冲突的事务中(与该查询语句相关联的事务),我们必须使用select for update 语句来进行数据库的访问

   

    session.lock(account, LockMode.UPGRADE);

或者也可以采用如下方式来加载对象:
 session.get(Account.class,identity,LockMode.UPGRADE).

1、悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

Hibernate通过使用数据库的for update 子句实现了悲观锁机制。

Query query = session.createQuery(hsqStr);

query.setLockMode(“table_name”, LockMode.UPGRADE);

List userList = query.list();

 

Ø LockMode.UPGRADE :利用数据库的for update子句加锁。

Ø LockMode. UPGRADE_NOWAIT :Oracle的特定实现,利用Oracle的for

update nowait子句实现加锁。

上.面这两种锁机制是我们在应用层较为常用的,加锁一般通过以下方法实现:

Criteria.setLockMode

Query.setLockMode

Session.lock

 

注意,只有在查询开始之前(也就是Hiberate 生成SQL 之前)设定加锁,才会

真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含forupdate

子句的Select SQL加载进来,所谓数据库加锁也就无从谈起。

 

2、乐观锁( Optimistic Locking )

相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

假设数据库中帐户信息表中有一个

version字段,当前值为1;而当前帐户余额字段(balance)为$100。

1 操作员A 此时将其读出(version=1),并从其帐户余额中扣除$50

($100-$50)。

2 在操作员A操作的过程中,操作员B也读入此用户信息(version=1),并

从其帐户余额中扣除$20($100-$20)。

3 操作员A完成了修改工作,将数据版本号加一(version=2),连同帐户扣

除后余额(balance=$50),提交至数据库更新,此时由于提交数据版本大

于数据库记录当前版本,数据被更新,数据库记录version更新为2。

4 操作员B完成了操作,也将版本号加一(version=2)试图向数据库提交数

据(balance=$80),但此时比对数据库记录版本时发现,操作员B提交的

数据版本号为2,数据库记录当前版本也为2,不满足“提交版本必须大于记

录当前版本才能执行更新“的乐观锁策略,因此,操作员B 的提交被驳回。

这样,就避免了操作员B 用基于version=1 的旧数据修改的结果覆盖操作

员A的操作结果的可能。

 

乐观锁机制避免了长事务中的数据库加锁开销(操作员A

和操作员B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系

统整体性能表现。

<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
optimistic-lock="version"  //乐观锁
>
<id
name="id"
column="id"
type="java.lang.Integer"
>
<generator class="native">
Hibernate Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
</generator>
</id>
<version
column="version"
name="version"
type="java.lang.Integer"
/>
……
</class>
</hibernate-mapping>

 注意version节点必须出现在ID节点之后。

这里我们声明了一个version属性,用于存放用户的版本信息,保存在TUser表的

version字段中。

 12、异常处理                                                                                        

Ø  LazyInitializationException

               collection is not geneic导入有歧义的包

内存溢出异常(OutOfMemoryException

 catch(ClassNotFoundException cnf) {    
         logger.fatal("Couldn't find class " + classname);        
      }    
      catch(InstantiationException ie) {    
         logger.fatal("Couldn't instantiate an object of type " +     
                       classname);        
      }    
      catch(IllegalAccessException ia) {    
         logger.fatal("Couldn't access class " + classname);        
     }    

13、关联更新                                                                                       

在程序中双向要设置两者之间的的关联

user.setGroup(group);

group.getUser().add(user);

撤销关联关系:user.setGroup(null);

 

存的时候:

cascade=(CascadeType.ALL)  所有操作都关联到有映射关系的表中

双向:同时要写cascade=(cascadeType.ALL)

 

拿的时候

fetch=(FetchType.EAGER|  LAZY)

@ManyToOne  默认是EAGER直接将关联关系拿出来 1 + N问题

@OneToMany  默认是LAZY 什么时候用到了才会取

 

1 + N 解决方案

映射双方不要同时设fetchEAGER,设为LAZY什么时候用到什么时候发出

@BacthSize(size=5)加到@Entity下面,任何与这个类的CURD都只是显示5条记录

createCriteria()查询

from Topic t left join  fetch  t.categort c //使用连接

14、hibernate数据加载方式                                                                    

即时加载(ImmediateLoading)

       当实体加载完成后,立即加载其关联数据,并完成关联属性的填充,发出两条甚至若干条SQL

        

延迟加载(lazyloading)

       实体加载时,其关联数据并非马上获取,而是当关联数据第一次被访问时再进行读取

预先加载(Eagerloading)

       预先加载时,实体及其对象同时读取,不过实体及其关联数据是通过一条sql语句outer-join同时读取

批量加载(BatchLoading)

       对于即时加载与延迟加载可以采用批量加载方式进行性能上的优化,通过批量加载提交多个限定条件

       Selectfrom User where id=1 or id=3;

Hibernate 会自动在当前的Session中寻找是否还有其他同类型的待加载的数据,如果有,则将其查询条件合并当前的select语句中一并

       在实体配置的class节点中,我们可以通过batch-size参数打开批量加载机制,并限定每次批量加载的数量:

 

延迟初始化错误

如果对一个类或者集合配置了延迟检索策略,那么必须当代理类实例或代理集合处于持久化状态(即处于 Session 范围内)时,才能初始化它。如果在游离状态时才初始化它,就会产生延迟初始化错误。

当执行session的load()方法时,不会立即执行查询,仅仅返回类的代理类实例:这个代理类特征

下面把Customer.hbm.xml文件的<class>元素的lazy属性设为true(默认为true),表示使用延迟检索策略:

 

<classname="mypack.Customer" table="CUSTOMERS"lazy="true">

 

当执行 Session 的load()方法时,Hibernate不会立即执行查询CUSTOMERS表的select语句,仅仅返回Customer类的代理类的实例,这个代理类具由以下特征:

 

(1) 由Hibernate在运行时动态生成,它扩展了Customer类,因此它继承了Customer类的所有属性和方法,但它的实现对于应用程序是透明的。

(2) 当Hibernate创建Customer代理类实例时,仅仅初始化了它的OID属性,其他属性都为null,因此这个代理类实例占用的内存很少。

(3)当应用程序第一次访问Customer代理类实例时(例如调用customer.getXXX()或customer.setXXX()方法), Hibernate会初始化代理类实例,在初始化过程中执行select语句,真正从数据库中加载Customer对象的所有数据。但有个例外,那就是当应用程序访问Customer代理类实例的getId()方法时,Hibernate不会初始化代理类实例,因为在创建代理类实例时OID就存在了,不必到数据库中去查询

 

提示:Hibernate采用CGLIB工具来生成持久化类的代理类。CGLIB是一个功能强大的Java字节码生成工具,它能够在程序运行时动态生成扩展 Java类或者实现Java接口的代理类。

 

以下代码先通过 Session 的load()方法加载Customer对象,然后访问它的name属性:

 

tx = session.beginTransaction();

Customer customer=(Customer)session .load(Customer.class,new Long(1));

customer.getName(); //往往通过这个方法实例化实体

tx.commit();

 

在运行 session .load()方法时Hibernate不执行任何select语句,仅仅返回Customer类的代理类的实例,它的OID为1,这是由load()方法的第二个参数指定的。当应用程序调用customer.getName()方法时,Hibernate会初始化Customer代理类实例,从数据库中加载Customer对象的数据,执行以下select语句:

 

select * from CUSTOMERSwhere ID=1;

 

当<class>元素的lazy属性为true,会影响 Session 的load()方法的各种运行时行为,下面举例说明。

 

1.如果加载的Customer对象在数据库中不存在, Session 的load()方法不会抛出异常,只有当运行customer.getName()方法时才会抛出以下异常:

 

ERROR LazyInitializer:63 -Exception initializing proxy

net.sf.hibernate.ObjectNotFoundException:No row with the given identifier exists: 1, of class:

mypack.Customer

 

2.如果在整个 Session 范围内,应用程序没有访问过Customer对象,那么Customer代理类的实例一直不会被初始化,Hibernate不会执行任何select语句。以下代码试图在关闭 Session 后访问Customer游离对象:

 

tx = session.beginTransaction();

Customer customer=(Customer)session .load(Customer.class,new Long(1));

tx.commit();

session .close();

customer.getName();

 

由于引用变量customer引用的Customer代理类的实例在 Session 范围内始终没有被初始化,因此在执行customer.getName()方法时,Hibernate会抛出以下异常:

 

ERROR LazyInitializer:63 -Exception initializing proxy

net.sf.hibernate.HibernateException:Could not initialize proxy - the owning Session was closed

 

由此可见,Customer代理类的实例只有在当前 Session 范围内才能被初始化。

 

3.net.sf.hibernate.Hibernate类的 initialize ()静态方法用于在 Session 范围内显式初始化代理类实例,isInitialized()方法用于判断代理类实例是否已经被初始化。例如:

 

tx = session.beginTransaction();

Customercustomer=(Customer) session .load(Customer.class,new Long(1));

if(!Hibernate.isInitialized(customer))

Hibernate.initialize (customer);

tx.commit();

session .close();

customer.getName();

 

以上代码在 Session 范围内通过Hibernate类的initialize ()方法显式初始化了Customer代理类实例,因此当 Session 关闭后,可以正常访问Customer游离对象。

 

4.当应用程序访问代理类实例的getId()方法时,不会触发Hibernate初始化代理类实例的行为,例如:

 

tx = session.beginTransaction();

Customer customer=(Customer)session .load(Customer.class,new Long(1));

customer.getId();

tx.commit();

session .close();

customer.getName();

15、一级缓存跟二级                                                                                           

每个事务都有单独的第一级缓存进程范围或集群范围,缓存被同一个进程或集群范围内的所有事务共享

并发访问策略由于每个事务都拥有单独的第一级缓存,不会出现并发问题,无需提供并发访问策略

 

由于多个事务会同时访问第二级缓存中相同数据,因此必须提供适当的并发访问策略,来保证特定的事务隔离级别。

 

处于一级缓存中的对象永远不会过期,除非应用程序显式清空缓存或者清除特定的对象,必须提供数据过期策略,如基于内存的缓存中的对象的最大数目,允许对象处于缓存中的最长时间,以及允许对象处于缓存中的最长空闲时间。

 

对象的散装数据首先存放在基于内存的缓存中,当内存中对象的数目达到数据过期策略中指定上限时,就会把其余的对象写入基于硬盘的缓存中。

 

缓存的软件实现HibernateSession的实现中包含了缓存的实现由第三方提供,Hibernate仅提供了缓存适配器(CacheProvider)。用于把特定的缓存插件集成到Hibernate中。启用缓存的方式只要应用程序通过Session接口来执行保存、更新、删除、加载和查询数据库数据的操作,Hibernate就会启用第一级缓存,把数据库中的数据以对象的形式拷贝到缓存中,对于批量更新和批量删除操作,如果不希望启用第一级缓存,可以绕过Hibernate API,直接通过JDBCAPI来执行指操作。用户可以在单个类或类的单个集合的粒度上配置第二级缓存。如果类的实例被经常读但很少被修改,就可以考虑使用第二级缓存。只有为某个类或集合配置了第二级缓存,Hibernate在运行时才会把它的实例加入到第二级缓存中。用户管理缓存的方式第一级缓存的物理介质为内存,由于内存容量有限,必须通过恰当的检索策略和检索方式来限制加载对象的数目。Sessionevit()方法可以显式清空缓存中特定对象,但这种方法不值得推荐。第二级缓存的物理介质可以是内存和硬盘,因此第二级缓存可以存放大量的数据,数据过期策略的maxElementsInMemory属性值可以控制内存中的对象数目。

管理第二级缓存主要包括两个方面:选择需要使用第二级缓存的持久类,设置合适的并发访问策略:选择缓存适配器,设置合适的数据过期策略。

 

16、fetch join cascade inserve lazy                                                        

Hibernate定义了一下获取策略

Fetch = join

1.   Join fetchinghibernate取关联数据或集合是通过outer join的方式,通过同一条select语句来实现。

(使用left  outer join加载)

 

Fetch = select(默认)

2.Select fetching hibernate在没有指定了lazy = false的情况下通过另一条select语句来获得与已经获得的实

体相关的实体或集合。(N+1条)在真正需要的时候才加载。

 

一般来说,我们不是通过在映射配置文件自定义取策略,而是在一个事务里,通过在特定的HQL里使用left join

而Criteria

User user = (User) session.createCriteria(User.class)

                                  .setFetchMode(“permissions”,FetchMode.JOIN)

                                  .add(Restrictions.idEq(UserId))

                                  .uniqueResult();

Hibernate对持久化对象得代理的延迟加载是通过对运行时字节的动态注入实现的(CGLIB)

另外一种角度:Hibernate的 fetching 分成以下几种

Immediate fetching 如果实体已经被加载了,他的关联对象,关联集合,属性也要及时加载

Lazy collection fetching 只有应用程序真正使用这个集合的时候,才加载这个集合

Extra-lazy collectingfetching hibernate 不加载一个集合的所有对象到内力,需要哪个,加载哪个

 

Proxy fetching 当前对象的单值相关对象只有在调用它的主键外的其他属性的get方法时才加载它

No-proxy fetching 当前对象的单值相关对象在它的实体变量被访问的时候被加载

 

Lazy attribute fetching 当前对象的某个属性或单值相关对象只有在与它对应的实体变量被访问的时候才加载

 

默认的情况下,hibernate在获取关联对象集合的时候使用的是lazy策略,获得单值关联对象得时候使用的是lazy proxy策略(Proxyfetching)

 

fetch和lazy主要是用来级联查询的,而cascade和inverse主要是用来级联插入和修改的

      cascade主要是简化了在代码中的级联更新和删除

      cascade设在one-to-many端,而且要设置inverse=true由少的一方维持关系

     

      同时设置fetch="join"和 lazy="true"时, 将使用fetch="join"联合查询

      把关联对象一次性拿出来






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未名胡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值