对于数据量大,性能要求高的系统,是不适合使用 Hibernate 的;主要用于事务操作比较多的系统(比如 OA/CRM 行业软件)
忽然想起来,现在已经是 Hibernate5.x 了,有些配置方面已经做了更改,比如 SessionFactory 的获取方式,我也刚刚才意识到,这个版本的问题我准备以后单独搞一篇来说
级联操作
级联是什么学过数据库的都懂,应用在 Hibernate 下就是:当对主对象进行操作的时候,是否对从对象也进行类似的操作
在 Hibernate 中只需要在配置文件里配一下就可以了
比如可以在 set 标签设置 cascade 属性,一般有五个可选值,如果设置多个可以用逗号分割:
- save-update
级联保存或者更新(也就是说 load 以后如果子对象发生了更新,也会级联更新数据库). 但它不会级联删除 - delete
级联删除, 但不具备级联保存和更新 - all-delete-orphan
在解除父子关系时,自动删除不属于父对象的子对象, 也支持级联删除和级联保存更新. - all
能够级联删除和级联更新,但解除父子关系时不会自动删除子对象. - delete-orphan
删除所有和当前对象解除关联关系的对象
以上设在哪一段就是指对哪一端的操作而言,比如 delete,如果设在 one 的一端的 <set>
属性里,就是当 one 被删除的时候,自动删除所有的子记录;
如果设在 many 一端的 <many-to-one>
标签里,就是在删除 many 一端的数据时,会试图删除 one 一端的数据,如果仍然有 many 外键引用 one,就会报“存在子记录”的错误;
如果在 one 的一端同时也设置了cascade="delete"
属性,就会发生很危险的情况:删除 many 一端的一条记录,会试图级联删除对应的 one 端记录,因为 one 也设置了级联删除 many,所以其他所有与 one 关联的 many 都会被删掉。
所以,千万谨慎在 many-to-one 的 many 一端设置 cascade="delete"
属性。故此 cascade 一般用在 <one-to-one>
和<one-to-many>
中
懒加载
关于这个问题在上一篇中提到过,就是在获取 Session 的时候,get 和 load 方式;load 方式就是使用的懒加载
所带来的问题就是:如果 domain 对象中存在其他对象的引用,默认这个引用的对象数据是空的,它以为你不会用,所以并没有去数据库查;等你用的时候,它会试图去数据库查,但是用数据的时候 Session 可能已经关闭了,所以只能抛异常了
常见的解决方案有:
- 关闭懒加载,就是在配置文件设置 lazy 属性为 false
- 显式的进行加载
初始化代理对象Hibernate.initialize(bean.getBean);
就是相当于手动调用了一下 domain 对象中的”对象“ - 使用 openSessionInViewFilter
前面貌似也说过,通过过滤器实现拦截所有请求,扩大 Session 的范围,在请求开始的时候开启 Session 与事务,在请求结束的时候提交事务、关闭连接
过滤器最好配置在 Struts2 核心过滤器的前面 - 在 SSH 项目中可以使用注解来解决
然后再多说点关于第一点的,因为如果配置不好反而效率会降低,Hibernate 的配置很关键!
在 many-to-one 也就是多对一的 many 方,如果配置了 <class ... lazy="false">
,那么 Hibernate 就会在查询 many 方的时候把它相互关联的对象也进行查询,对 select 的影响并不是很大,毕竟是多对一
在 one-to-many 的 one 这一方,如果配置了 <set ... lazy="false">
,那么当你查询这个 one 的时候,会把相关联的 many 全部查询回来,不管你是否使用(都会发一大堆的 select 语句)
所以说,不要在 one-to-many 的 many 的一方设置 lazy 为 false ,效率会大大降低,可以用的是在 set、list 标签中设置属性 lazy 为 extra;这样会比较智能,在使用 list.size() 方法的时候会发送 select count(*)...
语句,而不是一条条的查回来
如果要使用第一种方案,少在 many-to-one 和 one-to-one 标签里设置 lazy;不要在 class 和 property 标签里设 lazy
懒加载其实是依赖的动态代理,前面也提到过,返回的其实是个代理对象,也就是说如果要使用懒加载在设计 domain 对象的时候就不能设置为 final
懒加载除了设置 true 和 false,还有一个值:extra
它一般设置在配置文件中集合标签的属性中(如 set 标签中),意义是当使用集合的 size、isEmpty 等函数时,并不把所有的数据查询回来,而是使用相应的 sql 函数进行查询
extra 也是使用懒加载,只不过是更智能的懒加载,一般设置这个就行
关于缓存
前面提到过一点,Hibernate 的缓存常用的就是一级和二级缓存,一级缓存又称 Session 缓存(存在于内存),因为它伴随着 Session,会话结束了缓存也就不存在了
对于 Session 的那些操作会使其放进一级缓存?
save、update、saveOrUpdate、load、get、list、lock、iterate
在执行上面的这些方法的时候会先将对象加入到一级缓存,然后在事务提交的时候发送相应的 sql 语句,同步到数据库
都知道使用 get、load 方法会从一级缓存中获取数据,但是 list(或者 uniqueResult)并不会从缓存获取数据,它只放进缓存不会主动取缓存数据
然后如果频繁的调用上面的方法,为了防止内存溢出,记得调用 evict 或者 clear 来清除缓存 ;分别对应的是清除单个对象和所有对象;此外还有一个 flush 的方法,用来刷新缓存,让一级缓存和数据库进行同步。
所以说,一级缓存只有在短时间内频繁的操作数据库的情况下,效果才比较明显;处于持久化的对象一般都存在于 Session 缓存中
关于 List 和 iterate 这里简单说下,list 会一次性把所有的记录都查出来;而 iterate 会查询 n +1 次,也就是如果有五条数据,那么会查询六次。
第一次会查询全部的 id ,然后每一次的 next 会根据这个 id 进行查询,所以总共是 n +1 次查询;但是它会从缓存中获取,List 不会从缓存中获取
所以说,当你第一次用 List 获取后会放入缓存,如果第二次你用 iterate 获取,他会从缓存中获取(List 放进去的数据)数据,这样也验证了 List 是会把数据放进缓存中去的
然后再来说说二级缓存,又叫 SessionFactory 缓存,为什么应该也能猜到了,肯定和其的生命周期有关;不同于一级缓存(生命周期短,大小限制),二级缓存必须配置后才可以使用,并且其是交给第三方进行处理的 ;当然 hibernate 也带一个。
它是全局性的,应用中的所有 Session 都共享这个二级缓存。
常用的二级缓存插件有:Ehcache、OSCache、JBossCache;这些缓存有可能存在内存中,也有可能存在于硬盘中
开启二级缓存首先在 cfg 配置文件中(可以从 hibernate.properties 文件中查找):
1 | <!-- 启动二级缓存 --> |
缓存策略的配置除了 class-cache 标签还可以在 hbm 映射配置文件中的 cache 标签里配,但是为了便于管理多数还是在 cfg 文件中配置。
- read-only
只读缓存,提供 serializable 事务隔离级别,对于从来不会修改的数据,可以采用这种访问策略,可以避免脏读,不可重复读和幻读。 - read-write
读写缓存,提供 read committed 事务隔离级别。对于经常读但是很少被修改的数据,可以采用这种隔离类型,它可以防止脏读。(通常选用的策略) - nonstrict-read-write
不严格的读写,不保证缓存与数据库种数据的一致性。提供 read uncommitted 事务隔离级别。对于极少被修改,而且允许脏读的数据,可以采用这种策略。 - transcational
仅在受管理环境下适用。它提供了repeatable read 的事务隔离级别。对于经常读,但是很少被修改的数据,可以采用这种隔离类型,它可以防止脏读和不可重复读。
在查询数据的时候,会放进二级缓存和一级缓存(前提是你配置过这个实体),就是都放一份,还可以通过 statistics 查看命中率 (通过 SessionFactory 获取)
如果配置了集合缓存,记得集合里面的实体也必须是配置过 class-cache 的,否则是没有效果的。
如果使用的是 EHcache 就要在 src 目录下加入 ehcache.xml
文件来具体配置缓存策略,具体的配置不写了,也不是很常用,也就配个大小
放入二级缓存的数据必须是很少被修改的内容
其实还有一个查询缓存…..他可以让 query.list() 方法从二级缓存中取数据。
查询缓存的开启是:<property name="hibernate.cache.use_query_cache">true</property>
;需要定义在 class-cache 之前,并且 list 如果要使用的话需要手动调用一下 q.setCacheable(true)
才行。
主键增长策略
至于在那配置我想都是知道的,就是在 id 标签里的 generator 标签的 class 属性中设置;貌似总共有16+,常用的也就 8、9种,比如:
- increment
用于 long、short 或 int 类型(下面就称数值型吧),适用于所有数据库,每次增量为1。只有当没有其他进程向同一张表中插入数据时才可以使用,不能在集群环境下使用。适用于代理主键。
实质其实就是用 sql 的 max 函数计算出最大的 id 然后 +1,所以自然不能用字符串 - identity
采用底层数据库本身提供的主键生成标识符,每次增长1,条件是数据库支持自动增长数据类型(MySQL、SQL server)。在 MySQL 数据库中可以使用该生成器,该生成器要求在数据库中把主键定义成自增长类型。适用于代理主键。Oracle 数据库并不适用。 - sequence
Hibernate 根据底层数据库序列生成标识符。条件是数据库支持序列。适用于代理主键。在 oracle 数据库中可以使用该生成器。类型也是数值型 - native
根据底层数据库对自动生成表示符的能力来选择 identity、sequence、hilo 三种生成器中的一种,适合跨数据库平台开发。适用于代理主键。 主键类型一般是数值型 - uuid
基于128 位(bit)唯一值算法,根据当前 IP,时间,jvm 启动时间,内部自增量产生等多个参数生成 16 进制数值(编码后以长度为 32 的字符串表示)作为主键,注意类型是字符串哦 - assigned
使用这种方法,主键要提前设置,就是交给用户自己定义,可以是数值型或者字符串 - foreign
使用另外一个相关联的对象的主键作为该对象主键。主要用于一对一关系中。
或者说是用其他表的主键来决定自己的 id - hilo
高低位算法生成,类型为数值型,生成方法不依赖数据库,所以适用于全部数据库,但是需要新建表并且在配置文件配置,不太常用
Oracle:数值型的话,可以使用 sequence;如果是字符串类型使用 uuid 或者 assigned
MySQL:数值型的话,可以使用 increment ;字符串类型使用 uuid 或者 assigned
SQL Server:数值型的话,可以使用 identity 或者 native;字符串类型使用 uuid 或者 assigned
使用注解
都知道从 JAVA5.0 之后,可以利用注解来简化配置,所以自然可以用在 Hibernate 上,可以不再写 hbm 文件了,但是 cfg 文件还是要写的…
其实大部分实体对象都是用工具生成的,我感觉看懂就行了,并且现在多是用 JPA 的注解了,它们基本是一致的:
1 | //如果我们当前这个bean要设置成实体对象,就需要加上Entity这个注解 |
采用了注解就不需要写映射文件了,但是需要让 hibernate 知道这是一个实体,所以还要在 cfg 配置文件中这样写:
1 | <!-- 基于annotation的配置 --> |
当然这是最简单的,后面还有更多的对应关系的注解,移步这里吧:http://www.cnblogs.com/xiaoluo501395377/p/3374955.html
关于连接池
Hibernate 有自带的连接池,也支持第三方的连接池,推荐使用 C3P0,无论使用那个,记得导包(hibernate 中就有)
默认情况下(即没有配置连接池的情况下),Hibernate 会采用内建的连接池。但这个连接池性能不佳,且存在诸多 BUG,因此官方也只是建议仅在开发环境下使用。
比如配置 C3P0 连接池,除了必须的数据库连接信息,只需要在 cfg 配置文件中添加一小部分配置信息即可
1 | "1.0" encoding="UTF-8" xml version= |
上面说的那个是连接池的最大连接数目,不要当成是连接的最大数目,如果不知道怎么配可以从 hibernate 的 project/etc 目录下的 hibernate.properties 文件中抄
记得一定要配置 provider_class ,这一句用于指定 Hibernate 的连接提供方式,如果没有将不会使用 c3p0 作为 Hibernate 的连接池。那么如何知道连接池是否生效了呢,比如可以在 MySQL 中使用 SHOW PROCESSLIST
来查看活跃的连接数。
或者可以尝试下评价比较高的 Proxool 连接池,对于它,配置可能会有些复杂,还需要配置单独的 proxool.xml 文件,这里不多说了,或者看看阿里的 druid ?
另外还有一种比较特殊的,就是使用 Tomcat 自带的(JNDI),这种用法应该比较少见,关于自带连接池的配置我在 这篇文章 说过了,配置好了在 cfg 文件中加入
1 | <property name="hibernate.connection.datasource"> |
jdbc/name 就是你配置的数据源(连接池)的名字,后面其实还有 Spring 中配置的方式,这个下次再说吧,可以看看参考的最后一个连接,挺全的
关于映射文件
hbm 映射文件我感觉大部分都是自动生成的,因为我们多数还是习惯于先设计数据库,hbm 配置文件也很少见了,大多都使用 JPA 的注解了;所以我就没写这方面的
但还是要提一点,在一对多或者多对一等这种遇到使用集合的时候,我们有四种选择:数组、Set、List、Map;配置文件中也有对应的标签(在一对多对象引用时选择 one-to-many 标签)。
数组和 List 差不多,都是要保证顺序,所以在配置的时候需要指定一个 index 额外字段来标识顺序,它们的区别也就是可变于不可变的差别;
Set 应该是最常用的,它不需要保证顺序,所以不需要额外的字段,配置比较简单,只需要配置个外键字段和相应的列(对象)即可;
Map 虽然没顺序,但是需要配置一个 key,这个 key 也会存在于数据库的表中。
但是,一般不会在配置文件中进行配置这些集合,而是只维护多的一方(一对多、多对一);因为如果多的一方很多,在查询“一” 的一方时会大量的消耗资源来查询多的对象,并且还不一定用得到
inverse,可翻译为控制反转,就是说把控制器交出去了
设置位 false 代表有控制器;设置位 true 说明交出了控制权
所谓的控制权就是维护关系(外键)的权利了,比如只能在一对多的一的一方设置,如果设置了为 true ,那么它就没有权利更新外键了(数据都是保存的,只是它内部的集合中的实体不会保存引用了)
此属性对获取数据没有影响
关于组合与继承映射
组合简单说就是一个类中包含有另一个类,映射到数据库中对应的是一张表;继承就继承了…..额
组合使用 component 标签,name 属性为组合类类型的属性名,使用子标签 property 指定到底是组合了那些属性
参考
http://blog.csdn.net/sinlff/article/details/7342527
缓存机制学习
注解大全
http://www.cnblogs.com/oumyye/p/4442764.html
https://teakki.com/p/57df75a21201d4c1629b86c1
评论框加载失败,无法访问 Disqus
你可能需要魔法上网~~