从昨晚后半夜开始下午,现在外面的雪还没停,这是来北京后最大的一场雪。早上6:30起床,然后去吃早餐。今天是元旦假期的最后一天,幸好乘车的人不多,很快就坐上了车。如果是平时可能得在大雪里走几站,甚至走到学校。气温还可以,在外面等车时,雪好美!
今日继续讲解hibernate,也是hibernate课程的最后一天。Hibernate的内容非常多,如果详细着讲估计还得两天,但课程安排是三天。大部分之前的学生说外面用hibernate的很少!~~ 无奈!继续上一次课程…。
一、Hibernate的检索策略
上一次课程我们学习了类级别的检索策略和关联级别的一对多检索策略,简单复习一下:
一对多关联检索:
Fecth lazy | True | false | extra |
Join | 迫左 | 迫左 | 迫左 |
Select | 延迟 | 立即 | 延迟(特别) |
subselect | | | |
Fecth的优先级别高于lazy
延迟(特别):select count(id) from orders where cid=?
Subselect:使用嵌套子查询,批量查询的时候。
多对一关联检索:
Fecth lazy | false | proxy | No-proxy |
Join | 迫左 | 迫左 | 迫左 |
Select | 立即 | 延迟:对端类级别是延迟 立即:对端类级别是立即 | |
表单所属的客户,客户是表单的一级关联,再取出客户对应的所有表单,此时表单是前一个表单的二级关联。一级关联、二级关联,两次左外连接使用一条语句的话,会降低性能。所以使用了一条左边连接查询和一条查询语句。这就是著名的N+1查询。
hibernate3.0的所有检索策略都为延迟
接下来我们继续学习关联级别的一对一检索策略与多对多检索策略。
1.一对一检索策略
一对一关联关系是一种什么样的关系?一对一关系不可以把他们定义为一个类吗?把他们放在一个表里不可以吗?当然可以!
一对一关联两种方案:
1).外键关联,此种解决方案有两种方法:
老徐举的例子,一个用户表对应一个地址。为什么要这么做?假设地址中包含大字段,这样可以减少资源的浪费,提高访问性能。
l 方法一、就是在多对一关联的元素节点中添加一个unique=”true”属性便形成了一对一关联。
在用户类的映射文件中添加:
<!-- 影射一对一外键关联用many-to-one进行模拟,增加唯一性约束 --> <many-to-one name="addr" column="aid" class="AddrFk" unique="true" /> |
在地址类的映射文件中添加:
<one-to-one name="user" property-ref="addr"/> |
l 方法二、直接使用一对一关系:
在用户类的映射文件中添加:
<!-- 影射一对一主键关联 --> <one-to-one name="addr" class="AddrPk" /> |
在地址类的映射文件中添加:
<one-to-one name="user" class="UserPk" constrained="true"/> |
同时我们需要设置地址类映射文件的主键为:
<id name="id" column="id" type="integer"> <generator class="foreign"> <param name="property">user</param> </generator> </id> |
将用户表的主键设置为地址表的外键。
2).主键关联,此时子表的主键与主表的主键一一对应。有三种解决方案:
老徐举了一个例子:员工有钟点工和普通工人之分,钟点工领取的是时效工资(rate),普通工人领取的是月薪(salary)。所以我们有必要提取一个员工的超类,并分别实现钟点工和普通工人的子类。
l 方法一、我们将钟点工和普通工人的信息放到一个表里,需要在映射文件中添加:
<!-- 区分符 --> <discriminator column="etype" type="string" length="2" /> <property name="name" column="name" type="string" length="15" /> <!-- 子类(整个继承关系树对应于一个表) --> <subclass name="HeSingle" discriminator-value="he" lazy="false"> <property name="rate" column="rate" type="float" /> </subclass> <subclass name="SeSingle" discriminator-value="se" lazy="false"> <property name="salary" column="salary" type="float" /> </subclass> |
注意“discriminator”元素用于定义一个额外的列,用于区分员工的类型。
l 方法二、我们为每类员工都分别建一个表,如:
<!-- 每个子类对应一个表,从表和主表间一对一关系 --> <joined-subclass name="HeJoined" table="hib_hejoineds" lazy="false"> <key column="eid" /> <property name="rate" column="rate" type="float" /> </joined-subclass> <joined-subclass name="SeJoined" table="hib_sejoineds" lazy="false"> <key column="eid" /> <property name="salary" column="salary" type="float" /> </joined-subclass> |
“column="eid"”被做为外键对应主表的员工id。
l 方法三、为每类员工建立一个表,他们与主表无关各自具有全字段。但是这三个表所使用的Id是不重复的:
<union-subclass name="HeUnion" table="hib_heunions"> <property name="rate" column="rate" type="float" /> </union-subclass> <union-subclass name="SeUnion" table="hib_seunions"> <property name="salary" column="salary" type="float" /> </union-subclass> |
此时,我们需要使用一个特殊的主键增值生成器:
<id name="id" column="id" type="integer"> <generator class="hilo"> <param name="table">hib_hilo</param> <param name="column">currvalue</param> <param name="max_lo">10</param> </generator> </id> |
“"max_lo"”是id取值区间,每次插入记录时,生成器会根据此值步长增长。如果我们执行的一次插入操作,插入了10条记录。那么就需要将此设置为10,以便生成器自动为我们生成10个不同的id。
2.多对多检索策略
在以前的学习中,我们知道多对多关联关系需要一个中间表,用于记录两个多对多表的对应主键。反应到映射文件中,我们需要为两个类添加set元素。我们依然使用,老师与学生的例子,一个老师可以有多个学生,一个学生可以有多个老师,映射文件如下:
老师类映射文件中的set元素:
<set name="stus" table="hib_tea_stu_links" lazy="false"> <key column="tid" /> <many-to-many class="Stu" column="sid" /> </set> |
“column="tid"”对应中间表“hib_tea_stu_links”的老师外键。“column="sid"”对应中间表“hib_tea_stu_links”学生的外键。
学生类映射文件中的set元素:
<set name="teas" table="hib_tea_stu_links" lazy="false" inverse="true"> <key column="sid" /> <many-to-many class="Tea" column="tid" /> </set> |
检索策略 | 优点 | 缺点 | 优先考虑使用的场合 |
立即检索 | 对应用程序完全透明,不管对象处于持久化状态还是游离状态,应用程序都可以从一个对象导航到关联的对象 | (1)select语句多 (2)可能会加载应用程序不需要访问的对象,浪费许多内存空间。 | (1)类级别 (2)应用程序需要立即访问的对象 (3)使用了二级缓存 |
延迟检索 | 由应用程序决定需要加载哪些对象,可以避免执行多余的select语句,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并节省内存空间。 | 应用程序如果希望访问游离状态的代理类实例,必须保证她在持久化状态时已经被初始化。 | (1)一对多或者多对多关联 (2)应用程序不需要立即访问或者根本不会访问的对象 |
迫切左外连接检索 | (1)对应用程序完全透明,不管对象处于持久化状态还是游离状态,都可从一个对象导航到另一个对象。 (2)使用了外连接,select语句少 | (1)可能会加载应用程序不需要访问的对象,浪费内存。 (2)复杂的数据库表连接也会影响检索性能。 | (1)多对一或一对一关联 (2)需要立即访问的对象 (3)数据库有良好的表连接性能。 |
二、Hibernate的检索方式
Hibernate的检索方式:
1.对象导航图:根据映射文件检索
2.OID检索:根据OID检索
3.HQL检索:一种类似于SQL语句的面向对象的hibernate检索文本语句
4.QBC检索:相当于将HQL分解为一个个对象的检索方式
5.本地SQL检索:SQL语句
只有HQL和QBC我们比较陌生,在此我们介绍一下:
1.HQL语句
session.createQuery("from Customer c where c.name = 'itcast' and c.age = 12"); |
Customer:指向类名
c.name:为映射文件中property元素的name
c.ge:为映射文件中property元素的age
看到没有,HQL与数据库毫无关系。HQL操作的持久化类的映射文件。
HQL有三种查询方式:
1).使用参数名,如:
//绑定参数(按照参数名称绑定) q = s.createQuery("from Customer c where c.name = :a and c.age = :b"); q.setString("a","itcast"); q.setInteger("b",12); q.list(); |
2).使用索引
//绑定参数(按照参数索引位置绑定) q = s.createQuery("from Customer c where c.name = ? and c.age = ?"); q.setString(0,"itcast"); q.setInteger(1,12); q.list(); |
3).命名查询
我们需要在映射文件中添加一个class的兄弟元素,这个元素可以添加在任意相关的映射文件中:
<!-- 命名查询 --> <query name="findCustomerByName"> <![CDATA[from Customer c where c.name = ?]]> </query> |
我们在程序中直接调用:
//命名查询(将查询条件定义到影射文件中,从影射文件中提取查询条件) q = s.getNamedQuery("findCustomerByName"); q.setString(0, "t,om"); q.list(); |
4).特别的左外连接查询:
//左外连接查询,集合中的每个元素都是对象数组 s.createQuery("from Customer c left outer join c.orders").list(); //迫切左外连接查询,集合中的每个元素都是客户对象 list = s.createQuery("from Customer c left outer join fetch c.orders").list(); Set<Customer> customers = new HashSet<Customer>(list); |
左外连接查询返回值:list中每个元素都是一个具有两个成员的数组。数组[0]是Customer对象,数组[1]是Order对象。
迫切左外连接返回值:list中每个元素都是一个Customer对象。
2.QBC语句
我们将上面的HQL语句,使用QBC来实现:
Criteria cra = s.createCriteria(Customer.class); Criterion ctn_name = Restrictions.eq("name", "itcast"); Criterion ctn_age = Restrictions.eq("age", 12); //调用list时执行查询并返回值 list = cra.add(ctn_name).add(ctn_age).list(); |
QBC语句,一般用于动态查询。比如,WEB应用中的高级搜索功能!
上面介绍的都是相对简单的查询。至于复杂的查询,我想玩过数据库的人一想就明白了。
比较方面 | HQL检索 | QBC检索 |
可读性 | 优点:和sql相近,易读 | 将语句肢解成一组criteria,较差 |
功能 | 支持各种查询 | 不支持报表查询和子查询。有限的连接查询 |
查询语句形式 | 基于字符串形式的sql | 更加面向对象 |
何时被解析 | 运行时被解析 | 编译时被解析,更易排错 |
可扩展性 | 不具扩展性 | 用户可扩展criteria接口 |
对动态查询语句的支持 | 支持动态查询,编程麻烦 | 适合动态生成查询语句 |
三、Hibernate的映射继承关系
就是前边检索策略中提到的:
joined-subclass、union-subclass、subclass这些元素。
四、Hibernate批量操作
使用HQL和QBC等查询语句进行批量操作…
五、Hibernate的缓存
二级缓存位于SessionFactory中,用于进程范围:多个工作单元共享,可并发访问,可存储实例本身也可存散列数据,然后在重新组装成对象放到一级缓存中。集群范围:多个进程和主机间访问,网络通信是重点。需要将数据复制到所有集群中的节点。
1. Hibernate缓存基础
缓存不是JPA和EJB规范,用于性能优化,不同厂商方案不同。
缓存维护数据状态在本地,内存或者磁盘。
缓存会减少数据库访问。
2.缓存策略与范围
缓存类型
一.事务范围:位于当前工作单元,不能并发访问。
二.进程范围:多个工作单元共享,可并发访问,可存储实例本身也可存散列数据,然后在 重新组装。
三.集群范围:多个进程和主机间访问,网络通信是重点。需要将数据复制到所有集群中的节点。
缓存和OID:
事务级缓存也用于对象id的使用范围,是理想的缓存。
进程级缓存可选择实现id的进程范围存储,也和主键对应。并发工作单元查询同一id对象的话返 回相同的实例。在进程级缓存中的对象也可按值返回,每个工作单元再重新组装形成副本。
一级缓存是强制的,他也维护了OID,二级缓存(进程级/集群级)对某些数据来说是有用的。
3.缓存的架构
1、一级缓存即session。
2、二级缓存是可配的插件,可用于进程/集群范围缓存。他们缓存都是状态(按值返回),而不是真正的持久化对象。对于特定的数据项来说缓存的并发策略定义了事务的隔离细节。每个类或者每个集合的二级缓存是可选可配的。每个缓存都使用了自己的缓存区域。
3、Hibernate还实现了对查询结果集的缓存,他和二级缓存紧密结合.而且需要额外的两个物理缓存区域来容纳缓存的查询结果和最后更新表的时间戳。
1).Hibernate二级缓存
1、所有通过同一SessionFactory开启的会话共享同一二级缓存。
2、对象以拆解的形式存于二级缓存中(拆解是串行化过程,算法更多,更快比java串行)。
3、重点在于缓存方案(缓存策略与物理缓存提供商)。
4、不同数据需要不同的缓存方案。涉及如下设置:
a、是否开启二级缓存
b、Hibernate并发策略
c、缓存过期策略(timerout LRU 内存敏感)
d、缓存的物理格式(内存 索引文件 集群替换)
二级缓存安装需要两步:
1、决定使用哪个缓存并发策略
2、配置缓存过期和物理缓存属性(cache provider)
2).内置并发策略
并发策略是调解人,负责在缓存中检索数据。对于特定数据项,他也定义了事务隔离的语义。对类 或集合来说使用哪个并发策略需要做出判断。
四个内置的并发策略体现了递减的事务隔离的等级。
1、Transationsal
只在受管环境中使用,如果需要可以确保所有的事务隔离到可重复读,很少更新且防止脏数 据情况下该策略很重要
2、Read-write
该策略维护读已提交隔离级别,使用了时间戳机制只在非集群环境下使用。
3、Nostrict-read-write
不保证在数据库和缓存之间数据的一致性,如果使用的话,应该配置一个高效短期的过期超 时。否则,可能读到脏数据。
4、Read-only
适用于从不发生改变的数据。只对数据进行引用。约束的降低带来了性能提升。
还可以实现自定义策略:
org。hibernate。cache。CacheConcurrencyStrategy
3). 选择缓存供应商
Hibernate要求必须为整个应用选择一个供应商。以下是开源的实现:
1、EHCache
适用于单个虚拟机中单个进程范围,可缓存至内存和磁盘,支持查询缓存(最新版本支持集群
缓存,未测试)
2、OpenSymphony OSCache
在单个虚拟机中缓存至内存或磁盘。支持丰富的过期策略和查询缓存。
3、SwarmCache
基于JGROUPS的集群缓存, 不支持Hibernate查询缓存
4、JBoss Cache
同样基于JGROUPS广播库的完全事务性自我复制集群缓存,支持自我复制、校验、同/异步
通信、乐观/ 悲观锁
二级缓存讲的有些快,内部实现原理没讲,因为时间来不急了。至于二级缓存的详细配置与实现有的是以后有时间再自学吧!
今天的日志主要使用了老徐的资料,在检索策略那块还好,因为讲的比较细。之后的主要就是缓存技术,一级缓存学的挺好,就这个二级缓存留有些“悬念”。
老徐是一位优秀的老师,传智播客确实很牛。能留住这么多人才!
明天就开始学习luncene了,期待它的到来!
加油!