一、hbm 映射文件配置 (持久化配置和操作)
1、持久化对象(Persistent Object)
PO = POJO + hbm映射配置
编写规则:
1)必须提供无参数public构造器;
2)所有属性都为private,提供public的getter和setter方法;
3)必须提供标识属性,与数据表中主键对应,例如Customer类id属性;
4)PO类的属性应该尽量使用基本数据类型的包装类型。
5)不要用final修饰(将无法生成代理对象进行优化)
问题:Session的get方法和load方法的区别?
session.get 方法, 查询立即执行 , 返回Customer类对象
session.load 方法,默认采用延迟加载数据方式,不会立即查询,返回 Customer类子类对象 (动态生成代理对象)
* 如果 PO类使用final修饰,load无法创建代理对象,返回目标对象本身 (load效果和 get效果 相同 )
分析图解如下:
2、OID
Java按地址区分同一个类的不同对象。
关系型数据库用主键区分一条记录。
Hibernate使用OID来建立内存中的对象和数据库中记录的对应关系。
对象的OID和数据库的表的主键对应。为保证OID的唯一性,应该让Hibernate为OID赋值。
OID指与数据表中主键对应PO类中属性,例如:Customer类中的id属性。
Hibernate框架使用OID来区分不同PO对象。
*例如:内存中有两个PO对象,只要具有相同OID,Hibernate会认为它们是同一个对象。
*Hibernate不允许缓存同样OID的两个不同对象。
3、自然主键和代理主键
使用具有业务含义字段作为主键 ----- 自然主键 ,例如:身份证号, 用户名
随机生成,不具备业务含义的字段 ----- 代理主键 , 例如 流水号 ,UUID
*** 现在企业系统,大多使用 代理主键
4、基本数据类型和包装类型
使用基本类型 ,无法区分 0 和 null , 使用int类型分数,如果学生分数为0 可以没有考试, 也可能考试得了0分
使用包装类型,如果不设置数据,数据表存放null ,而不是默认值 0
* 以后开发中,PO类属性 都使用包装类型
5、hbm文件配置
配置类到指定数据库中表的映射。
<class name="lsq.hibernate.domain.Customer" table="customer" catalog="hibernate3day1">
* 省略 catalog, 表会创建到 jdbcurl 指定数据库中
配置类中OID属性到表主键映射:
<id name="id" column="id" type="int">
* 配置主键生成策略
<generator class="identity"></generator>
配置普通属性的映射
<property name="name" column="name" type="java.lang.String"></property>
* 在配置过程中,省略column 和 type ,如果不写column 生成列名就是属性名, 不写type将按照 类中属性类型自动映射
* 配置列其它属性 unique="true" 唯一 not-null="true" 非空 length="20" 长度
6、主键生成策略
在<id>元素中 通过 <generator class="生成策略"></generator> 指定数据表主键生成策略
常用六种主键生成策略:
1) increment
increment 标识符生成器由 Hibernate 以递增的方式为代理主键赋值
原理:select max(id) , insert max(id)+1
* 使用 increment 创建数据表 没有主键自增长, 通过hibernate在程序内部完成自增
* 好处跨平台 ,缺点 高并发访问时,可以出现主键冲突问题
主键冲突报错如下:
2)identity(抢占主键,无线程问题)---Mysql
identity标识符生成器由底层数据库来负责生成标识符,它要求底层数据库把主键定义为自动增长字段类型。
原理: 依赖数据库内部自增长,和hibernate无关
* 创建数据表 `id` int(11) NOT NULL AUTO_INCREMENT
* 优点,无需程序处理,数据库自己完成主键增长,缺点 Mysql支持 自增主键, oracle 不支持
3)sequence--- Oracle
sequence 标识符生成器利用底层数据库提供的序列来生成标识符
原理: 依赖数据库序列支持 ,和hibernate程序无关
* Oracle 支持序列, Mysql 不支持序列
* 序列原理
create sequence customer_seq;
insert into customer(id) values(customer_seq.nextval); 自动序列+1
4)native(本地策略)
native 标识符生成器依据底层数据库对自动生成标识符的支持能力, 来选择使用 identity, sequence 或 hilo 标识符生成器.
* Mysql 自动选择 identity , oracle 自动选择 sequence
5)uuid
uuid 的主键生成,采用String 类型主键
随机生成32位字符串
6)assigned
前五种策略,都是代理主键生成策略
assigned 是自然主键生成策略 ---- 必须用户在程序中指定 (无法自动生成)
* 复合主键 (联合主键),一个数据表中 多列共同作为主键 --------- 复合主键是一种特殊 assigned 策略
<composite-id>
<!-- 配置多列 -->
<key-property name="firstname"></key-property>
<key-property name="secondname"></key-property>
</composite-id>
错误:Caused by: org.hibernate.MappingException: composite-id class must implement Serializable: cn.itcast.domain.Person
复合主键类 必须实现序列化接口
二、Hibernate持久化对象状态
1、持久化对象的三种状态
*transient瞬时态(临时态、自由态):不存在持久化OID,尚未与Hibernate Session关联对象,被认为处于瞬时状态,失去引用将被JVM回收
*persist持久态:存在持久化标识OID,与当前session有关联,并且相关联的session没有关闭,并且事务未提交。
*detached 脱管态(离线态、游离态) : 存在持久化标识OID,但没有与当前session关联,脱管状态改变hibernate不能检测到
区分三种状态, 判断对象是否有OID,判断对象是否与Session关联(被一级缓存引用 )
package lsq.hibernate.firstcache;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import lsq.hibernate.utils.HibernateUtils;
/**
* 测试持久化对象操作(一级缓存)
* @author Daniel Li
*
*/
public class HibernateTest {
//区分持久化对象三种状态
@Test
public void demo1(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Book book = new Book();//瞬时态(没有OID,未与Session关联)
book.setName("Hibernate入门");
book.setPrice(23d);
session.save(book);//持久态(具有OID,与Session关联)
//提交事务关闭session
transaction.commit();
session.close();
System.out.println(book.getId());//脱管态(具有OID,与Session断开连接)
}
}
2、 持久化对象状态转换
1) 瞬时态对象 通过new获得
瞬时--持久 save 、 saveOrUpdate (都是Session)
瞬时--脱管 book.setId(1); 为瞬时对象设置OID
2) 持久态对象 get/load 、Query查询获得
持久--瞬时 delete (被删除持久化对象 不建议再次使用 )
持久--脱管 evict(清除一级缓存中某一个对象)、close(关闭Session,清除一级缓存)、clear(清除一级缓存所有对象 )
3) 脱管态对象 无法直接获得
脱管--瞬时 book.setId(null); 删除对象OID
脱管--持久 update、saveOrUpdate、 lock(过时)
3、 Session中一级缓存
Hibernate框架共有两级缓存, 一级缓存(Session级别缓存) 、 二级缓存 (SessionFactory级别缓存)
Hibernate Session接口 实现类 SessionImpl 类
* private transient ActionQueue actionQueue; ---- 行动队列
* private transient StatefulPersistenceContext persistenceContext; ---- 持久化上下文
持久化对象保存Session 一级缓存中 (一级缓存 引用 持久化对象 地址 ), 只要Session不关闭,一级缓存存在,缓存中对象 也不会被回收
Session会在一些特定时间点,将缓存中数据flush 到数据库
* Transaction 的 commit()
* 应用程序执行一些查询操作时
* 调用 Session 的 flush() 方法
案例一: 证明一级缓存是存在的
package lsq.hibernate.firstcache;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import lsq.hibernate.utils.HibernateUtils;
/**
* 测试持久化对象操作(一级缓存)
* @author Daniel Li
*
*/
public class HibernateTest {
//案例一: 证明一级缓存是存在的
@Test
public void demo2(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Book book = (Book) session.get(Book.class, 1);
System.out.println(book);
Book book2 = (Book) session.get(Book.class, 1);
System.out.println(book2);
transaction.commit();
session.close();
}
}
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> Book book = (Book) session.get(Book.class, 1); // 第一次查询,缓存中没有</span>
System.out.println(book);
Book book2 = (Book) session.get(Book.class, 1);// 因为第一次查询,对象已经被放入1级缓存,不会查询数据
System.out.println(book2);
* 生成一条SQL语句,返回同一个对象 ,第一次查询生成SQL,查询对象,将对象放入一级缓存,第二次查询,直接从一级缓存获得
案例二 : 测试Hibernate快照 (深入理解一级缓存 内存结构原理 )
hibernate 向一级缓存放入数据时,同时保存快照数据(数据库备份),当修改一级缓存数据,在flush操作时,对比缓存和快照,
如果不一致,自动更新 (将缓存的内容 同步到数据库, 更新快照)
* 快照区使用,在Session 保存一份 与数据库 相同的数据 ,在session的 flush时, 通过快照区 比较得知 一级缓存数据是否改变,如果改变执行对应 操作(update)
* Hibernate中 持久态 对象具有自动更新数据库能力 (持久态对象 才保存在 Session中,才有快照 )
4、一级缓存常见操作
1) flush : 修改一级缓存数据 针对内存操作, 需要在session执行flush操作时,将缓存变化同步到数据库
* 只有在 缓存数据 与 快照区 不同时,生成update 语句
2) clear : 清除所有对象 一级缓存
3) evict : 清除一级缓存 指定对象
4) refresh :重新查询数据库,更新快照和一级缓存
5、 一级缓存 刷出时间点 设置 (FlushMode)
ALWAYS 在每次查询时,session都会flush (不要求 )
AUTO : 在有些查询时,session会flush (默认) ---------- 查询、commit 、session.flush
COMMIT : 在事务提交时,session会flush ------- commit 、session.flush
MANUAL :只有手动调用 session.flush 才会刷出 ---- session.flush
刷出条件(时间点严格程度 )
MANUAL > COMMIT> AUTO> ALWAYS
6、session持久化对象 操作方法
1) save 将数据保存到数据库 , 将瞬时对象 转换 持久对象
持久化对象,不允许随便修改 OID
2) update 更新数据 ,主要用于脱管对象的更新 (持久对象,可以根据快照自动更新 ), 将脱管对象 转换 持久对象
问题一: 调用update,默认直接生成update语句,如果数据没有改变,不希望生成update
在hbm文件 <class>元素 添加 select-before-update
问题二: 当update,脱管对象变为持久对象, 一级缓存不允许出现相同OID 两个持久对象
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [lsq.hibernate.firstcache.Book#1]
问题三 : 脱管对象 OID 在数据表中 不存在,update时,发生异常
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [lsq.hibernate.firstcache.Book#20]
3) saveOrUpdate , 如果参数是一个瞬时对象 执行save, 如果参数 是一个脱管对象 执行update, 如果参数是持久对象 直接返回
判断对象是瞬时对象 : OID为null , 在hbm文件中为 <id>元素指定 unsaved-value属性,如果PO对象OID为 unsaved-value 也是瞬时对象
<id name="id" unsaved-value="-1"> 如果对象 OID为-1 也是瞬时对象
4) get/load
如果查询OID 不存在, get方法 返回 null , load 方法返回代理对象 (代理对象初始化时 抛出 ObjectNotFoundException )
5) delete 方法
方法既可以删除一个脱管对象, 也可以删除一个持久化对象
如果删除脱管,先将脱管对象 与 Session 关联,然后再删除
执行delete,先删除一级缓存数据,在session.flush 操作时,删除数据表中数据