- 主键生成机制
表示符生成器 |
描述 |
Increment |
由hibernate自动以递增的方式生成表识符,每次增量为1 |
Identity |
由底层数据库生成表识符。条件是数据库支持自动增长数据类型。 |
Sequence |
Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列。 |
Native |
根据底层数据库对自动生成表示符的能力来选择identity、sequence、hilo |
Uuid.hex |
Hibernate采用128位的UUID算法来生成标识符。该算法 能够在网络环境中生成唯一的字符串标识符,这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。 如果主键用字符类型,并且不代表任何含义。 |
assigned |
适用于自然主键。由java程序负责生成标识符。不能把setID()方法声明为 Private的。尽量避免使用自然主键。 |
increment 标识符生成器
- increment 标识符生成器由 Hibernate 以递增的方式为代理主键赋值
- Hibernate 会先读取 NEWS 表中的主键的最大值, 而接下来向 NEWS 表中插入记录时, 就在 max(id) 的基础上递增, 增量为 1.(带走+1)
- 适用范围:
- 由于 increment 生存标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统
- 适用于只有单个 Hibernate 应用进程访问同一个数据库的场合
- OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
identity 标识符生成器
- identity 标识符生成器由底层数据库来负责生成标识符, 它要求底层数据库把主键定义为自动增长字段类型(加1带走)
- 适用范围:
- 由于 identity 生成标识符的机制依赖于底层数据库系统, 因此, 要求底层数据库系统必须支持自动增长字段类型. 支持自动增长字段类型的数据库包括: DB2, Mysql, MSSQLServer, Sybase 等
- OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
sequence 标识符生成器
- sequence 标识符生成器利用底层数据库提供的序列来生成标识符.
- Hibernate 在持久化一个 News 对象时, 先从底层数据库的 news_seq 序列中获得一个唯一的标识号, 再把它作为主键值
- 适用范围:
- 由于 sequence 生成标识符的机制依赖于底层数据库系统的序列, 因此, 要求底层数据库系统必须支持序列. 支持序列的数据库包括: DB2 Oracle 等
- OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
native 标识符生成器
- native 标识符生成器依据底层数据库对自动生成标识符的支持能力, 来选择使用 identity, sequence 或 hilo 标识符生成器.
- 适用范围:
- 由于 native 能根据底层数据库系统的类型, 自动选择合适的标识符生成器, 因此很适合于跨数据库平台开发
- OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
assigned 标识符生成器
- hibernate和底层数据库都不帮助你生成主键,也就是说得自己在程序中手动的设置主键的值。
- 适用范围:
主键有一定的含义,需要根据业务产生的情况。
Uuid标识符生成器
- Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间
- 使用范围:
主键是字符串,而且必须是唯一
- Myeclipse开发hibernate
开发步骤
- 新建一个JavaProject工程 myeclipsehibernate
- 添加Hibernate到myeclipsehibernate.
- 生成以后的结构如图所示:
- 利用hibernate自带的工具自动生成持久化类和映射文件。
具体方法在课堂上进行演示。
- 建立客户端进行编辑
HibernateSessionFactory类
利用ThreadLocal类保证了Session的线程安全。具体内容看代码是怎么样形成的。
- 持久化对象的状态
持久化对象有3种状态:
持久化状态
临时状态
游离状态
Session 的特定方法能使对象从一个状态转换到另一个状态
临时对象(transient)
-
- 在使用代理主键的情况下, OID 通常为 null
- 不处于 Session 的缓存中
- 在数据库中没有对应的记录
持久化对象(也叫”托管”)(Persist)
-
- OID 不为 null
- 位于 Session 缓存中
- 持久化对象和数据库中的相关记录对应
- Session 在清理缓存时, 会根据持久化对象的属性变化, 来同步更新数据库
- 在同一个 Session 实例的缓存中, 数据库表中的每条记录只对应唯一的持久化对象
游离对象(也叫”脱管”)(Detached)
-
- OID 不为 null
- 不再处于 Session 的缓存中
- 一般情况需下, 游离对象是由持久化对象转变过来的, 因此在数据库中可能还存在与它对应的记录
Session使用以下方法可以使持久化对象转变成游离对象:
测试hibernate中对象变化的状态:
程序代码 |
生命周期 |
状态 |
tx = session.beginTransaction(); Customer c = new Customer); |
开始生命周期 |
临时状态 |
Session.save(c) |
处于生命周期中 |
转变为持久化状态 |
Long id=c.getId(); c = null; Customer c2 = (Customer)session.load(Customer.class,id); tx.commit(); |
处于生命周期中 |
处于持久化状态 |
session.close(); |
处于生命周期中 |
转变为游离态 |
c2.getName(); |
处于生命周期中 |
处于游离态 |
c2 = null; |
结束生命周期 |
结束生命周期 |
对象状态转化图
对象状态的总结
操纵持久化对象-save()
- Session 的 save() 方法使一个临时对象转变为持久化对象
- Session 的 save() 方法完成以下操作:
- 把 News 对象加入到 Session 缓存中, 使它进入持久化状态
- 选用映射文件指定的标识符生成器, 为持久化对象分配唯一的 OID. 在使用代理主键的情况下, setId() 方法为 News 对象设置 OID 使无效的.
- 计划执行一条 insert 语句,把Customer对象当前的属性值组装到insert语句中
- Hibernate 通过持久化对象的 OID 来维持它和数据库相关记录的对应关系. 当 News 对象处于持久化状态时, 不允许程序随意修改它的 ID
操纵持久化对象-update()
- Session 的 update() 方法使一个游离对象转变为持久化对象, 并且计划执行一条 update 语句.
操纵持久化对象-saveOrupdate()
saveOrUpdate:
该方法同时包含save和update方法,如果参数是临时对象就用save方
法,如果是游离对象就用update方法,如果是持久化对象就直接返回。
如果参数是临时对象就用save方法
如果是游离对象就用update方法
如果是持久化对象就直接返回,不执行操作
- 映射一对多关联关系
6.1单向关联
仅仅建立从Order到Customer的多对一关联,即仅仅在Order类中定义customer属性。或者仅仅建立从Customer到Order的一对多关联,即仅仅Customer类中定义orders集合。
单向 n-1 关联只需从 n 的一端可以访问 1 的一端
域模型: 从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中无需定义存放 Order 对象的集合属性
关系数据模型:ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
Hibernate 使用 <many-to-one> 元素来映射多对一关联关系
6.1.1保存操作
实验1、
先保存订单,再保存客户
从这里可以看出执行了两条insert语句,一条update语句
先保存客户,再保存订单
可以看出这种情况程序是执行了两条insert语句,而没有执行update语句。
查询订单
先保存客户,再保存订单,在下面的代码中注释掉session.save(c),会有什么后果
级联保存和更新
当hibernate持久化一个临时对象时,在默认情况下,他不会自动持久化所关联的其他临时对象,会抛出TransientObjectException.如果设定many-to-one元素的cascade属性为save-update的话,可实现自动持久化所关联的对象。
6.2双向关联
双向 1-n 与 双向 n-1 是完全相同的两种情形
双向 1-n 需要在 1 的一端可以访问 n 的一端, 反之依然.
域模型:
从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中需定义存放 Order 对象的集合属性
关系数据模型:
ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
建立一对多的双向关联关系
Hibernate使用set元素来映射一对多关联关系
保存客户和订单(客户和订单建立双向关联)
保存客户和不保存订单
在下面的代码中注释掉session.save(order1),会有什么后果
级联保存和更新
当hibernate持久化一个临时对象时,在默认情况下,他不会自动持久化所关联的其他临时对象,会抛出TransientObjectException.如果设定set元素的cascade属性为save-update的话,可实现自动持久化所关联的对象。
查询客户和订单
查询客户和订单(图)
保存客户或订单的区别
保存订单时会发出两条insert语句。
Hibernate: select max(id) from customers
Hibernate: insert into customers (name, id) values (?, ?)
Hibernate: insert into orders (order_number, price, customer_id) values (?, ?, ?)
而保存客户时会发出两条insert语句和一条update语句
Hibernate: select max(id) from customers
Hibernate: insert into customers (name, id) values (?, ?)
Hibernate: insert into orders (order_number, price, customer_id) values (?, ?, ?)
Hibernate: update orders set customer_id=? where id=?
因为订单是多的一方,而客户是一的一方,所以在多对一的关系中,应该使用一的一方进行操作,这样效率更高。
订单变更客户
上述例子产生了两条update语句:
Hibernate: update orders set order_number=?, price=?, customer_id=? where id=?
Hibernate: update orders set customer_id=? where id=?
原因为:
Hibernate会自动清理缓存中的所有持久化对象,按照持久化对象的改变来同步更新数据库,因此执行了上述的两条更新语句所以会产生两条update语句
c4.getOrders().add(o6)执行了一条update语句
o6.setCustomer(c4)执行了一条update语句
级联删除
inverse属性
Inverse来源
在hibernate中通过对 inverse 属性的值决定是由双向关联的哪一方来维护表和表之间的关系. inverse=false 的为主动方,inverse=true 的为被动方, 由主动方负责维护关联关系
Inverse设值
在没有设置 inverse=true 的情况下,父子两边都维护父子关系
Inverse设值原则
在 1-n 关系中,将 n 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
在 1-N 关系中,若将 1 方设为主控方 会额外多出 update 语句
在一的一方设值inverse为TRUE表明一的一方不维护其关系,这样就会发出一条update语句,这样效率也就提高了。
Inverse结论
1.在映射一对多的双向关联关系时,应该在one方把inverse属性设为true,
这可以提高性能。
2.在建立两个对象的关联时,应该同时修改关联两端的相应属性:
customer.getOrders().add(order);
order.setCustomer(customer);
这样才会使程序更加健壮,提高业务逻辑层的独立性,使业务逻辑层的程序代码
不受Hibernate实现类的影响。同理,当删除双向关联的关系时,也应该修改
关联两端的对象的相应属性:
Customer.getOrders().remove(order);
Order.setCustomer(null);
解除关联关系
解除某个订单与某个客户的关系
这样在order表中,ID为6的相应的外键为3的那行的外键会置为null;
级联删除
cascade属性
在数据库中对集合进行排序
<set> 元素有一个 order-by 属性, 如果设置了该属性, 当 Hibernate 通过 select 语句到数据库中检索集合对象时, 利用 order by 子句进行排序
- 映射多对多关联关系
- 深入Session
session概述
Session 接口是 Hibernate 向应用程序提供的操纵对数据库的最主要的接口, 它提供了基本的保存, 更新, 删除和加载Java 对象的方法.
理解session的缓存
- 在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存. 只要 Session 实例没有结束生命周期, 存放在它缓存中的对象也不会结束生命周期
- 当session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,只要缓存不清空,该对象仍然处于生命周期中。当试图load()对象时,会判断缓存中是否存在该对象,有则返回。没有在查询数据库
清理session的缓存
- Session 具有一个缓存, 位于缓存中的对象称为持久化对象, 它和数据库中的相关记录对应. Session 能够在某些时间点, 按照缓存中对象的变化来执行相关的 SQL 语句, 来同步更新数据库, 这一过程被称为清理缓存(flush)
- 默认情况下 Session 在以下时间点清理缓存:
- 当应用程序调用 Transaction 的 commit()方法的时, 该方法先清理缓存(session.flush()),然后在向数据库提交事务(tx.commit())
- 当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先清理缓存,以保证查询结果能够反映持久化对象的最新状态
- 显式调用 Session 的 flush() 方法.
- 区别:
flush: 进行清理缓存(此时缓存中的数据并不丢失)的操作,让缓存和数据库同步 执行一些列sql语句,但不提交事务,;
commit:先调用flush() 方法,然后提交事务. 则意味着提交事务意味着对数据库操作永久保存下来。
reresh:刷新,让session和数据库同步,执行查询,把数据库的最新信息显示出来,更新本地缓存的对象状态.
clear:清空缓存,等价于list.removeAll();
利用Session缓存读取持久化对象的数据
1 Customer c = new Customer(“TOM”,new HashSet());
2 session.save(c); //customer对象被持久化,并且加入到session的缓存中
3 Long id = c.getId();
4 c = null; //c变量不再引用customer对象
5 //从session缓存中读取customer对象,使c2变量引用customer对象
6 Customer c2 = (Customer)session.load(Customer.class,id);
7 tx.commit(); //缓存中的对象和数据库同步
8 session.close(); //关闭session 清空缓存
9 System.out.println(c2.getName()); //访问customer对象
10 C2 = null; //C2对象不再引用customer对象,customer对象结束生命周期
---------------------------------------------------------------------------------------------
缓存的作用:
1。减少访问数据库的频率。
2。保证缓存中的对象与数据库中的相关记录保持同步。
Session执行批量操作
可以写一个for循环,Session可以批量插入上万条数据。如下面的代码:
For(int i=0;i<10000;i++){
Session.save(object);
}
这样写的代码会产生一个什么问题?会使一万个对象的缓存全部存在于内存中,这样做加大了内存的压力。所以应该定期清理session的缓存,也就是flush一下,这样内存才能保证足够的空间。
- Session.load与session.get方法
不同场合的不同解决方案
场合一:当用户要取数据库的一张表的一个字段,这个字段很可能就是一个字符,总而言之长度是比较短的。
场合二:当用户要取数据库的一张表的一个字段的值,而这个值很可能是blob类型,也许存取的是一个很大的视频文件。
两种场合的取数据的方法一样吗?是用load还是用get方法?
延迟加载
类的延迟加载
lazy 为true或者为false
集合的延迟加载
True false extra
extra为更进一步的延迟加载策略。
当调用getStudents()时不会加载hql语句,当加载student的属性的时候才会发出SQL语句
调用getStudents().size()方法的时候,会触发类似于:Hibernate: select count(id) from T_Student where classid =? 这样的SQL查询语句(这是一种很聪明的做法,如果lazy=”true”,getStudents().size()将会使得hibernate加载所有集合的数据到内存中)。
调用getStudents().contains()方法的时候(即判断是否包含某个对象),会触发类似于:select 1 from T_Student where classid =? and id =? 这样的SQL查询语句。
单端关联
False proxy no-proxy
proxy:当前对象的単值相关对象只有在调用它的主键外的其他属性的get方法时才加载它。
no-proxy:当前对象的単值相关对象只有在调用它的属性时才加载,需要字节码增强。
- Hibernate检索策略
初始化数据
类级别检索策略
立即检索
延迟检索
默认的检索策略是立即检索。在Hibernate映射文件中,通过在<class>上配置 lazy属性来确定检索策略。对于Session的检索方式,类级别检索策略仅适用于load方法;也就说,对于get、qurey检索,持久化对象都会被立即加载而不管lazy是false还是true.一般来说,我们检索对象就是要访问它,因此立即检索是通常的选择。由于load方法在检索不到对象时会抛出异常(立即检索的情况下),因此我个人并不建议使用load检索;而由于<class>中的lazy属性还影响到多对一及一对一的检索策略,因此使用load方法就更没必要了。
关联级别检索策略
fetch (默认值select) |
Lazy (默认值是true) |
策略 |
Join |
False |
采用迫切左外联接检索。 |
Join |
True |
采用迫切左外联接检索。 |
join |
Extra |
采用迫切左外联接检索。 |
select |
False |
采用立即检索 |
select |
True |
采用延迟检索 |
select |
Extra |
采用延迟检索 c.getOrders().size() 执行 select count(id) from orders where customer_id =? for(Order o:set){ o.getOrderNumber();} 将执行: select customer_id , id,order_number ,price from orders where customer_id=? |
subselect |
false/true/extra 也分为3中情况 |
嵌套子查询(检索多个customer对象时) Lazy属性决定检索策略) select customer_id,order_number,price from orders where customer_id in (select id from customers) |
检索策略 |
优点 |
缺点 |
优先考虑使用的场合 |
立即检索 |
对应用程序完全透明,不管对象处于持久化状态还是游离状态,应用程序都可以从一个对象导航到关联的对象 |
(1)select语句多 (2)可能会加载应用程序不需要访问的对象,浪费许多内存空间。 |
(1)类级别 (2)应用程序需要立即访问的对象 (3)使用了二级缓存 |
延迟检索 |
由应用程序决定需要加载哪些对象,可以避免执行多余的select语句,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并节省内存空间。 |
应用程序如果希望访问游离状态的代理类实例,必须保证她在持久化状态时已经被初始化。 |
(1)一对多或者多对多关联 (2)应用程序不需要立即访问或者根本不会访问的对象 |
迫切左外连接检索 |
(1)对应用程序完全透明,不管对象处于持久化状态还是游离状态,都可从一个对象导航到另一个对象。 (2)使用了外连接,select语句少 |
(1)可能会加载应用程序不需要访问的对象,浪费内存。 (2)复杂的数据库表连接也会影响检索性能。 |
(1)多对一 (2)需要立即访问的对象 (3)数据库有良好的表连接性能。 |
- Hibernate的检索方式
HQL(Hibernate Query Language)
- HQL(Hibernate Query Language) 是面向对象的查询语言, 它和 SQL 查询语言有些相似. 在 Hibernate 提供的各种检索方式中, HQL 是使用最广的一种检索方式. 它有如下功能:
- 在查询语句中设定各种查询条件
- 支持投影查询, 即仅检索出对象的部分属性
- 支持分页查询
- 支持连接查询
- 支持分组查询, 允许使用 HAVING 和 GROUP BY 关键字
- 提供内置聚集函数, 如 sum(), min() 和 max()
- 能够调用 用户定义的 SQL 函数或标准的 SQL 函数
- 支持子查询
- 支持动态绑定参数
OID 检索方式
按照对象的 OID 来检索对象
QBC 检索方式
使用 QBC(Query By Criteria) API 来检索对象. 这种 API 封装了基于字符串形式的查询语句, 提供了更加面向对象的查询接口.
简单的查询
使用别名
对查询结果排序
分页查询
- 分页查询:
- setFirstResult(int firstResult): 设定从哪一个对象开始检索, 参数 firstResult 表示这个对象在查询结果中的索引位置, 索引位置的起始值为 0. 默认情况下, Query 从查询结果中的第一个对象开始检索
- setMaxResult(int maxResults): 设定一次最多检索出的对象的数目. 在默认情况下, Query 和 Criteria 接口检索出查询结果中所有的对象
检索单个对象
绑定参数的形式,按参数名称绑定
绑定参数的形式,按参数位置绑定
在映射文件中定义命名查询语句
hibernate检索方式
短语 |
含义 |
Expression.eq |
等于= |
Expression.allEq |
使用Map,使用key/value进行多个等于的判断 |
Expression.gt |
大于> |
Expression.ge |
大于等于>= |
Expression.lt |
小于< |
Expression.le |
小于等于<= |
Expression.between |
对应sql的between子句 |
Expression.like |
对应sql的like子句 |
Expression.in |
对应sql的in子句 |
Expression.and |
and 关系 |
Expression.or |
or关系 |
Expression.sqlRestriction |
Sql限定查询 |
Expression.asc() |
根据传入的字段进行升序排序 |
Expression.desc() |
根据传入的字段进行降序排序 |
HQL和QBC支持的各种运算
运算类型 |
HQL运算符 |
QBC运算方法 |
含义 |
比较运算 |
= |
Express.eq() |
|
<> |
Express.not(Express.eq()) |
||
>= |
Express.ge() |
||
< |
Express.lt() |
||
<= |
Express.le() |
||
is null |
Express.isNull() |
||
is not null |
Express.isNotNull() |
||
范围运算符 |
In |
Express.in() |
|
not in |
Express.not(Express.in()) |
||
Between |
Express.between() |
||
not between |
Express.not(Express.between()) |
HQL和QBC支持的各种运算
运算类型 |
HQL运算符 |
QBC运算方法 |
含义 |
字符串模式匹配 |
Like |
Expression.like() |
|
逻辑 |
And |
Expression.and()| Expression.conjunction() |
|
Or |
Expression.or()| Expression.disjunction() |
||
Not |
Expression.not() |
||
迫切左外连接
- 二级缓存
缓存的概念
计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存
Hibernate中提供了两个级别的缓存
Session 级别的缓存
它是属于事务范围的缓存。这一级别的缓存由 hibernate 管理的,一般情况下无需进行干预
SessionFactory 级别的缓存
它是属于进程范围的缓存
启用二级缓存的条件
很少被修改
很多系统模块都要用到
不是私有的数据,是共享的
二级缓存的供应商
配置二级缓存
例子
从二级缓存中取数据
注:当二级缓存关闭以后会是一个怎么样的情况?