Hibernate(4)——主键生成策略、CRUD 基础API区别的总结 和 注解的使用

俗话说,本身写的代码,6个月后也是别人的代码……复习!复习!复习!涉及的知识点总结以下:java

  • hibernate的主键生成策略
  • UUID
  • 配置的补充:hbm2ddl.auto属性用法
  • 注解仍是配置文件
  • hibernate注解的基本用法
  • 使用Session API CRUD操做对象,以及对象状态的转换
  • hibernate缓存的概念
  • get()/load()的区别究竟是什么,源码分析
  • 代理模式实现的懒加载
  • saveOrUpdate()/merge()的区别 

  

  Assigned(经常使用,通常状况使用很方便):mysql

  由程序生成主键值,而且在save()以前指定,不然会抛出异常。web

  特色:主键的生成值彻底由用户决定,与底层数据库无关。用户须要维护主键值,在调用session.save()以前要指定主键值。惟一的例外:int auto_increment类型主键除外,在程序中不用指定主键值,直接看代码:算法

sid是主键,且是int类型,设置为自动增加。sql

/**
 * StudentDao
 *
 * @author Wang Yishuai.
 * @date 2016/3/11 0011.
 * @Copyright(c) 2016 Wang Yishuai,USTC,SSE.
 */
public class StudentDao {
    private static final Logger LOG = LoggerFactory.getLogger(StudentDao.class);

    private Session session;

    @Before
    public void init() {
        Configuration configuration = new Configuration().configure();
        SessionFactory sessionFactory = configuration.buildSessionFactory();
        this.session = sessionFactory.getCurrentSession();
    }

    @After
    public void clear() {

    }

    @Test
    public void save() {
        Transaction transaction = session.beginTransaction();

        Student student = new Student();

        try {
            //student.setSname("dashuai");
            student.setSname("dadadad");
            session.save(student);
            transaction.commit();
        } catch (Exception e) {
            LOG.error("save error", e);
            transaction.rollback();
        }
    }
}
View Code

配置文件设置:数据库

<hibernate-mapping>
    <!-- class 里面的属性必须和对应的类名,表名保持一致,实现映射-->
    <class name="zhujian.vo.Student" table="students">
        <!-- id表明数据库表的主键, name="userId",对应数据库表里的字段column="userId"-->
        <id name="sid" column="sid" type="string">
            <generator class="assigned"/>
        </id>
        <!-- 数据库里其余普通字段和实体属性的映射,属性的类型须要小写-->
        <property name="sname" column="sname" type="string"/>
    </class>
</hibernate-mapping>
View Code

前后保存两个学生对象,没有错误,以下:设计模式

即便我设置了主键生成策略为assigned,由于学生表的sid是自动增加且为int类型,因此程序中不用人工指定sid值,这是一个例外状况。其余状况若是设置主键为asigned策略,必须手动设主键值,不然会出问题。api

  把sid的自动增加设置取消,再运行以前的代码,会发生问题:若是不设置sid(主键)值,则没法成功插入数据,且会生成主键为0的数据记录(if以前没有),本地测试没有报错,可是没法成功插入。数组

  把主键改成String(varchar)类型(对象映射文件和实体类也须要修改),再运行以前的代码,一样只能插入sid为0的记录(if以前没有),其余的没法继续插入。若改成手动设置主键值,则恢复正常。缓存

 

  Increment(MySQL不适用,集群不适用,多进程不适用,与底层数据库有关)

  Increment方式对主键值采起自动增加的方式生成新的主键值,但要求底层数据库支持Sequence序列。如Oracle,DB2等。须要在映射文件xxx.hbm.xml中加入Increment标志符的设置。

  特色:由Hibernate自己维护,适用于全部的数据库,不适合多进程并发更新数据库适合单一进程访问数据库。不能用于群集环境

  好比如今有4个数据库的集群,共享一张学生表,我从中查找某个编号(主键)为1000的学生,若是使用的hibernate的主键生成策略为increment,则没法保证编号1000的学生在集群里是惟一的。

   Identity (经常使用,与底层数据库有关,oracle没法使用)

  Identity根据底层数据库支持自动增加,无需手动设置主键,但不一样的数据库用不一样的主键增加方式,移植性很差。

  特色:与底层数据库有关,适用于MySQL、DB二、MS SQL Server,采用数据库生成的主键,用于为long、short、int类型生成惟一标识。使用SQL Server 和 MySQL 的自增字段,这个方法不能放到 Oracle 中,Oracle 不支持自增字段,要设定sequence(MySQL 和 SQL Server 中很经常使用),Identity无需Hibernate和用户的干涉,使用较为方便,但不便于在不一样的数据库之间移植程序。

 

  Sequence (经常使用)

  Sequence须要底层数据库支持Sequence方式,例如Oracle数据库等。

  特色:须要底层数据库的支持序列,支持序列的数据库有DB二、PostgreSql、Qracle、SAPDb等在不一样数据库之间移植程序,特别从支持序列的数据库移植到不支持序列的数据库须要修改配置文件。好比:Oracle:create sequence seq_name increment by 1 start with 1; 须要主键值时能够调用seq_name.nextval或者seq_name.curval获得,数据库会帮助咱们维护这个sequence序列,保证每次取到的值惟一,如:insert into tbl_name(id, name) values(seq_name.nextval, ‘Jimliu’);

  配置:

<id name="id" column="id" type="long">
<generator class="sequence">
<param name="sequence">seq_name</param>
</generator>
</id> 
View Code

  

  Native(经常使用,推荐使用,移植性好,也适合多数据库)

  Native主键生成方式会根据不一样的底层数据库自动选择Identity、Sequence、Hilo主键生成方式。

  特色:根据不一样的底层数据库采用不一样的主键生成方式。因为Hibernate会根据底层数据库采用不一样的映射方式,所以便于程序移植,项目中若是用到多个数据库时,可使用这种方式。

  配置:

<id name="id" column="id">
<generator class="native" /></id>
View Code

  

  UUID(经常使用在网络环境,可是耗费存储空间)

  UUID使用128位UUID算法生成主键,可以保证网络环境下的主键惟一性,也就可以保证在不一样数据库及不一样服务器下主键的惟一性。

  特色;可以保证数据库中的主键惟一性,生成的主键占用比较多的存贮空间。 

  配置:

<id name="id" column="id">
<generator class="uuid.hex" /></id>
View Code

 

  Hilo(不经常使用)

  使用高低位算法生成主键,高低位算法使用一个高位值和一个低位值,而后把算法获得的两个值拼接起来做为数据库中的惟一主键。Hilo方式须要额外的数据库表和字段提供高位值来源。默认请况下使用的表是hibernate_unique_key,默认字段叫做next_hi。next_hi必须有一条记录不然会出现错误。

  特色:和底层数据库无关,可是须要额外的数据库表的支持,能保证同一个数据库中主键的惟一性,但不能保证多个数据库之间主键的惟一性。Hilo主键生成方式由Hibernate 维护,因此Hilo方式与底层数据库无关,但不该该手动修改hilo算法使用的表的值,不然会引发主键重复的异常。 

 

  配置的补充:hbm2ddl.auto属性用法

  在项目的配置文件里设置:<property name="hbm2ddl.auto">update</property>,属性含义的解释:

  • create:表示启动的时候先drop数据库,再create。
  • create-drop: 也表示建立,只不过再系统关闭前执行一下drop。
  • update: 这个操做启动的时候会去检查schema是否一致,若是不一致会作scheme更新。
  • validate: 启动时验证现有schema与你配置的hibernate是否一致,若是不一致就抛出异常,并不作更新。

 

  在Hibernate是用映射文件好仍是用注解的方式好?

  两种方式本质上没区别。使用元数据能够在写实体的同时配好映射,而 XML 则须要来回切换配置和实体,不太方便。从维护上来讲,注解更好。由于代码和“配置”在一块儿,不容易漏掉信息。从这一点上来讲,注解必须是第一选择。可是也有人说到这样一个问题,若是使用注解,有很大可能会违反开闭原则,使用配置文件则不会。闲言少叙,看代码:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory name="MySQL">
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/bbs</property>
        <property name="connection.username">root</property>
        <property name="connection.password">123</property>
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <property name="hbm2ddl.auto">update</property>
        <property name="current_session_context_class">thread</property>

        <mapping class="net.nw.vo.Students"/>
    </session-factory>
</hibernate-configuration>
View Code

写好配置文件,代码以下:

package net.nw.vo;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

//学生实体类
@Entity
public class Students {
    private int sid;

    private String sname;
    
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }
}
View Code

测试:

import net.nw.vo.Students;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;


public class HibernateTest {

    public static void main(String[] args) {
        AnnotationConfiguration annotationConfiguration = new AnnotationConfiguration().configure();
        SessionFactory sessionFactory = annotationConfiguration.buildSessionFactory();
        Session session = sessionFactory.getCurrentSession();
        Transaction transaction = session.beginTransaction();

        try {
            Students s = new Students();
            //s.setSid(1);
            s.setSname("xxxx");
            session.save(s);
            transaction.commit();
        } catch (Exception ex) {
            transaction.rollback();
            ex.printStackTrace();
        }
    }
}
View Code

执行成功!本地测试成功插入xxxxx(接以前的bbs数据库表students):

  小结:

  • 使用AnnotationConfiguration建立config对象代替以前的Configuration方式(hibernate3的方式)。
  • hibernate4中新增了ServiceRegistry接口。4.0之后改成使用ServiceRegistry 注册:
Configuration cfg = new Configuration().configure(); 
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(cfg.getProperties()).buildServiceRegistry(); 
SessionFactory factory = cfg.buildSessionFactory(serviceRegistry); 
Session s = factory.openSession();
View Code
  • @Entity——必选的注解,在class类体上面,注解将一个类声明为一个实体bean。属性 name 可选,对应数据库中的一个表。若表名与实体类名相同,则能够省略。
  • @Table(name="",catalog="",schema="") ——该注解可选,一般和@Entity 配合使用,只能标注在实体的 class 定义处,表示实体对应的数据库表的信息。属性:

    • name - 可选,表示表的名称,默认地,表名和实体名称一致,只有在不一致的状况下才需要指定表名

    • catalog - 可选,表示Catalog名称,默认为 Catalog("").

    • schema - 可选 , 表示 Schema 名称 , 默认为Schema("").

  • @Id——必选,通常加在getter方法上面, 定义了映射到数据库表的主键的属性,一个实体只能有一个属性被映射为主键,置于get方法前。
  • @GeneratedValue(strategy=GenerationType,generator="") ——可选,用于定义主键生成策略。属性:
    • Strategy - 表示主键生成策略,取值有:
      • GenerationType.AUTO - 根据底层数据库自动选择(默认),若数据库支持自动长类型,则为自动增加。
      • GenerationType.INDENTITY - 根据数据库的Identity字段生成,支持DB二、MySQL、MS、SQL Server、SyBase与HyperanoicSQL数据库的Identity类型主键。

      • GenerationType.SEQUENCE - 使用Sequence来决定主键的取值,适合Oracle、DB2等 ,支持Sequence的数据库,通常结合@SequenceGenerator使用。(Oracle没有自动增加类型,只能用Sequence)

      • GenerationType.TABLE - 使用指定表来决定主键取值,结合@TableGenerator使用。如:

        @Id
        
        @TableGenerator(name="tab_cat_gen",allocationSize=1)
        
        @GeneratedValue(Strategy=GenerationType.Table)
        View Code
    • Generator - 表示主键生成器的名称,这个属性一般和ORM框架相关 , 例如:Hibernate 能够指定 uuid 等主键生成方式

  • @Version——能够在实体bean中使用@Version注解,经过这种方式可添加对乐观锁定的支持。

  • @Basic ——用于声明属性的存取策略:

    • @Basic(fetch=FetchType.EAGER)   即时获取(默认的存取策略)

    • @Basic(fetch=FetchType.LAZY)    延迟获取

  • @Temporal ——用于定义映射到数据库的时间精度:@Temporal(TemporalType=TIME) 时间、@Temporal(TemporalType=DATE) 日期、@Temporal(TemporalType=TIMESTAMP) 二者兼具

 

  注解太多了,之后一一说明。

  • 使用注解,在Hibernate.cfg.xml中一样也须要注册实体类。固然配置文件不用配,只是仍是须要引入实体类的关系,<mapping class="net.nw.vo.Students"/>,配置文件方式的resource=改成了class=。

 

  使用session api实现crud

  结合以前的总结,看看crud时对象状态的转换关系。直接看代码:

  • create,session.save(obj);,注意这里面的玄机:
    • 若是当前对象是持久态,则执行save方法,会自动触发Update语句的执行,而不是insert语句。
    • 若是当前对象为瞬时态,则执行save方法,会马上执行insert语句,使得对象从瞬时态转换为持久态。
    @Test
    public void save() {
        Transaction transaction = session.beginTransaction();
        Student student = new Student();

        try {
            student.setSname("xiaoli");
            // 以前的student是瞬时态的
            session.save(student);
            // 以后变为持久态
            transaction.commit();
        } catch (Exception e) {
            LOG.error("save error", e);
            transaction.rollback();
        }
    }
View Code

debug一下,验证以前的理论:在sava处断点,step over以后,当即控制台打印了insert into students(sname) values(?)。后台数据库也插入了xiaoli。

  下面看若是当前对象是持久态的状况:

    @Test
    public void save() {
        Transaction transaction = session.beginTransaction();
//        Student student = new Student();

        try {
//            student.setSname("xiaoli");

            // 从数据库读取xiaoli,该对象是持久态的
            Student student = (Student) session.get(Student.class, 7);
            // 从新赋值,再保存
            student.setSname("小李");
            session.save(student);
            transaction.commit();
        } catch (Exception e) {
            LOG.error("save error", e);
            transaction.rollback();
        }
    }
View Code

仍是在save处debug,发现首先发送SQL语句:

    select
        student0_.sid as sid2_0_,
        student0_.sname as sname2_0_ 
    from
        students student0_ 
    where
        student0_.sid=?
View Code

执行save以后,发送的SQL是:

    update
        students 
    set
        sname=? 
    where
        sid=?
View Code

道理很明显,若是仍是插入语句,则会插入新的记录,显然不对了。

  • retrive,hibernate基本的查询api是get和load(只根据主键查询,不能根据其它字段查询,若是想根据非主键查询,可使用HQL),以前也看了get的用法了,使用get查询对象,执行以后,对象变为持久态的,马上发送SQL语句select……并且也能够直接填写实体类名,get查找返回的是真正的实体对象,若是找不到则返回null,代码以下:
Student student = (Student) session.get("zhujian.vo.Student", 1);
            LOG.info("sid = {}, sname = {}", student.getSid(), student.getSname());
View Code

debug一下:

执行48句以后:

再看load方法,参数写法和get同样的,可是load是一种懒加载的方式,只有使用的时候才生成sql语句:

    @Test
    public void retrive() {
        Transaction transaction = session.beginTransaction();

        try {
            Student student = (Student) session.load(Student.class, 2);
            student.setSname("小李");
            session.save(student);
            transaction.commit();
        } catch (Exception e) {
            LOG.error("save error", e);
            transaction.rollback();
        }
    }
View Code

load方法查询的对象是代理对象,且执行此方法时不会当即发出查询语句。由于使用load加载对象去查询某一条数据的时候并不会直接将这条数据以指定对象的形式来返回,而是在真正须要使用该对象里面的一些属性的时候才会去查找对象。他的好处就是能够减小程序自己由于与数据库频繁的交互形成的处理速度缓慢的问题。

  而hibernate的load方法延迟加载实现原理是使用代理(代理设计模式在hibernate的的应用),Hibernate的懒加载,是经过在内存中对类、集合等的加强(即在内存中扩展类的特性[继承])来实现的,这些类一般称为代理类。好比咱们经过session.load(class, id)操做,加载一个对象的时候,hibernate返回的其实是实体类的代理类实例。如图:

执行65句以后:

  但如经过session.get操做,则返回实际的对象实例(不是代理类实例),对上例而言,get操做返回实体类的实例。采用load()方法加载数据,若是数据库中没有相应的记录,则会抛出异常:org.hibernate.ObjectNotFoundException: No row with the given identifier exists:……因此若是我知道该id在数据库中必定有对应记录存在,那么我就可使用load方法来实现延迟加载。

  

  session缓存(hibernate一级缓存)概念

  在继续探讨这个问题以前,必须先提早总结下缓存的一些东西。

  Hibernate缓存包括两大类:Hibernate一级缓存和Hibernate二级缓存。

  一级缓存就是Session级别的缓存,一个Session作了一个查询操做,它会把这个操做的结果放在一级缓存中,若是短期内这个session(必定要同一个session)又作了同一个操做,那么hibernate直接从一级缓存中拿,而不会再去连数据库取数据。它是属于事务范围的缓存,Session对象的生命周期一般对应一个数据库事务或者一个应用事务,一级缓存中,持久化类的每一个实例都具备惟一的OID。这一级别的缓存由hibernate管理,无需主动干预便可工做。

  二级缓存就是 SessionFactory 级别的缓存,查询的时候会把查询结果缓存到二级缓存中若是同一个sessionFactory建立的某个session执行了相同的操做,hibernate就会从二级缓存中拿结果,而不会再去链接数据库,由于SessionFactory对象的生命周期和应用程序的整个过程对应,所以Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,所以须要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别,须要人工配置和更改,能够动态加载,卸载(二级缓存是一个可配置的插件,默认下SessionFactory不会启用这个插件。)。而一级缓存不能够卸载。

  当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查,查不到,若是配置了二级缓存,那么从二级缓存中查,若是都查不到,再查询数据库,把结果按照ID放入到缓存删除、更新、增长数据的时候,同时更新缓存。

            Student student = (Student) session.get(Student.class, 6);
            LOG.info("student sid = {}, student sname = {}", student.getSid(), student.getSname());

            Student student1 = (Student) session.get(Student.class, 6);
            LOG.info("student1 sid = {}, student1 sname = {}", student1.getSid(), student1.getSname());
View Code

debug查看效果:执行完第一个get语句,当即发送了SQL语句,执行第二个同样的get语句时,我发现并没打印SQL语句,而是直接打印了log,只能说明,它第二次同样的对象查询是在缓存中查找到的。

  网上有人说get不从缓存中查找,这是不对的,还有人说get不会去二级缓存中查找,其实也不对。或者严格的说必须指定哪一个版本的hibernate再来讨论这个问题,下面还会分析源码来再次佐证(hibernate 3)

 

  再看load的方式:

            Student student = (Student) session.load(Student.class, 6);
            LOG.info("sid = {}, sname = {}", student.getSid(), student.getSname());

            Student student1 = (Student) session.load(Student.class, 6);
            LOG.info("student1 sid = {}, student1 sname = {}", student.getSid(), student.getSname());
View Code

也是第二次查找同一个对象的时候,没有再生成SQL语句,直接打印的两个log,只能说明它是在缓存中查询了。load方法也是走缓存的。那么具体怎么走,下面看源码级别的分析。

 

  load和get的源码分析:

  先看get和load的api源码,位于SessionImpl类,它是Session接口的一个实现类:

上面是load方法的,下面是get方法:

两种方式相似,那么继续看重载的load和get方法源码:

public Object get(String entityName, Serializable id) throws HibernateException {
        LoadEvent event = new LoadEvent(id, entityName, false, this);
        boolean success = false;

        Object var5;
        try {
            this.fireLoad(event, LoadEventListener.GET);
            success = true;
            var5 = event.getResult();
        } finally {
            this.afterOperation(success);
        }

        return var5;
    }


    public Object load(String entityName, Serializable id) throws HibernateException {
        LoadEvent event = new LoadEvent(id, entityName, false, this);
        boolean success = false;

        Object var5;
        try {
            this.fireLoad(event, LoadEventListener.LOAD);
            if(event.getResult() == null) {
                this.getFactory().getEntityNotFoundDelegate().handleEntityNotFound(entityName, id);
            }

            success = true;
            var5 = event.getResult();
        } finally {
            this.afterOperation(success);
        }

        return var5;
    }
View Code

关键区别是this.fireLoad(event, LoadEventListener.LOAD);和this.fireLoad(event, LoadEventListener.GET);这两句代码,官方文档说,使用fireLoad方法把加载事件event和事件加载的监听器(一个接口)的方式组合,也就是这两个方法触发事件的方式不同。下面先看看这两个表明不一样触发方式的常量:

    LoadEventListener.LoadType GET = (new LoadEventListener.LoadType("GET")).setAllowNulls(true).setAllowProxyCreation(false).setCheckDeleted(true).setNakedEntityReturned(false);
    LoadEventListener.LoadType LOAD = (new LoadEventListener.LoadType("LOAD")).setAllowNulls(false).setAllowProxyCreation(true).setCheckDeleted(true).setNakedEntityReturned(false);
View Code

区别就是:在allowNulls上get容许空,也就是get找不到就返回null,而load不容许空,因此抛出异常!而在allowProxyCreation上get不容许代理建立,而load容许代理的建立,记住这点。继续看fireload方法源码:

    private void fireLoad(LoadEvent event, LoadType loadType) {
        this.errorIfClosed();
        this.checkTransactionSynchStatus();
        LoadEventListener[] loadEventListener = this.listeners.getLoadEventListeners();

        for(int i = 0; i < loadEventListener.length; ++i) {
            loadEventListener[i].onLoad(event, loadType);
        }

    }
View Code

使用的内部的LoadEventListener[] loadEventListener数组去调用loadEventListener[i].onLoad(event, loadType);方法,加载方式和事件做为参数传入,下面看onload方法:

    public void onLoad(LoadEvent event, LoadType loadType) throws HibernateException {
        EventSource source = event.getSession();
        EntityPersister persister;
        if(event.getInstanceToLoad() != null) {
            persister = source.getEntityPersister((String)null, event.getInstanceToLoad());
            event.setEntityClassName(event.getInstanceToLoad().getClass().getName());
        } else {
            persister = source.getFactory().getEntityPersister(event.getEntityClassName());
        }

        if(persister == null) {
            throw new HibernateException("Unable to locate persister: " + event.getEntityClassName());
        } else {
            if(!persister.getIdentifierType().isComponentType() || EntityMode.DOM4J != event.getSession().getEntityMode()) {
                Class keyToLoad = persister.getIdentifierType().getReturnedClass();
                if(keyToLoad != null && !keyToLoad.isInstance(event.getEntityId())) {
                    throw new TypeMismatchException("Provided id of the wrong type for class " + persister.getEntityName() + ". Expected: " + keyToLoad + ", got " + event.getEntityId().getClass());
                }
            }

            EntityKey keyToLoad1 = new EntityKey(event.getEntityId(), persister, source.getEntityMode());

            try {
                if(loadType.isNakedEntityReturned()) {
                    event.setResult(this.load(event, persister, keyToLoad1, loadType));
                } else if(event.getLockMode() == LockMode.NONE) {
                    event.setResult(this.proxyOrLoad(event, persister, keyToLoad1, loadType));
                } else {
                    event.setResult(this.lockAndLoad(event, persister, keyToLoad1, loadType, source));
                }

            } catch (HibernateException var7) {
                log.info("Error performing load command", var7);
                throw var7;
            }
        }
    }
View Code

找到关键的代码,由于个人测试例子里,两个方法都没有调用带 lockMode 的 api,它们默认 null,执行这一句:

get和load查询方法都走该句,那么继续看proxyOrLoad方法:

    protected Object proxyOrLoad(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
        if(log.isTraceEnabled()) {
            log.trace("loading entity: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
        }

        if(!persister.hasProxy()) {
            return this.load(event, persister, keyToLoad, options);
        } else {
            PersistenceContext persistenceContext = event.getSession().getPersistenceContext();
            Object proxy = persistenceContext.getProxy(keyToLoad);
            return proxy != null?this.returnNarrowedProxy(event, persister, keyToLoad, options, persistenceContext, proxy):(options.isAllowProxyCreation()?this.createProxyIfNecessary(event, persister, keyToLoad, options, persistenceContext):this.load(event, persister, keyToLoad, options));
        }
    }
View Code

关键语句 if(!persister.hasProxy()),大概机制就是当该方式没有发现代理存在,也就是get的加载方式,就return this.load(event, persister, keyToLoad, options) 方法的值,那么看load方法的源码:

    protected Object load(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
        if(event.getInstanceToLoad() != null) {
            if(event.getSession().getPersistenceContext().getEntry(event.getInstanceToLoad()) != null) {
                throw new PersistentObjectException("attempted to load into an instance that was already associated with the session: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
            }

            persister.setIdentifier(event.getInstanceToLoad(), event.getEntityId(), event.getSession().getEntityMode());
        }

        Object entity = this.doLoad(event, persister, keyToLoad, options);
        boolean isOptionalInstance = event.getInstanceToLoad() != null;
        if((!options.isAllowNulls() || isOptionalInstance) && entity == null) {
            event.getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound(event.getEntityClassName(), event.getEntityId());
        }

        if(isOptionalInstance && entity != event.getInstanceToLoad()) {
            throw new NonUniqueObjectException(event.getEntityId(), event.getEntityClassName());
        } else {
            return entity;
        }
    }
View Code

关键一句:Object entity = this.doLoad(event, persister, keyToLoad, options);,到这里就一目了然了:

 1 protected Object doLoad(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
 2         if(log.isTraceEnabled()) {
 3             log.trace("attempting to resolve: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
 4         }
 5 
 6         Object entity = this.loadFromSessionCache(event, keyToLoad, options);// 先从session缓存(一级缓存)中加载对象
 7 
 8         if(entity == REMOVED_ENTITY_MARKER) {
 9             log.debug("load request found matching entity in context, but it is scheduled for removal; returning null");
10             return null;
11         } else if(entity == INCONSISTENT_RTN_CLASS_MARKER) {
12             log.debug("load request found matching entity in context, but the matched entity was of an inconsistent return type; returning null");
13             return null;
14         } else if(entity != null) {// 在一级缓存里查找到对象就返回该对象
15             if(log.isTraceEnabled()) {
16                 log.trace("resolved object in session cache: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
17             }
18 
19             return entity;
20         } else { // 一级缓存找不到,get方法去二级缓存找
21             entity = this.loadFromSecondLevelCache(event, persister, options);
22             if(entity != null) {// 若是存在且找到就返回该对象
23                 if(log.isTraceEnabled()) {
24                     log.trace("resolved object in second-level cache: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
25                 }
26 
27                 return entity;
28             } else { // 最后缓存找不到,才去数据库查询
29                 if(log.isTraceEnabled()) {
30                     log.trace("object not resolved in any cache: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
31                 }
32 
33                 return this.loadFromDatasource(event, persister, keyToLoad, options);
34             }
35         }
36     }
View Code

  显然,get的查询方式,首先查询session缓存,也就是一级缓存,没有就去查询二级缓存,若是没有二级缓存或者没找到,最后才去查数据库

  下面看看load方法,其实大同小异,和get的源码差很少,只不过走了其余一些分支,仍是看方法proxyOrLoad里,若是找到了代理(也就是使用的load查找方式),进入以下分支:

PersistenceContext persistenceContext = event.getSession().getPersistenceContext();// load方式先从事件的上下文环境中查找(一级缓存session)
// 返回的对象赋值给proxy,名字都那么明显了,代理对象
Object proxy = persistenceContext.getProxy(keyToLoad);
// 若是是空的,就建立代理对象,不是空的,先判断是否容许建立代理(还记得以前的LOAD和GET常量么,就在这里起做用),load确定容许,那么开始建立代理
return proxy != null?this.returnNarrowedProxy(event, persister, keyToLoad, options, persistenceContext, proxy):(options.isAllowProxyCreation()?this.createProxyIfNecessary(event, persister, keyToLoad, options, persistenceContext):this.load(event, persister, keyToLoad, options));
View Code

  到这里确定明确一点:load查找方式首先会去缓存里查找,那么load的代理对象在获取对象属性时究竟如何加载数据?

  若是缓存中找不到对象,则进入该方法:

// 该内部的私有方法中第一个log就说了:实体代理类先从session中查找
private Object returnNarrowedProxy(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options, PersistenceContext persistenceContext, Object proxy) {
        log.trace("entity proxy found in session cache");
        // 执行的是懒加载
        LazyInitializer li = ((HibernateProxy)proxy).getHibernateLazyInitializer();
        if(li.isUnwrap()) {// 没有解包,就直接返回该代理对象
            return li.getImplementation();
        } else {
            Object impl = null;
            // load查找容许建立代理,那么这个if确定不进入执行
            if(!options.isAllowProxyCreation()) {
                impl = this.load(event, persister, keyToLoad, options);
                // 没有找到就返回异常信息
                if(impl == null) {
                    event.getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound(persister.getEntityName(), keyToLoad.getIdentifier());
                }
            }
   
            // 重写使用代理包装一下再返回
            return persistenceContext.narrowProxy(proxy, persister, keyToLoad, impl);
        }
    }
View Code

也就是说,此时的load方式直接返回相应的对象,不会发送SQL语句查询数据库,也无论给定的id是否在数据库中存在数据对象。也就是此时此刻load不会直接访问数据库,只是简单地返回一个由底层封装的一个代理对象,永远不为null,null会抛出异常。固然了,固然了最后load也会查数据,只不过是在实际使用数据时才去查询二级缓存,二级缓存找不到,最后去数据库查找,不然抛出异常。

  以上源码,来自hibernate 3。若是其余版本状况大同小异,大不了变化了就分析源码,看官方文档……不再随便相信网上的一些不负责任转载的文章了。

 

  Get()和Load()的区别小结

  1. get()方法默认不支持lazy(延迟加载)功能,而load支持延迟加载get方法,get方法首先查询session缓存,也就是一级缓存,没有找到查二级缓存,若是没二级缓存或者找不到,则查询数据库,而load方法建立时首先查询session缓存,没有就建立代理对象并返回,实际使用数据时才查询二级缓存,没有就查询数据库。
  2. 若是找不到符合条件的记录,get方法返回null,而load方法抛出异常(ObjectNotFoundException),load拥有不为null。
  3. 使用load方法,通常都假定你要取得对象确定是存在的,才能使用,不然报错,而get方法则尝试查找,若是不存在,就返回null。
  4. get()和load()只根据主键查询,不能根据其它字段查询,若是想根据非主键查询,可使用HQL或者原生SQL。
  5. 使用load()时若是在session关闭以后再查询此对象,会报异常:could not initialize proxy - no Session。处理办法:在session关闭以前初始化一下查询出来的对象:Hibernate.initialize(对象);
  6. 虽然好多书中都这么说:“get()永远只返回实体类”,但实际上这是不正确的,get方法若是在session缓存中找到了该id对应的对象,若是恰好该对象前面是被代理过的,如被load方法使用过,或者被其余关联对象延迟加载过,那么返回的仍是原先的代理对象,而不是实体类对象,若是该代理对象尚未加载实体数据(就是id之外的其余属性数据),那么它会查询二级缓存或者数据库来加载数据,可是返回的仍是代理对象,只不过已经加载了实体数据。
  7. 前面已经从源码角度讲了,get方法首先查询session缓存,没有的话查询二级缓存,最后查询数据库;load方法建立时首先查询session缓存,没有就建立代理,实际使用数据时才查询二级缓存和数据库。

 

这里比较细致了,继续看删除和修改:

  • update,总结两点:
    • 瞬时态不能执行update()。
    • 持久态和游离态能够执行update()。
    • 注意:代理对象在session关闭以后,不能执行update()。

第一个瞬时态对象,也就是数据库没有该对象保存,那么确定更新不了啦!很好理解。看代码:

        try {
            Student student = new Student();
            // 建立瞬时态对象student
            student.setSid(9);
            student.setSname("隔壁老王");
            // 更新瞬时态对象
            session.update(student);
            transaction.commit();
        } catch (Exception e) {
            LOG.error("save error", e);
            transaction.rollback();
        }
View Code

报错:org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]

第二个持久态确定能执行更新,毋庸置疑,先从数据库中把对象读取,变为持久态,在更新就ok。而游离态是存在于数据库,可是脱离session管理的对象,也是能够更新的。道理都同样。好比:

    @Test
    public void updateObj() {
        Transaction transaction = session.beginTransaction();

        try {
            Student student = (Student) session.get(Student.class, 1);
            transaction.commit(); // 读取一个持久对象,什么都不作,提交事务,session自动关闭(使用的线程封闭的session)
            // 此时student变为游离态
            // 从新开启session和事务
            session = sessionFactory.getCurrentSession();
            Transaction transaction1 = session.beginTransaction();
            session.update(student); // ok
            transaction1.commit();// ok
        } catch (Exception e) {
            LOG.error("save error", e);
            transaction.rollback();
        }
    }
View Code

发送了SQL:

update
students
set
sname=?
where
sid=?

Process finished with exit code 0

  • SaveOrUpdate():当对象从瞬时态转换为持久态时,若是不肯定主键是否冲突,推荐使用SaveOrUpdate()。很简单,顾名思义,设置的主键在数据库存在,自动执行的是更新方法update,若是主键不存在,那么执行的是save方法保存这个对象,把对象从瞬时态变为持久态。站在用户角度,用户事先不知道该主键在数据库存在否,使用这个保险。固然了,主键生成策略不能是native,由于它会自增,根本不会冲突。使用assigned就能够。
  • delete:持久态或者游离态对象均可以执行delete()方法。不过瞬时态对象也能够执行delete(),但Hibernate并不推荐使用delete()删除瞬时状态对象,由于这样作没有任何意义。 
  • merge方法:若是数据库中有该记录,则更新该记录,若是不存在该记录,则进行insert。乍一看很像saveOrUpdate方法,可是有区别,由于执行完merge(obj)后,它返回一个持久化对象的引用,而实参obj自己仍是游离的。

  merge和saveOrUpdate方法区别

  使用saveOrUpdate,若是数据库中有记录,会无条件执行update,若是数据库中无记录,则执行insert操做。merge方法是把咱们提供的对象变为游离状态的对象,只不过merger从新返回一个新的持久化对象的引用。而saveOrUpdate则把咱们提供的对象转变为一个持久化对象。

  也就是说saveOrUpdate后的对象会归入session的管理,对象的状态会跟数据库同步,再次查询该对象会直接从session缓存中取,merge后的对象不会归入session的管理,再次查询该对象仍是会从数据库中取。

Students s1 = new Students();

s1.setSid(4);// 假设id=4的对象存在数据库

s1.setSname(“masi”);//s1是托管态的

Students s2 =(Students)session.merge(s1);
View Code

可是merge(s1)以后,返回的s2(引用)这个对象归入了session管理,和数据库同步,变为持久化态,而s1仍是托管态的,没有session管理它。

相关文章
相关标签/搜索