Hibernate的缓存和几种特殊的映射
一. 理解对象的状态
Hibernate中对象的几种状态: 临时状态,持久化状态,游离状态
临时状态: 直接new关键字创建的普通对象,不在session下管理,数据库中没有对应的记录,即普通的一个实例.
持久化状态: 当调用session的save/saveOrUpdate/get/load/list等方法的时候,对象就是持久化状态,处于持久化状态的对象,当对对象的属性进行修改的时候,会反映到数据库中.
特点: 处于session的管理中,数据库中有对应的记录.
游离状态: 不处于session的管理,数据库有对应的记录,session关闭后,对象的状态会不存在.
二. 一级缓存
使用缓存的目的: 减少对数据库的访问次数,提升Hibernate的执行效率,Hibernate中的缓存分为一级缓存和二级缓存.
一级缓存基本概念:
* Hibernate的一级缓存: 也叫做session的缓存,可以在session范围内减少数据库的访问次数,只在session范围内有用,session关闭,一级缓存失效.
* 当调用session的save/saveOrUpdate/get/load/list/iterator 等方法的时候,都会把对象放入session的缓存中.
* session的缓存由Hibernate维护,用户不能操作缓存的内容,如果需要操作缓存的内容,必须通过Hibernate提供的evict/clear方法操作.
特点: 只在当前session有效,作用时间断,效果不是特别明显.在短时间内多处操作数据库,效果比较明显.
缓存相关的几个方法:
session.flush() : 刷新缓存,让一级缓存与数据库同步
session.evict(obj) 清空一级缓存中指定的对象
session.clear() 清空一级缓存中的所有对象
使用场景: 在批量操作的时候
session.flush(); //先与数据库同步
session.clear(); //再清空一级缓存内容
注意点:
不同的session不会共享缓存数据. 比如Dept dept = session1.get(Dept.class,1);
session2.update(dept);//dept放入session2的缓存.
dept.setDeptName("name");//如果生成2条update SQL语句,说明不同的session使用不同的缓存区,不能共享数据.
list和iterator的区别:
list():会一次性把所有记录都查询出来,会放入缓存,但是不会从缓存中获取数据.
iterator: N+1次查询,N表示总的记录数,即会先发送一条语句查询所有的记录的主键(1次)
再根据每一个主键去数据库查询(N)
会把数据放入缓存,也会从缓存中取数据.
- Hibernate的一级缓存测试:
Javabean设计
Dept.java
package com.mrq.domain;
public class Dept {
private Integer deptId;
private String deptName;
public Integer getDeptId() {
return deptId;
}
public void setDeptId(Integer deptId) {
this.deptId = deptId;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
@Override
public String toString() {
return "Dept [deptId=" + deptId + ", deptName=" + deptName + "]";
}
}
Dept.hbm.xml配置
- 缓存测试
@Test
public void testCache() throws Exception{
Session session = sf.openSession();
session.beginTransaction();
Dept dept = null;
dept = (Dept)session.get(Dept.class, 1);//向数据库查询
System.out.println(dept);
dept = (Dept)session.get(Dept.class, 1);//不查询,使用session缓存
session.getTransaction().commit();
session.close();
}
运行结果类似:
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Dept [deptId=1, deptName=人事部]
从运行的结果分析:这里使用了两个get方法,但是只进行了一次SQL查询,说明第二次的get是从缓存中获取
- flush方法同步数据
@Test
public void flush() throws Exception {
Session session = sf.openSession();
session.beginTransaction();
Dept dept = null;
dept = (Dept) session.get(Dept.class, 5);
dept.setDeptName("行政部");
// 刷新数据,和数据库同步
session.flush();
dept.setDeptName("研发部");
session.getTransaction().commit(); // 相当于session.flush();
session.close();
}
结果如下:
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Hibernate: update t_dept2 set deptName=? where deptId=?
Hibernate: update t_dept2 set deptName=? where deptId=?
进行了2次update,一次是flush,一次是commit操作.
- clear清除缓存
@Test
public void clear() throws Exception {
Session session = sf.openSession();
session.beginTransaction();
Dept dept = null;
//放入缓存
dept = (Dept) session.get(Dept.class, 5);
// 清除缓存对象
//session.clear(); // 全部清除
session.evict(dept);// 清除特定对象
dept = (Dept) session.get(Dept.class, 5);//重新从数据库获取对象
session.getTransaction().commit(); // session.flush();
session.close();
}
结果:
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
清除缓存后
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
进行了2次select操作,一次是在清除缓存之后
- 不同的session不共享数据
@Test
public void sessionTest() throws Exception {
Session session1 = sf.openSession();
session1.beginTransaction();
Session session2 = sf.openSession();
session2.beginTransaction();
// dept放入session1中
Dept dept = (Dept) session1.get(Dept.class, 1);
// dept放入session2中
session2.update(dept);
//
dept.setDeptName("人事部");
session1.getTransaction().commit(); // session1.flush();
session1.close();
session2.getTransaction().commit(); // session2.flush();
session2.close();
}
运行结果:
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Hibernate: update t_dept2 set deptName=? where deptId=?
Hibernate: update t_dept2 set deptName=? where deptId=?
生成2条update SQL语句,说明不同的session使用不同的缓存区,不能共享数据.
2.1 list和iterator的缓存
- list操作
@Test
public void list() {
Session session = sf.openSession();
session.beginTransaction();
//HQL查询
Query query = session.createQuery("from Dept");
List list = query.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
运行结果:
Hibernate: select dept0_.deptId as deptId0_, dept0_.deptName as deptName0_ from t_dept2 dept0_
[Dept [deptId=1, deptName=事业部], Dept [deptId=2, deptName=文化部1], Dept [deptId=3, deptName=文化部2], Dept [deptId=4, deptName=文化部3], Dept [deptId=5, deptName=研发部]]
可以看到,list操作会一次性的把数据读取出来
- iterator操作
@Test
public void iterator() {
Session session = sf.openSession();
session.beginTransaction();
//HQL查询
Query query = session.createQuery("from Dept");
Iterator iterator = query.iterate();
while (iterator.hasNext()) {
Dept dept = (Dept) iterator.next();
System.out.println(dept);
}
session.getTransaction().commit();
session.close();
}
运行结果:
Hibernate: select dept0_.deptId as col_0_0_ from t_dept2 dept0_ //执行一次次数查询操作
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Dept [deptId=1, deptName=事业部]
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Dept [deptId=2, deptName=文化部1]
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Dept [deptId=3, deptName=文化部2]
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Dept [deptId=4, deptName=文化部3]
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Dept [deptId=5, deptName=研发部]
可以看到,iterator方法,会先进行一次次数查询,再进行N次对象查询.
- list和iterator的缓存测试
list:
Session session = sf.openSession();
session.beginTransaction();
Query query = session.createQuery("from Dept");
List list = query.list();
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("==============List====");
list = query.list();
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
session.getTransaction().commit();
session.close();
运行结果1:
Hibernate: select dept0_.deptId as deptId0_, dept0_.deptName as deptName0_ from t_dept2 dept0_
Dept [deptId=1, deptName=事业部]
Dept [deptId=2, deptName=文化部1]
Dept [deptId=3, deptName=文化部2]
Dept [deptId=4, deptName=文化部3]
Dept [deptId=5, deptName=研发部]
==============List====
Hibernate: select dept0_.deptId as deptId0_, dept0_.deptName as deptName0_ from t_dept2 dept0_
Dept [deptId=1, deptName=事业部]
Dept [deptId=2, deptName=文化部1]
Dept [deptId=3, deptName=文化部2]
Dept [deptId=4, deptName=文化部3]
Dept [deptId=5, deptName=研发部]
从list方法运行结果分析: 两次list都进行了同样的向数据库发送SQL语句进行查询.表示list不会从session中获取数据.
iterator:
/*
* Iterator
* */
Session session = sf.openSession();
session.beginTransaction();
Query query2 = session.createQuery("from Dept");
Iterator iterator = query2.iterate();
while (iterator.hasNext()) {
Dept dept = (Dept) iterator.next();
System.out.println(dept);
}
System.out.println("====iterator===");
iterator = query2.iterate();
while (iterator.hasNext()) {
Dept dept = (Dept) iterator.next();
System.out.println(dept);
}
session.getTransaction().commit();
session.close();
运行结果2:
Hibernate: select dept0_.deptId as col_0_0_ from t_dept2 dept0_
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Dept [deptId=1, deptName=事业部]
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Dept [deptId=2, deptName=文化部1]
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Dept [deptId=3, deptName=文化部2]
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Dept [deptId=4, deptName=文化部3]
Hibernate: select dept0_.deptId as deptId0_0_, dept0_.deptName as deptName0_0_ from t_dept2 dept0_ where dept0_.deptId=?
Dept [deptId=5, deptName=研发部]
====iterator===
Hibernate: select dept0_.deptId as col_0_0_ from t_dept2 dept0_
Dept [deptId=1, deptName=事业部]
Dept [deptId=2, deptName=文化部1]
Dept [deptId=3, deptName=文化部2]
Dept [deptId=4, deptName=文化部3]
Dept [deptId=5, deptName=研发部]
iterator结果分析: 先进行一次次数查询,再执行N次查询对象. 第二次iterator查询,只进行了一次次数查询,并不进行对象查询,而是直接从缓存中获取.
下面的测试也证明:list获取会把数据缓存到session,iterator操作也可以从list缓存的数据中获取对象
@Test
public void list_iterator() throws Exception {
Session session = sf.openSession();
session.beginTransaction();
Query q = session.createQuery("from Dept");
//
List list = q.list();
for (int i=0; i it = q.iterate();
while(it.hasNext()){
Dept user = it.next();
System.out.println(user);
}
session.getTransaction().commit();
session.close();
}
运行结果如下
Hibernate: select dept0_.deptId as deptId0_, dept0_.deptName as deptName0_ from t_dept2 dept0_
Dept [deptId=1, deptName=事业部]
Dept [deptId=2, deptName=文化部1]
Dept [deptId=3, deptName=文化部2]
Dept [deptId=4, deptName=文化部3]
Dept [deptId=5, deptName=研发部]
Hibernate: select dept0_.deptId as col_0_0_ from t_dept2 dept0_
Dept [deptId=1, deptName=事业部]
Dept [deptId=2, deptName=文化部1]
Dept [deptId=3, deptName=文化部2]
Dept [deptId=4, deptName=文化部3]
Dept [deptId=5, deptName=研发部]
三. 懒加载
get方法和load方法的区别:
get: 及时加载,只要调用get方法立即向数据库查询
load. 默认使用懒加载,当用到数据的时候才向数据库查询
懒加载(lazy) :当用到数据的时候才向数据库查询,是Hibernate的懒加载特性,主要的目的是提供程序的执行相率
lazy的取值:
TRUE: 使用懒加载
FALSE: 关闭懒加载
extra: 在集合数据懒加载的时候提升效率,在真正使用数据的时候才向数据库发送查询SQL语句;如果调用集合的size()/isEmpty()方法,只是统计,不真正查询数据
• 懒加载异常
Session关闭后,不能使用懒加载数据!
如果session关闭后,使用懒加载数据报错:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
如何解决session关闭后不能使用懒加载数据的问题?
// 方式1: 先使用一下数据
//dept.getDeptName();
// 方式2:强迫代理对象初始化
Hibernate.initialize(dept);
// 方式3:关闭懒加载
设置lazy=false;
// 方式4: 在使用数据之后,再关闭session!
四: 特别的多对一映射(一对一映射)
一对一映射的例子:身份证与用户的关系
映射有两种方式:基于外键映射(用户使用外键,引用身份证ID)和基于主键映射(用户ID左外身份证ID),两种方式设计都差不多,主要在于映射文件的设计
4.1 基于外键的映射配置
javabean设计.
User.java
package com.mrq.domain;
public class User {
private Integer userId;
private String userName;
private IdCard idCard;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public IdCard getIdCard() {
return idCard;
}
public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}
}
身份证IdCard.java
package com.mrq.entity;
//身份证类
public class IdCard {
private String cardNum;//身份证主键
private String address;//身份证地址
private User user;//身份证与用户一对一关系
public String getCardNum() {
return cardNum;
}
public void setCardNum(String cardNum) {
this.cardNum = cardNum;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
- 映射文件的配置要点:
一对一映射,有外键方
unique="true" 给外键字段添加唯一约束
User.hbm.xml
IdCard.hbm.xml
测试方法
//一对一映射:基于外键
@Test
public void testOne2One() {
Configuration configuration = new Configuration();
SessionFactory sf = configuration.configure().buildSessionFactory();
Session session = sf.openSession();
session.beginTransaction();
User user = new User();
user.setUserName("小明!");
IdCard idCard = new IdCard();
idCard.setAddress("山西长治");
idCard.setCardNum("8673287979Ax");
idCard.setUser(user);
session.save(idCard);
session.getTransaction().commit();
session.close();
}
4.2 基于主键的设计
一对一映射,有外键方
(基于主键的映射)
constrained="true" 指定在主键上添加外键约束
主键生成方式: foreign 即把别的表的主键作为当前表的主键;
property (关键字不能修改)指定引用的对
User bean保持不变
IdCard.java
package com.mrq.domain;
//身份证类
public class IdCard {
private Integer user_id;//主键
private String cardNum;//身份证主键
private String address;//身份证地址
private User user;//身份证与用户一对一关系
public Integer getUser_id() {
return user_id;
}
public void setUser_id(Integer user_id) {
this.user_id = user_id;
}
public String getCardNum() {
return cardNum;
}
public void setCardNum(String cardNum) {
this.cardNum = cardNum;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
映射配置文件
IdCard.hbm.xml
user
测试:
//一对一映射,基于主键
@Test
public void testOne2OneOther() {
Configuration configuration = new Configuration();
SessionFactory sf = configuration.configure().buildSessionFactory();
Session session = sf.openSession();
session.beginTransaction();
User user = new User();
user.setUserName("嘻嘻");
IdCard idCard = new IdCard();
idCard.setAddress("山西长治");
idCard.setCardNum("SDASDS3233Ax");
idCard.setUser(user);
session.save(idCard);
session.getTransaction().commit();
session.close();
}
4.3 组合(组件)映射
- 简单理解类的关系:
类的关系:
组合关系:一个类中包含了另外一个类.这两个类就是组合关系
比如: 汽车和车轮,礼物盒子和礼物
继承关系: 一个类继承另外一个类,即为继承关系
组合映射的例子:
- 组件类和被包含的组件类,共同映射到一张表
汽车和车轮
Car.java
package com.mrq.component;
public class Car {
private Integer id;
private String name;
private Wheel wheel;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Wheel getWheel() {
return wheel;
}
public void setWheel(Wheel wheel) {
this.wheel = wheel;
}
@Override
public String toString() {
return "Car [id=" + id + ", name=" + name + ", wheel=" + wheel + "]";
}
}
wheel.java
package com.mrq.component;
public class Wheel {
private Integer count;
private Integer size;
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
@Override
public String toString() {
return "Wheel [count=" + count + ", size=" + size + "]";
}
}
- 组合关系映射的要点: component节点的使用
Car.hbm.xml
4.4 继承映射的4种配置方式
- 方式一:简单继承关系映射,直接写父类的属性,每个子类对应一个表
Animal.java
package com.mrq.extends1;
public abstract class Animal {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Cat类继承Animal类
package com.mrq.extends1;
public class Cat extends Animal{
private String catchMouse;
public String getCatchMouse() {
return catchMouse;
}
public void setCatchMouse(String catchMouse) {
this.catchMouse = catchMouse;
}
}
简单继承的映射配置,和普通的类类似
- 方式二:所有子类映射到一张表
什么情况用?
子类教多,且子类较为简单,即只有个别属性!
好处:因为使用一个映射文件, 减少了映射文件的个数。
缺点:(不符合数据库设计原则)
一个映射文件: Animal.hbm.xml
通过鉴别器区分是哪个子类的信息
Animal.hbm.xml
- 关键节点discriminator和sub-class
写法较为简单:所有子类用一个映射文件,且映射到一张表!
但数据库设计不合理!
(不推荐用。)
- 方式三: 每一个类都映射一张表,包括父类
关键节点:joined-subclass
Animal.hbm.xml
一个映射文件,存储所有的子类; 子类父类都对应表;
缺点:表结构比较负责,插入一条子类信息,需要用2条sql: 往父类插入、往子类插入!
- 方式四: 每个子类映射一张表, 父类不对应表(推荐使用)
关键节点:union-class,同时主键不能使用自增长的策略
Animal.hbm.xml
所有的子类都写到一个映射文件;
父类不对应表; 每个子类对应一张表