美文网首页
Hibernate(一) 初探

Hibernate(一) 初探

作者: Super超人 | 来源:发表于2018-11-04 15:40 被阅读12次

    最近重新学习了一遍Hibernate框架,感觉收益颇多,再次做个学习总结和笔记。本篇文章主要记录和总结 Hibernate 的基本配置项和 一级缓存及 Hibernate 中的对象状态。

    今天这篇总结将会用Hibernate最新的稳定版本,数据库也用新的稳定版本,由于设计到众多依赖项,所以就构建一个Maven工程,pom.xml依赖项配置如下

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.2.17.Final</version>
    </dependency>
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    

    一、基本配置及概念

    源自百度百科:Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的ORM(Object Relative DateBase-Mapping)框架。

    1. XML基本配置

    hibernate.cfg.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>
        <!-- 数据库连接部分 -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://10.22.70.2:3306/database_chao?useUnicode=true&amp;characterEncoding=utf8</property>
        <property name="connection.username">xxx</property>
        <property name="connection.password">xxx</property>
    
        <!-- 数据库方言 -->
        <property name="dialect">org.hibernate.dialect.MySQL55Dialect </property>
        
        <!-- 其他配置部分 -->
        <property name="hbm2ddl.auto">update</property>
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        
        <!-- 实体类映射 -->
        <mapping resource="com/ogemray/a_primary/entity/Contact.hbm.xml" />
    
    </session-factory>
    </hibernate-configuration>
    

    *.hbm.xml配置 (实体类映射文件)

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
    
    <hibernate-mapping package="com.ogemray.a_primary.entity">
        
        <class name="Contact" table="t_contact">
            <id name="id" column="id" type="integer">
                <generator class="native"></generator>
            </id>
            <property name="name" type="string"></property>
            <property name="birthday" type="date"></property>
        </class>
        
    </hibernate-mapping>
    

    其他字段就不过累述,下面主要讲下总配置文件中的数据库方言、建表策略和实体映射文件中的主键生成策略。

    2. Hibernate数据库方言

    方言就是总配置文件中的dialect字段,因为 Hibernate 是要把JavaBean对象映射成数据库能够识别的数据结构,而不同的数据库有自己不同的标准,因此,Hibernate 为了更好适配各种数据库,针对每种数据库都指定了一个方言。根据指定的方言,将不同数据类型、SQL语法转换成 Hibernate 能理解的统一的格式。如果没有对应的方言,Hibernate 是无法进行数据关系转换映射的。

    具体方言:https://blog.csdn.net/jialinqiang/article/details/8679171
    因为现我基本都用MySQL数据库,关于MySQL方言有三种。

    • org.hibernate.dialect.MySQLDialect
    • org.hibernate.dialect.MySQLInnoDBDialect
    • org.hibernate.dialect.MySQLMyISAMDialect

    MySQLMyISAMDialectMySQLInnoDBDialect 都是继承 MySQLDialectMySQLInnoDBDialect 创建的数据库引擎是 InnoDB,而 MySQLMyISAMDialect 创建的数据库引擎是 MyISAM,InnoDB 支持事务和外键,MyISAM 不支持事务和外键,但是处理速度要比 InnoDB 快,MyISAM 类型的二进制数据文件可以用做在不同操作系统中迁移。也就是可以直接从Windows系统拷贝到linux系统中使用。
    InnoDB的 autocommit 默认是打开的,即每条SQL语句会默认被封装成一个事务,自动提交,这样会影响速度,所以最好是把多条SQL语句显示放在 begincommit 之间,组成一个事务去提交。
    经过探索发现,在我目前尝试的这个版本,MySQL 5.1.47 默认创建的数据库引擎是 MyISAM,MySQL 5.5 之后默认创建引擎用的是 InnoDB ,MySQL 8 已经移除了 MyISAM 引擎。

    总结:一般关系型数据库用 InnoDB 。

    上面三个方言是针对 MySQL 5 之前的版本。主要变化还是在于建表SQL语句。MySQL 5 之后跟之前还是有不小的变化。比如 varchar 在 5.0.3 及之前版本最大长度限制为255,之后版本最大长度限制为65535。所以为了兼容 MySQL 5 之后,Hibernate 作者新出来了几种dialect。

    • org.hibernate.dialect.MySQL5Dialect
    • org.hibernate.dialect.MySQL55Dialect
    • org.hibernate.dialect.MySQL57InnoDBDialect
    • org.hibernate.dialect.MySQL5InnoDBDialect

    但是升级到 Hibernate 5 的时候,就会发现 MySQL5InnoDBDialect,被标注过时了。不仅仅是它过时了,所有带 InnoDB 的 Dialect 都被标注过时了,在标注有 InnoDBDialect 过时的同时 新加了 MySQL55Dialect 及 MySQL57Dialect。
    如果查看源码就会发现 MySQL55Dialect 与 MySQL5InnoDBDialect 源码一模一样。
    毕竟 MySQL 后面把 MyISAM 引擎给淘汰了,所以 Hibernate 作者认为没有必要再分为两类。

    2. 建表策略

    Hibernate 会根据 hbm2ddl.auto 设置生成对应的建表策略。
    create:每次都会先drop先前的表然后重新创建
    create-drop: 也表示创建,但是SessionFactory关闭的时候就会自动删除
    update: 没有则创建,有则不重新创建,有改变会更新表结构
    validate: 每次启动会验证配置的表结构是否和数据库里面的表结构一致,不一致会抛出异常,并不做更新

    3. 主键生成策略

    映射配置文件里面 <id> 元素内部 提供 <generator> 元素, 有 class 属性,指定数据表主键生成策略。Hibernate里面对于主键生成策略有11种之多,下面来说说这其中最常用的几种策略。

    • assigned:主键由外部程序负责生成,Hibernate和底层数据库都不负责维护,可以跨数据库。

    • increment:由Hibernate从数据库中取出最大值(每个session只取一次),以该值为基础每次增量为1,在内存中生成主键而不用依赖于数据库,因此可以实现跨数据库。但是这种方式不适合多进程并发插入数据到数据库,适合单一进程访问数据库,不能用于集群环境

    • hilo:高低位方式,Hibernate中最常用的一种生成方式,需要一张额外的表保存hi的值,保存hi值的表至少有一条记录(只与第一条记录有关),否则会出现错误,可以跨数据库(Hibernate内部有一套自己的算法生成标志值,不依赖数据库底层)。

    <id name="id" column="id">
        <generator class="hilo">
            <!-- 指定保存 hi 位值的表名 -->
            <param name="table">hibernate_hilo</param>
            <!-- 指定保存 hi 位置列的名 -->
            <param name="column">next_hi</param>
            <!-- 指定地位最大值 -->
            <param name="max_lo">100</param>
        </generator>
    </id>
    

    现在 Hibernate已经将这种主键生成方式给移除了


    Hibernate主键生成策略源码.png
    • seqhilo:与hilo类似,通过hi/lo算法实现主键生成机制,只是将hilo中的数据表转换成了序列sequence,需要数据库中先创建sequence,使用于支持sequence的数据库,如Oracle。

    • sequence:采用数据库提供的sequence机制来生成主键,需要数据库支持sequence,如Oracle、DB...支持,MySQL不支持。

    • identity:由底层数据库生成自增长标识符,其他数据库都基本支持,但是Oracle不支持。

    • native:由Hibernate根据使用数据库自行判断主键生成策略,灵活性很强,如果支持identity则使用identity,如果支持sequence则使用sequence。基本上是我们最常用的一中主键生成方式配置。

    • uuid:Hibernate在保存对象时,生成UUID字符串作为主键,保证了唯一性,可以实现跨数据库,以后切换数据库极其方便。缺点就是占用空间要大点,不过现在新标准的 UUID2 主键生成策略已经出来了,高版本建议用 UUID2 来代替 UUID。

    • guid:Hibernate在维护主键时,先查询数据库,获得一个uuid字符串,用该字符串作为主键,缺点就是支持生成uuid的数据库有限,需要数据库的支持。

    • foreign:使用另外的一个相关联的对象的主键作为主键,主要用于一对一关系中。

    • select:使用触发器生成主键,主要用于早期的数据库主键生成机制,能用到的地方非常少。

    二、SessionFactory创建

    SessionFactory sessionFactory = null;
    
    // 创建一个 SessionFactory
    final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
            .configure() // 默认加载文件名为 hibernate.cfg.xml 配置文件
            .build();
    try {
        sessionFactory = new MetadataSources( registry ).buildMetadata().buildSessionFactory();
    }
    catch (Exception e) {
        //创建 SessionFactory 错误时销毁注册表
        StandardServiceRegistryBuilder.destroy( registry );
    }
    

    三、Hibernate一级缓存及对象状态

    在说到 Hibernate 的一级缓存,就不得不说Hibernate中对象的三种状态。
    Hibernate中对象有三种状态:临时状态(Transient)持久状态(Persistent)游离状态(Detached)

    理解Hibernate对象状态对于学习至关重要,话不多说,先从一张图开始。

    Hibernate中对象状态.jpg
    • 临时状态:对象刚被 new 出来并且未被持久化,还没有被加入到 Session 缓存中的 Java 对象。

    • 持久化状态:已经被持久化,并且已经被加入 Session 缓存中的 Java 对象。

    • 游离状态:已经被持久化,但是不处于 Session 的缓存中。

    注意下几个操作 Hibernate 中对象状态的方法,对上面的图做个简单解释。
    get(..)load(..)find(..)save(..)saveOrUpdate(..) 等方法直接将对象加入 session 缓存中,通过上面图和下面例子能理解。
    delete(..) 删除对象,将对象由持久化状态变成临时状态。
    close(..) 关闭 session 对象,对象从 session 缓存中清除,但是数据库有有对应的记录,此时对象由持久化状态变成游离状态。
    clear(..) 清空 session 缓存,对象也是从 session 缓存中清除,由持久化状态变成游离状态。
    evict(..) 将指定对象从 session 缓存中清除,效果跟上面 clear(..) 一样,只不过这个更片面化,被清除的对象由持久化状态直接变成游离状态。

    下面通过一段代码理解三种状态之间的关系

    @Test
    public void testSimpleObjectState() {
    
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        
        Contact contact = new Contact();
        contact.setName("LiuChao");
        contact.setBirthday(new Date());
        /**
         * ①.[临时状态]
         * 此时 contact 对象只是刚 new 出来的临时状态
         */
        
        session.save(contact);
        /**
         * ②. [持久化状态]
         * 对象被保存到 session 缓存中, 并发送 insert 语句 对象被持久化到数据库
         */
        
        session.getTransaction().commit();
        session.close();
        /**
         * ③. [游离状态]
         * session 关闭, 缓存自然被清空, 但是 contact 对象已经被保存到数据库
         */
    }
    

    下面我们再通过一段代码来理解一下 Hibernate 的一级缓存(session缓存) 和 对象状态之间的关系:

    @Test
    public void testRelationshipBetweenSessionCacheAndObjectState() {
        
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        
        Contact contact = session.get(Contact.class, 1);
        System.out.println(contact.toString());
        /**
         * ①. [持久化状态]
         * 直接从数据库里面查询出来的, 会发送一条 select 语句, 并被保存到 session 缓存中
         */
        
        Contact contact2 = session.get(Contact.class, 1);
        System.out.println(contact2.toString());
        /**
         * 这里不会再发送 select 语句, 而是直接 session 缓存里面拿
         */
        
        contact2.setName("Merry");
        
        contact.setName("Tony");
        /**
         * 这里的 contact 和 contact2 是同一个对象, 都是 session 所管理的对象
         * 可以打印对象的 hashCode进行查看
         */
        
        /**
         * 在 commit 的时候, 会拿 session 所缓存的对象跟刚查询出来的时候作对比, 不一样则发一条 update 语句更新数据库
         * 所以上面不管经过几次更改, 只有在 commit 的时候才会进行对比和更新
         */
        session.getTransaction().commit();
        session.close();
    }
    

    这里是我的猜测总结:当查询或者插入对象的时候,将对象缓存到 session 中(这时会缓存两份,一份用于查询返回,一份用作后期样本对比),在 session 未被关闭和清空前,不管查询多少次,都是返回 session 缓存的那个对象,是同一个对象,用 C 语言来说返回的指针指向堆上的同一个地址。在 commit 的时候,Hibernate 会拿 session 缓存中用于查询返回的对象和缓存的另一份样本对象作对比,不一样则更新数据库。
    这里可以通过一个小实验得到证明:在 commit 之前直接修改返回的对象,然后再通过 session.get 方法拿取到的是修改后的,但是数据库里面是以前的,commit 之后数据库才会改变。
    以上只是我的个人猜测,实际 Hibernate 里面怎么实现还是得看源码,现在能力有限看不了,等以后看了之后会对这里进行更正。

    最后要提到一个方法 flush(),session 通过调用 flush() 方法能提前更新缓存中改变的对象数据到数据库,而不用等到 commit。

    相关文章

      网友评论

          本文标题:Hibernate(一) 初探

          本文链接:https://www.haomeiwen.com/subject/doetxqtx.html