Hibernate学习笔记(二)

对于数据量大,性能要求高的系统,是不适合使用 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 可能已经关闭了,所以只能抛异常了

常见的解决方案有:

  1. 关闭懒加载,就是在配置文件设置 lazy 属性为 false
  2. 显式的进行加载
    初始化代理对象 Hibernate.initialize(bean.getBean); 就是相当于手动调用了一下 domain 对象中的”对象“
  3. 使用 openSessionInViewFilter
    前面貌似也说过,通过过滤器实现拦截所有请求,扩大 Session 的范围,在请求开始的时候开启 Session 与事务,在请求结束的时候提交事务、关闭连接
    过滤器最好配置在 Struts2 核心过滤器的前面
  4. 在 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 启动二级缓存 -->
<property name="cache.use_second_level_cache">true</property>
<!-- 启动命中率统计 -->
<property name="hibernate.generate_statistics">true</property>
<!-- 指定使用哪种二级缓存 -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- 指定哪个domain启用二级缓存
特别说明二级缓存策略:
1. read-only
2. read-write
3. nonstrict-read-write
4. transcational
-->
<class-cache usage="read-write" class="com.bfchengnuo.ssh.domain.User"/>
<!-- 集合缓存 -->
<collection-cache usage="read-write" collection="com.bfchengnuo.ssh.domain.User.set" />

缓存策略的配置除了 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Entity //如果我们当前这个bean要设置成实体对象,就需要加上Entity这个注解
@Table(name="t_user") //设置数据库的表名
public class User
{
private int id;
private String username;
private String password;
private Date registerDate;

// Column中的name属性对应了数据库的该字段名字,里面还有其他属性,例如length,nullable等等
@Column(name="register_date") 
public Date getRegisterDate()
{
return registerDate;
}
public void setRegisterDate(Date registerDate)
{
this.registerDate = registerDate;
}

// 定义为数据库的主键ID
// 建议不要在属性上引入注解,因为属性是private的,如果引入注解会破坏其封装特性,所以建议在getter方法上加入注解
@Id
@GeneratedValue //ID的生成策略为自动生成  
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
  ............
}

采用了注解就不需要写映射文件了,但是需要让 hibernate 知道这是一个实体,所以还要在 cfg 配置文件中这样写:

1
2
3
4
<!-- 基于annotation的配置 -->
<mapping class="com.xiaoluo.bean.User"/>
<!-- 基于hbm.xml配置文件 -->
<mapping resource="com/xiaoluo/bean/User.hbm.xml"/>

当然这是最简单的,后面还有更多的对应关系的注解,移步这里吧:http://www.cnblogs.com/xiaoluo501395377/p/3374955.html

关于连接池

Hibernate 有自带的连接池,也支持第三方的连接池,推荐使用 C3P0,无论使用那个,记得导包(hibernate 中就有)
默认情况下(即没有配置连接池的情况下),Hibernate 会采用内建的连接池。但这个连接池性能不佳,且存在诸多 BUG,因此官方也只是建议仅在开发环境下使用。
比如配置 C3P0 连接池,除了必须的数据库连接信息,只需要在 cfg 配置文件中添加一小部分配置信息即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///test</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

<!-- 配置c3p0连接池 -->
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<!--在连接池中可用数据库连接的最小数目-->
<property name="c3p0.min_size">5</property>
<!--在连接池中所有数据库连接的最大数目-->
<property name="c3p0.max_size">30</property>
<!--设定数据库连接的超时时间-->
<property name="c3p0.time_out">1800</property>
<!--可以被缓存的PreparedStatement的最大数目-->
<property name="c3p0.max_statement">50</property>

<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>

<mapping resource="domain/Customer.hbm.xml"/>
</session-factory>
</hibernate-configuration>

上面说的那个是连接池的最大连接数目,不要当成是连接的最大数目,如果不知道怎么配可以从 hibernate 的 project/etc 目录下的 hibernate.properties 文件中抄
记得一定要配置 provider_class ,这一句用于指定 Hibernate 的连接提供方式,如果没有将不会使用 c3p0 作为 Hibernate 的连接池。那么如何知道连接池是否生效了呢,比如可以在 MySQL 中使用 SHOW PROCESSLIST 来查看活跃的连接数。
或者可以尝试下评价比较高的 Proxool 连接池,对于它,配置可能会有些复杂,还需要配置单独的 proxool.xml 文件,这里不多说了,或者看看阿里的 druid ?


另外还有一种比较特殊的,就是使用 Tomcat 自带的(JNDI),这种用法应该比较少见,关于自带连接池的配置我在 这篇文章 说过了,配置好了在 cfg 文件中加入

1
2
3
4
5
6
7
8
<property name="hibernate.connection.datasource">
java:comp/env/jdbc/name
</property>
<property name="show_sql">true</property>
<property name="dialect">
com.huatech.sysframe.webapp.common.dao.hibernate.dialet.BaseInformixDialect
</property>
<property name="hibernate.generate_statistics">true</property>

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

你可能需要魔法上网~~