震惊!从未见过如此简单粗暴的Hibernate教程
1. 内容大纲
2. 引言
对象关系映射(Object Relational Mapping,简称ORM
)是一种为了解决面向对象与关系数据库存在的互不匹配的现象的规范,简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将java程序中的对象自动持久化到关系数据库中.
面向对象概念 | 面向关系概念 |
---|---|
类 | 表 |
对象 | 表中行(记录) |
属性 | 表中列(字段) |
ORM框架在前后端领域都能看到它的影子,比如Android的greenDAO,比如iOS的coreData,比如Node.js的mongoose,这里主要讲解Java中Hibernate我们比较容易忽略和重要的点。
3. save和get的流程
3.1.save
流程:
1. 根据对象找到user类(`user.getClass`)和对应的映射文件`User.hbm.xml`,并解析出表名`t_user`。
2. 使用内省机制操作`user`对象,获取其中的属性:id/name。
3. 解析映射文件,找到属性对应的列名。
4. 根据主键生成策略,如果是`native`,此时主键就不出现在SQL语句中,如当前的SQL语句为:`insert into t_user(uname) values (?)`其中`?`就对应`user.getXxx()`方法。
3.2.get
流程:
1. 根据对象找到user类(`user.getClass`)和对应的映射文件`User.hbm.xml`,并解析出表名`t_user`。
2. 解析映射文件,找到`<id>`元素对应的列名`uid`,SQL就拼接成功了
3. 处理结果集,把一条数据封装成`User`对象
4. 创建`User`对象
5. 根据列名找到对应的属性名,调用`user.setXxx()`后返回对象。
3.3. get
和load
区别
先看这段简单的测试代码
@Test
public void testGetAndLoad() {
Session session = HibernateUtil.getSession();
session.beginTransaction();
//立即发sql
//Person p = (Person) session.get(Person.class, 1L);
Person p = (Person) session.load(Person.class, 2L);
session.getTransaction().commit();
//预加载延迟加载对象
//Hibernate.initialize(p);
//当查询一个不存在的对象时,依然返回不为null(注意,打印对象会调用toString方法)
System.out.println(p == null);
session.close();
}
get
会立即发送select
语句,load
不会立刻发送,当使用到该对象的非OID
属性时才会发送,延迟加载。load
方法返回的对象永远不为null
,即使在数据库中不存在,所以不能使用if-null
的方式来判断,而get
可以为null
,因为load
执行的时候没有发送select
语句,所以他不知道数据库中有没有对应数据,所以索性返回一个不为null
的对象,如果存在,则再把数据设置到对象中去,如果不存在,使用该对象时报错。load
方法会创建出代理对象,但是代理对象必须在session
关闭之前创建出来,否则会报hibernate中最常见的错误no session
,解决办法为Hibernate.initialize
(代理对象)。
4. 持久对象的生命周期
为什么需要关注持久化对象的生命周期?那我们来回忆使用Hibernate中是否遇到的三个问题
-
问题一: 主键生成策略不同,
save
操作时发生INSERT
语句的时机不同?-
native
: 在执行save
方法的时候发送INSERT SQL
, -
increment
: 在提交事务的时候,才发送INSERT SQL
-
-
问题二: 删除对象的时候,没有立刻发生
DELETE
语句,而是在提交事务的时候发送的. -
问题三: 为什么在事务环境下,通过
get
方法得到的对象,只要修改了属性值,会发生UPDATE
语句.
那么SQL的执行时机和什么有关系呢?和对象的状态有关系.那持久化对象的状态有哪一些?怎么划分的?
划分的规则:
- 当前对象是否有
OID
(该对象在表中对应有一个id
值) - 对象是否被
session
所管理(对象是否在一级缓存中)
状态 | 描述 | 特点 |
---|---|---|
临时状态/瞬时态(transient) | 刚刚用new语句创建,没有被持久化,不处于session中 | 没有oid(数据不存在表中),不在session当中 |
持久化状态(persistent) | 已经被持久化,加入到session的缓存中 | 有oid(数据存在表中),在session当中 |
游离状态(detached)/脱管态 | 已经被持久化,但不处于session中 | 有oid(数据存在表中),不在session当中 |
删除状态(removed) | 对象有关联的ID,并且在session管理下,但是已经计划被删除 | 有oid(数据存在表中),在session当中,最终的效果是被删除 |
5. 对象状态总结
session
中的方法仅仅只是改变对象的状态,不负责发送SQL
/默认情况下事务提交的时候发送SQL
,那么之前是三个问题就可以迎刃而解了.
-
问题一解答:
save
:方法仅仅是把临时状态的对象转换为持久化状态,本身不负责发送SQL
.临时状态的对象没有OID
,调用save
方法之后,变成持久化状态,就必须有OID
.* native
: 表示数据库主键的自增长,只有发送SQL
,才能获取主键,从而获取OID
.
increment
: 先发送SELECT
语句查询id
(拥有了OID
),不需要发送insert
来获取OID
. -
问题二解答:
delete
方法仅仅是改变对象的状态,本身不负责发送SQL
.因此按照默认的方式,提交事务的时候发送SQL
-
问题三解答:
通过get
查询操作得到的对象处于持久化状态(有OID
,存在于一级缓存中).此时,修改了非OID
的属性值,发现一级缓存中的数据和快照区域的数据不同(脏数据),Hibernate就会做比较(一级缓存和快照区),发现不同,就发送UPDATE
语句,做数据同步session
的flush
方法,负责把一级缓存中的脏数据同步到数据库中去.
6. 一级缓存
-
session对象,通过sessionFactory对象创建而来,包含了connection对象,封装了很多操作方法.
-
session不是线程安全的(使用局部变量),所以,session的最大生命周期:一个线程,在web应用当中,一个session的最大生命周期:request
-
session中有一个缓存,称为一级缓存。存放当前工作单元加载的对象.在一个session的生命周期之内,连续拿相同类型,相同ID的对象,只需要发送一次SQL。
public void testSession() {
Session session = HibernateUtil.getSession();
//只打印一条SQL
User user = (User) session.get(User.class, 1L);
User user1 = (User) session.get(User.class, 1L);
}
原理如图:
虽然一级缓存可以提高性能,但是由于session的作用域有限,因此,提高的性能也是非常有限的.所以这就引出了二级缓存的概念。
7. 二级缓存
-
在整个应用中,有且只需要一个sessionFactory对象即可
-
生命周期为整个应用的缓存(二级缓存是sessionFactory上的缓存,能提供整个应用中所有的session使用)
-
所有的get,load方法,总是先查一级缓存,再查二级缓存,如果都没有,在去数据库里面查询
若想了解Hibernate和Mybatis的缓存对比可以戳这里Hibernate和Mybaitis缓存。
8. 事务并发问题
事务并发时,会产生两类丢失更新问题,如图
- 第一类丢失更新: A事务撤销时,把已经提交的B事务的更新数据覆盖了.
- 第二类丢失更新: A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失.
然而解决的办法有两个,一个称之为悲观锁,一个称之为乐观锁
悲观锁(Pessimistic Lock): 悲观地认为每次自己去拿数据的时候别人会修改数据,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。底层采用的就是SELECT ..... FOR UPDATE
//查询的时候
session.get(User.class,1L,LockOptions.UPGRADE);
//SQL: SELECT * FROM t_person WHERE id = 1 FOR UPDATE;
//悲观锁:效率低,容易造成死锁,我们一般使用乐观锁.
乐观锁(Optimistic Lock): 乐观地认为每次去拿数据的时候别人不会修改数据,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
在hibernate中使用乐观锁,推荐使用version
方式:
//1.给对象添加一个int version字段,最好设置属性为private;
//2.在mapping文件中添加<version>元素即可;
<class name="Employee">
<version name="version"/>
...
</class>
//3.剩下的Hibernate帮我们管理