Hibernate的缓存和几种特殊的映射(09)

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







    
        
        
            
        
        
        
        
        
         
            
         
         
        
            
        
         
         
        
    

所有的子类都写到一个映射文件;
父类不对应表; 每个子类对应一张表

你可能感兴趣的:(Hibernate的缓存和几种特殊的映射(09))