本文包括:
1、Hibernate的持久化类
2、Hibernate 持久化对象的三个状态(难点)
3、Hibernate 的一级缓存
4、Hibernate 中的事务与并发
5、Hibernate 的查询方式(HQL:Hibernate Query Language)
1、Hibernate的持久化类
-
什么是持久化类?
持久化类:就是一个 Java 类(咱们编写的 JavaBean),这个 Java 类与表建立了映射关系就可以成为是持久化类。
简单记:持久化类 = JavaBean + xxx.hbm.xml
-
持久化类的编写规则:
提供一个无参数 public访问控制符的构造器 —— 底层需要进行反射。
提供一个标识属性,映射数据表主键字段 —— 唯一标识 OID,数据库中通过主键,Java 对象通过地址确定对象,持久化类通过唯一标识 OID 确定记录。
SQL 语句:
cust_id
bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)'JavaBean 代码:private Long cust_id;
结论:JavaBean 中的 cust_id 即为唯一标识 OID。
所有属性提供 public 访问控制符的 set 和 get 方法。
标识属性应尽量使用基本数据类型的包装类型(默认值为 null)。
-
自然主键和代理主键的区别:
自然主键:该主键是对象本身的一个属性。例如:创建一个人员表,每个人都有一个身份证号(唯一的)。使用身份证号作为表的主键,称为自然主键。(开发中不会使用这种方式)
代理主键:该主键不是对象本身的一个属性。例如:创建一个人员表,为每个人员单独创建一个字段,用这个字段作为主键,称为代理主键。(开发中推荐使用这种方式)
简单记:创建表时,新增一个毫无关系的字段,用该字段作为主键。
-
主键的生成策略:
-
increment:适用于 short,int,long 作为主键,没有使用数据库的自动增长机制。
-
Hibernate 中提供的一种增长机制,具体步骤如下:
先进行查询:select max(id) from user;
再进行插入:获得最大值+1作为新的记录的主键。
问题:不能在集群环境下或者有并发访问的情况下使用。
分析:在查询时,有可能有两个用户几乎同时得到相同的 id,再插入时就有可能主键冲突!
-
-
identity:适用于 short,int,long 作为主键。但是必须使用在有自动增长机制的数据库中,并且该数据库采用的是数据库底层的自动增长机制。
- 底层使用的是数据库的自动增长(auto_increment),像 Oracle 数据库没有自动增长机制,而MySql、DB2 等数据库有自动增长的机制。
-
sequence:适用于 short,int,long 作为主键,底层使用的是序列的增长方式。
- Oracle 数据库底层没有自动增长,若想自动增长需要使用序列。
-
uuid:适用于 char,varchar 类型的作为主键。
- 使用随机的字符串作为主键.
-
native:本地策略。根据底层的数据库不同,自动选择适用于该种数据库的生成策略(short,int,long)。
如果底层使用的 MySQL 数据库:相当于 identity.
如果底层使用 Oracle 数据库:相当于 sequence.
assigned:主键的生成不用 Hibernate 管理了,必须手动设置主键。
重点掌握:uuid(字符串)、native(数字)
-
2、Hibernate 持久化对象的三个状态(难点)
-
持久化对象的状态
Hibernate 的持久化类(前文已写)
-
Hibernate 的持久化类的状态
- Hibernate为了管理持久化类:将持久化类分成了三个状态
-
瞬时态:Transient Object
- 没有持久化标识 OID, 没有被纳入到 Session 对象的管理(即没有关系)
-
持久态:Persistent Object
- 有持久化标识 OID,已经被纳入到 Session 对象的管理.
-
托管态:Detached Object
- 有持久化标识 OID,没有被纳入到 Session 对象的管理.
-
- Hibernate为了管理持久化类:将持久化类分成了三个状态
-
Hibernate 持久化对象的状态的转换
-
瞬时态 -- 没有持久化标识 OID, 没有被纳入到 Session 对象的管理
-
获得瞬时态的对象
User user = new User();
创建了持久化类的对象,该对象还没有 OID,也和 Session 对象无关,所以是瞬时态。
-
瞬时态对象转换持久态
session.save(user); 或者 session.saveOrUpdate(user);
user对象进入缓存,且自动生成了 OID,故为持久态。
-
瞬时态对象转换成托管态
user.setId(1);
手动设置了 OID,但没有和 Session 对象发生关系,故为托管态。
-
-
持久态 -- 有持久化标识OID,已经被纳入到Session对象的管理
-
获得持久态的对象
get()/load();
-
持久态转换成瞬时态对象
delete(); --- 比较有争议的,进入特殊的状态(删除态:Hibernate中不建议使用的)
-
持久态对象转成脱管态对象
session的close()/evict()/clear();
Session 对象被销毁,所以持久化类的对象没有被 session 管理,所以为托管态。
-
-
脱管态 -- 有持久化标识OID,没有被纳入到Session对象的管理
-
获得托管态对象:不建议直接获得脱管态的对象.
User user = new User(); user.setId(1);
-
脱管态对象转换成持久态对象
update();/saveOrUpdate()/lock();
-
脱管态对象转换成瞬时态对象
user.setId(null);
-
-
注意:持久态对象有自动更新数据库的能力!!!
-
测试代码:
/** * 持久态的对象有自动更新数据库的能力 * session的一级缓存!! */ @Test public void run1(){ Session session = HibernateUtils.getSession(); Transaction tr = session.beginTransaction(); // 获取到持久态的对象 User user = session.get(User.class,1); // user是持久态,有自动更新数据库的能力 System.out.println(user.getName()); // 重新设置新的名称 user.setName("隔离老王"); // 正常编写代码 // session.update(user); tr.commit(); session.close(); }
-
执行后,控制台输出:
Hibernate: select user0_.id as id1_1_0_, user0_.version as version2_1_0_, user0_.name as name3_1_0_, user0_.age as age4_1_0_ from t_user user0_ where user0_.id=? 小风 Hibernate: update t_user set version=?, name=?, age=? where id=? and version=?
从中可以发现,在测试代码中我们把
session.update(user);
注释掉了,但是在控制台中发现仍有update
语句,这就是持久态有自动更新数据库的能力,具体原因是 Session 对象的一级缓存(见下节)。
-
-
-
三个状态之间的转换图解:
3、Hibernate 的一级缓存
-
Session 对象的一级缓存
-
什么是缓存?
- 其实就是一块内存空间,将数据源(数据库或者文件)中的数据存放到缓存中。再次获取的时候,直接从缓存中获取,可以提升程序的性能!
-
Hibernate 框架提供了两种缓存
- 一级缓存 -- 自带的不可卸载的。一级缓存的生命周期与 session 一致,一级缓存称为 session 级别的缓存.
- 二级缓存 -- 默认没有开启,需要手动配置才可以使用的。二级缓存可以在多个 session 中共享数据,二级缓存称为是 sessionFactory 级别的缓存.
-
Session 对象的缓存概述
- Session 接口中,有一系列的 java 的集合,这些 java 集合构成了 Session 级别的缓存(一级缓存)。将对象存入到一级缓存中,session 没有结束生命周期,那么对象在 session 中存放着。
- 内存中包含 Session 实例 --> Session 的缓存(一些集合) --> 集合中包含的是缓存对象!
-
证明一级缓存的存在,编写查询的代码即可证明
- 在同一个 Session 对象中两次查询,可以证明使用了缓存。
-
测试代码:
@Test public void run3(){ Session session = HibernateUtils.getSession(); Transaction tr = session.beginTransaction(); // 最简单的证明,查询两次 User user1 = session.get(User.class, 1); System.out.println(user1.getName()); User user2 = session.get(User.class, 1); System.out.println(user2.getName()); tr.commit(); session.close(); }
-
控制台只输出一次如下信息:
Hibernate: insert into t_user (version, name, age) values (?, ?, ?)
进一步分析,若采用断点的方式 debug,发现在执行
User user2 = session.get(User.class, 1);
时,控制台不输出任何信息,所以证明了一级缓存的存在。
-
Hibernate 框架是如何做到数据发生变化时进行同步操作的呢?
- 实验步骤: 使用 get 方法查询 User 对象,然后设置 User 对象的一个属性,注意:没有做 update 操作。最后发现,数据库中的记录也改变了。
- 原因:利用快照机制来完成的(SnapShot),且该特性正好和持久态拥有自动更新数据库能力相符合。
-
快照机制:
-
-
控制 Session 的一级缓存(了解)
- 学习Session接口中与一级缓存相关的方法
-
Session.clear() -- 清空缓存。
-
测试代码:
/** * Session.clear() -- 清空缓存。 */ @Test public void run5(){ Session session = HibernateUtils.getSession(); Transaction tr = session.beginTransaction(); // 最简单的证明,查询两次 User user1 = session.get(User.class, 1); System.out.println(user1.getName()); // 清空缓存 session.clear(); User user2 = session.get(User.class, 1); System.out.println(user2.getName()); tr.commit(); session.close(); }
-
控制台输出:
Hibernate: select user0_.id as id1_1_0_, user0_.version as version2_1_0_, user0_.name as name3_1_0_, user0_.age as age4_1_0_ from t_user user0_ where user0_.id=? 隔离老王 Hibernate: select user0_.id as id1_1_0_, user0_.version as version2_1_0_, user0_.name as name3_1_0_, user0_.age as age4_1_0_ from t_user user0_ where user0_.id=? 隔离老王
由此可见,clear方法可以清除缓存。
-
-
Session.evict(Object entity) -- 从一级缓存中清除指定的实体对象。
- 若把上面代码中的
session.clear();
改为session.evict(user1);
,则控制台输出仍如上。
- 若把上面代码中的
-
Session.flush() -- 刷出缓存
在一般的快照机制中,是在事务提交时(
tr.commit();
),比较缓存与快照的不同,然后 Hibernate 框架自动执行update
SQL 语句,再更新数据库。而如果调用 flush 方法,则在执行该方法时就比较缓存与快照的不同,然后 Hibernate 框架自动执行
update
SQL 语句,最后在事务提交时更新数据库。
上面两点的区别本人经过断点调试一一验证,以保证其正确性。
-
- 学习Session接口中与一级缓存相关的方法
4、Hibernate 中的事务与并发
-
事务相关的概念
-
什么是事务
- 事务就是逻辑上的一组操作,组成事务的各个执行单元,操作要么全都成功,要么全都失败.
- 转账的例子:冠希给美美转钱,扣钱,加钱。两个操作组成了一个事情!
-
事务的特性
- 原子性 -- 事务不可分割.
- 一致性 -- 事务执行的前后数据的完整性保持一致.
- 隔离性 -- 一个事务执行的过程中,不应该受到其他的事务的干扰.
- 持久性 -- 事务一旦提交,数据就永久保持到数据库中.
-
如果不考虑隔离性:引发一些读的问题
- 脏读 -- 一个事务读到了另一个事务未提交的数据(数据库隔离中最重要的问题)
- 不可重复读 -- 一个事务读到了另一个事务已经提交的 update 数据,导致多次查询结果不一致.
- 虚读 -- 一个事务读到了另一个事务已经提交的 insert 数据,导致多次查询结构不一致.
-
通过设置数据库的隔离级别来解决上述读的问题
- 未提交读:以上的读的问题都有可能发生.
- 已提交读:避免脏读,但是不可重复读,虚读都有可能发生.
- 可重复读:避免脏读,不可重复读.但是虚读是有可能发生.
- 串行化:以上读的情况都可以避免.
-
如果想在Hibernate的框架中来设置隔离级别,需要在 hibernate.cfg.xml 的配置文件中通过标签来配置
- 通过:hibernate.connection.isolation = 4 来配置
- 取值:
- 1—Read uncommitted isolation
- 2—Read committed isolation
- 4—Repeatable read isolation
- 8—Serializable isolation
-
-
丢失更新的问题
如果不考虑隔离性,也会产生写入数据的问题,这一类的问题叫丢失更新的问题。
-
例如:两个事务同时对某一条记录做修改,就会引发丢失更新的问题。
- A 事务和 B 事务同时获取到一条数据,同时再做修改
- 如果 A 事务修改完成后,提交了事务
-
B 事务修改完成后,不管是提交还是回滚,如果不做处理,都会对数据产生影响
-
解决方案有两种
-
悲观锁
-
采用的是数据库提供的一种锁机制,如果采用做了这种机制,在SQL语句的后面添加 for update 子句
当A事务在操作该条记录时,会把该条记录锁起来,其他事务是不能操作这条记录的。
只有当A事务提交后,锁释放了,其他事务才能操作该条记录
-
-
乐观锁
-
使用的不是数据库锁机制,而是采用版本号的机制来解决的。给表结构添加一个字段
version=0
,默认值是0当 A 事务在操作完该条记录,提交事务时,会先检查版本号,如果发生版本号的值相同时,才可以提交事务。同时会更新版本号
version=1
.当 B 事务操作完该条记录时,提交事务时,会先检查版本号,如果发现版本不同时,程序会出现错误。
-
-
-
使用 Hibernate 框架解决丢失更新的问题
-
悲观锁(效率较低,不常见)
- 使用
session.get(Customer.class, 1,LockMode.UPGRADE);
方法
- 使用
-
乐观锁
在对应的 JavaBean 中添加一个属性,名称可以是任意的。例如:
private Integer version;
提供 get 和 set 方法在映射的配置文件中,提供
<version name="version"/>
标签即可。
-
附上本人以前学习的笔记,从 JDBC 角度分析事务、隔离级别、丢失更新问题:http://www.jianshu.com/p/aacde54542b5
-
绑定本地的 Session
-
在上文所附的文章里,讲解了 JavaWEB 的事务,需要在业务层使用 Connection 来开启事务。
- 一种是通过参数的方式,一层一层的传递下去
- 另一种是把 Connection 绑定到 ThreadLocal 对象中,因为 ThreadLocal 对于内通过 map 存储了
key = 当前线程
,value= Connection对象
ThreadLocal 两个重要方法
- get
public T get() 返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。
返回:
此线程局部变量的当前线程的值
- set
public void set(T value) 将此线程局部变量的当前线程副本中的值设置为指定值。大部分子类不需要重写此方法,它们只依靠 initialValue() 方法来设置线程局部变量的值。
参数:
value - 存储在此线程局部变量的当前线程副本中的值。
-
在 Hibernate 框架中,使用 session 对象开启事务,而 session 对象存在于业务层中,所以需要把 session 对象传递到持久层,在持久层使用 session 对象操作数数据对象,框架提供了 ThreadLocal 的方式:
-
首先需要在 hibernate.cfg.xml 的配置文件中提供配置
<property name="hibernate.current_session_context_class">thread</property>
-
然后重写 HibernateUtil 的工具类,使用 SessionFactory 的 getCurrentSession( )方法,获取当前的 Session 对象。并且该 Session 对象不用手动关闭,线程结束了,会自动关闭。
-
HibernateUtil 工具类:
package com.itheima.utils; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; /** * Hibernate框架的工具类 * @author Administrator */ public class HibernateUtils { // ctrl + shift + x private static final Configuration CONFIG; private static final SessionFactory FACTORY; // 编写静态代码块 static{ // 加载XML的配置文件 CONFIG = new Configuration().configure(); // 构造工厂 FACTORY = CONFIG.buildSessionFactory(); } /** * 从工厂中获取Session对象 * @return */ public static Session getSession(){ return FACTORY.openSession(); } /** * // 从ThreadLocal类中获取到session的对象 * @return */ public static Session getCurrentSession(){ return FACTORY.getCurrentSession(); } }
-
UserService 业务层:
package com.itheima.service; import org.hibernate.Session; import org.hibernate.Transaction; import com.itheima.dao.UserDao; import com.itheima.domain.User; import com.itheima.utils.HibernateUtils; public class UserService { public void save(User u1,User u2){ UserDao dao = new UserDao(); // 获取session Session session = HibernateUtils.getCurrentSession(); // 开启事务 Transaction tr = session.beginTransaction(); try { dao.save1(u1); int a = 10/0; dao.save2(u2); // 提交事务 tr.commit(); } catch (Exception e) { e.printStackTrace(); // 出现问题:回滚事务 tr.rollback(); }finally{ // 自己释放资源,现在,session不用关闭,线程结束了,自动关闭的!! } } }
-
UserDao 持久层:
package com.itheima.dao; import org.hibernate.Session; import com.itheima.domain.User; import com.itheima.utils.HibernateUtils; public class UserDao { public void save1(User u1){ Session session = HibernateUtils.getCurrentSession(); session.save(u1); //不用写 session.close; } public void save2(User u2){ Session session = HibernateUtils.getCurrentSession(); session.save(u2); } }
-
注意:想使用 getCurrentSession() 方法,必须要先配置才能使用。
-
-
5、Hibernate 的查询方式(HQL:Hibernate Query Language)
-
Query 查询接口
-
具体的查询代码如下:
// 1.查询所有记录 /*Query query = session.createQuery("from Customer"); List<Customer> list = query.list(); System.out.println(list);*/ // 2.条件查询(? 从0开始) /*Query query = session.createQuery("from Customer where name = ?"); query.setString(0, "李健"); List<Customer> list = query.list(); System.out.println(list);*/ // 3.条件查询(设置别名) /*Query query = session.createQuery("from Customer where name = :aaa and age = :bbb"); query.setString("aaa", "李健"); query.setInteger("bbb", 38); List<Customer> list = query.list(); System.out.println(list);*/ // 4.模糊查询(注意 % 要写在 setString 里面) /*Query query = session.createQuery("from User where name like ?"); query.setString(0, "%老%"); List<Customer> list = query.list(); System.out.println(list);*/
在 JDBC 中,记录从1开始!
在 HQL 中,记录从0开始!
-
-
Criteria 查询接口(做条件查询非常合适)
完全面向对象,代码中基本不会体现 SQL 语言的特点
Criterion 是 Hibernate 提供的条件查询的对象,如果想传入条件,可以使用工具类 Restrictions ,在其内部有许多静态方法可以用来描述条件
-
具体的查询代码如下
// 1.查询所有记录 /*Criteria criteria = session.createCriteria(Customer.class); List<Customer> list = criteria.list(); System.out.println(list);*/ // 2.条件查询(查询 name 字段为李健的记录) /*Criteria criteria = session.createCriteria(Customer.class); criteria.add(Restrictions.eq("name", "李健")); List<Customer> list = criteria.list(); System.out.println(list);*/ // 3.条件查询(查询 name 字段为李健、age 字段为38的记录) /*Criteria criteria = session.createCriteria(Customer.class); criteria.add(Restrictions.eq("name", "李健")); criteria.add(Restrictions.eq("age", 38)); List<Customer> list = criteria.list(); System.out.println(list);*/ // Restrictions 提供了许多静态方法来描述条件 criteria.add(Restrictions.gt("age", 18)); // gt:大于 criteria.add(Restrictions.like("name", "%小%")); // like:模糊查询