首页 > 代码库 > Hibernate复习之Hibernate基本介绍

Hibernate复习之Hibernate基本介绍

众所周知,目前流行的面向对象的对象关系映射的Java持久层框架有MyBatis和Hibernate,他们都是对象关系映射ORM, 解决的主要问题就是对象-关系的映射,域模型和关系模型都分别建立在概念模型的基础上,域模型是面向对象的,关系模型是面向关系的,一般情况下,一个持久化类和一个表对应,类的每个实例对应表中的一条记录。 (可能存在类中多个属性对应一列的情况,映射组成关系), ORM中间件采用元数据来描述对象-关系映射细节,元数据通常采用XML格式,并且存放在专门的对象-关系映射文件中,如果希望把ORM软件集成到自己的Java应用中,用户首先要配置对象-关系映射文件。Hibernate中也就是entity.hbm.xml,而MyBatis中就是entityMapper.xml
下面主要介绍一下Hibernate的执行过程:

session.save(customer)

执行过程:
1)运用反射机制,获得customer对象的类型Customer.class
2)参考对象-关系映射元数据,了解Customer类对应的表,以及属性对应的列,Customer类和其他类关系。
3)根据以上映射信息,生成SQL语句

insert into tab_customer values(id,name,password,telphone);

4)调用JDBC API,执行以上的SQL语句。


JDBC与Hibernate比较:
1,JDBC: Java中嵌入SQL,不便于维护
Hibernate: 无须编写SQL语句;由Hibernate通过读取映射文件在运行时自动生成SQL

save(student)-> insert into customer values(?,?,?);

2,JDBC:查询数据,需要手动封装成对象;保存对象,需要手动插入值
Hibernate: 可自动实现对象和表中记录的转换
3,JDBC:查询内存中已存在的对象,修改属性值,均需要和数据库进行交互
Hibernate:通过Dirty checking(脏检查)避免交互;


Hibernate的工作原理:
1,Hibernate框架根据hibernate.cfg.xml的配置的信息来和数据库进行通信,当然和Spring结合后也可以通过注解来实现。
2,Hibernate框架根据具体的映射文件**.hbm.xml 来保存,更新,删除,查询对象。


Hibernate API,Hibernate中几个比较重要的类:
Configuration接口:
Configuration对象用于配置并且启动Hibernate,Hibernate应用通过Configuration实例还指定对象-关系映射文件的位置或者动态配置Hibernate的属性,然后创建SessionFactory实例

Configuration config = new Configuration();
Config.configure();
SessionFactory factory = config.buildSessionFactory();

SessionFactory :
用来构造Session的实例对象,它的特点:
1)线程安全: 该实例对象何以被多个线程共享
2)重量级:该实例对象的构造和销毁消耗系统资源,所以一般在应用程序启动的时候就构造实例对象,一般一个数据库对应一个SessionFactory的实例对象,如果要访问多个数据库,就需要创建多个该实例对象。
3)SessionFactory实例对象中维护了一个很大的缓存,存放了一些预定义的SQL语句和XML配置文件的信息,另外还维持了一个Hibernate的第二级缓存(缓存了所有Session对象所加载的POJO对象),用来保存在该生命周期中的一些业务对象,但是这个第二级缓存默认是关闭的,必须在xml中手动配置才可以开放

Session session = factory.openSession();

Session(别名:持久化管理器),用来对对象的保存,更新,删除和查询
特点:
1)线程不安全,要避免同一个Session对象被多个线程共享,一般一个线程对应一个Session
2)轻量级:可以随意的构造和销毁该实例对象。
3)Session对象中维护了一个缓存,称为Hibernate的第二级缓存(缓存了当前Session对象所加载的那些POJO对象),每个Session对象都有自己的缓存。
Query:
利用HQL语句(Hibernate Query Language)用来查询单个或者多个对象,面向对象的

Query query = session.createQuery("from Student where studentId=1");
Student s = (Student)query.uniqueResult();

Criteria:
功能同上,以面向对象的形式和数据库进行复杂的CRUD操作,还适用于动态查询。
Transaction:
用来处理事务,事务的启动,提交,回滚


OID:主键
在Java语言中,按照内存地址来识别或区分同一个类的不同对象,关系型数据库按主键值来识别或区分同一个表的不同记录,Hibernate使用OID来统一两者之间的矛盾,OID是关系数据库中的主键(通常为代理主键)在Java对象模型中的等价物。在运行时,Hibernate根据OID来维持Java对象和数据库表中记录的对应关系。为了保证持久化对象的OID的唯一性和不可变性,通常由Hibernate或底层数据库来为OID赋值,可以将OID的setId()方法设置为private类型,以禁止Java应用程序随便修改OID。
:子元素用来设定标识符生成器
Hibernate提供了标识符生成器口:net.sf.hibernate.id.IdentifierGenerator,并且提供了很多内置实现。

net.sf.hibernate.id.IdentityGenerator –缩写名– identity
net.sf.hibernate.id.IncrementGenerator –缩写名– increment

主键生成方式:
1,increment:
其生成方式与底层数据库无关,大部分数据库都支持,该方式的实现机制是在当前应用实例中维持一个变量,以保存着当前的最大值,之后每次需要生成主键的时候将此值加1作为主键。其不足之处是当多个线程并发对数据库表进行写操作时,可能出现相同的主键值,发生主键重复的冲突,所以在多线程并发操作的时候不应该使用该方法。

<id name="id" column="ID">
    <!-- 主键生成机制 -->
    <generator class="increment"/>
</id>

2,identity:
与底层数据库有关,要求数据库支持Identity,如MySQL中auto_increment,SQL Server中的identity。支持的数据库有MySQL,SQL Server,DB2,Sybase,但是不支持oracle,它同时支持并发操作。

<id name="id" column="ID">
    <!-- 主键生成机制 -->
    <generator class="identity" />
</id>

3,assigned:
主键由外部程序负责生成,无需Hibernate参与。如果要由程序代码来指定主键,就采有这种方式。

<id name="id" column="ID">
    <!-- 主键生成机制 -->
    <generator class="assigned" />
</id>

4,sequence
使用序列生成主键,需要底层数据库支持在数据库中建一个序列

create sequence seq_name increment by 1 start with 1;

然后在映射文件中指定使用序列的名字

<id name="id" column="ID">
    <!-- 主键生成机制 -->
    <generator class="sequence">
       <param name="sequence">seq_name</param>
    </generator>
</id>

5,hilo
通过hi/lo算法生成主键,需要一个表来保存主键的信息,在数据库中建一张表:

create table hi_value(next_hi number not null);
insert into hi_value(next_hi) values(1);
commit;

映射文件中需要指明表的这些信息

<id name="id" column="ID">
    <!-- 主键生成机制 -->
    <generator class="hilo">
       <param name="table">hi_value</param>
       <param name="column">next_hi</param>
       <param name="max_lo">100</param>
    </generator>
</id>

6,seqhilo
与hilo 类似,通过hi/lo 算法实现的主键生成机制,只是主键历史状态保存在Sequence中,适用于支持Sequence的数据库,
在数据库中建一个序列

create sequence seq_name increment by 1 start with 1; 
<id name="id" column="ID">
    <!-- 主键生成机制 -->
    <generator class="seqhilo">
        <param name="sequence">seq_name</param>
        <param name="max_lo">100</param>
    </generator>
</id>

7, native:
由Hibernate根据不同的数据库方言自行判断采用identity、hilo、sequence其中一种作为Hibernate主键生成方式,native的优点是与底层无关,便于不同数据库之间的移植,由Hibernate根据不同数据库选择主键的生成方式。在oracle中需要创建叫 Hibernate_sequence名字的sequence,如果设置了Hibernate.hbm2ddl.auto属性,不需要手动建立序列,前提是数据库帐号必须有Create Sequence这种高级权限。mysql等数据库则不用建立sequence。

<id name="id" column="ID">
    <!-- 主键生成机制 -->
    <generator class="native" />
</id>

8,foreign
外键生成方式,依赖其他表的主键,在主键一对一映射中需要使用到

<id name="id" column="ID">
    <!-- 主键生成机制 -->
    <generator class="foreign>
      <param name="property">id</param>
    </generator>
</id>

9,uuid.hex:
采用基于128位的算法生成唯一值,并编制成32位长度的唯一字符串作为主键值,uuid.hex的优点是支持大部分数据库,缺点就是要占用较大的存储空间。对于并发Insert要求较高的系统,推荐采用uuid.hex 作为主键生成机制。

<id name="id" column="ID">
    <!-- 主键生成机制 -->
    <generator class="uuid.hex" />
</id>

10,uuid.string:
使用UUID算法,UUID被编码为一个16个字符长的任意ASCII字符组成的字符串。不能在PostgreSQL数据库中使用。uuid.string同uuid.hex类似,需要占很大的存储空间。
11,select
使用触发器生成主键(主要用于早期的数据库主键生成机制,少用)


介绍完以上这些内容之后,下面就简单的体验一下搭建Hibernate的开发环境:
1.导入Hibernate的jar包,我这里新建的是Maven项目,因此首先需要添加关于Hibernate的依赖,由于我们还需要与数据库打交道,因此还需要添加数据库以及数据源的依赖jar包,以下是我的pom.xml文件的内容:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <hibernate.version>4.1.7.Final</hibernate.version>
    <druid.version>1.0.12</druid.version>
    <mysql.version>5.1.18</mysql.version>
</properties>

<dependencies>
   <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.12</version>
     <scope>test</scope>
   </dependency>
   <!-- hibernate4 -->
   <dependency>
       <groupId>org.hibernate</groupId>
       <artifactId>hibernate-core</artifactId>
       <version>${hibernate.version}</version>
   </dependency>
   <!--Druid连接池包 -->
   <dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>druid</artifactId>
       <version>${druid.version}</version>
   </dependency>
   <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>${mysql.version}</version>
   </dependency>
</dependencies>

2.在src/main/resources目录下新建一个hibernate.cfg.xml的文件,里面我们主要配置数据源:

<hibernate-configuration>
    <session-factory>
        <!-- 数据库连接设置 -->
        <!-- Database connection settings -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/hibernate</property>
        <property name="connection.username">root</property>
        <property name="connection.password">admin</property>
        <!-- SQL方言 -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <!-- 在控制台打印出执行的SQL语句 -->
        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>
        <!-- 是否格式化打印出来的SQL -->
        <property name="format_sql">true</property>
        <!-- 将映射文件转换为数据库定义语言 -->
        <!-- Drop and re-create the database schema on startup -->
        <!-- web项目启动时怎么操作表结构:更新、删除重建 -->
        <property name="hbm2ddl.auto">update</property>
        <!-- 映射文件 -->
        <mapping resource="student.hbm.xml" />
    </session-factory>
</hibernate-configuration>

3.我们这里新建一个Student的实体类,并且也在src/main/resources目录下创建一个映射文件:student.hbm.xml
Student.java

package com.ecjtu.hibernate.entity;

import java.io.Serializable;
import java.util.Date;

public class Student implements Serializable {

    private static final long serialVersionUID = 1L;

    private String major;
    private Long id;
    private String name;
    private String sex;
    private Integer age;
    private Date birthday;

    public Student() {
    }

    public Student(String major, Long id, String name, String sex, int age,
            Date birthday) {
        super();
        this.major = major;
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getMajor() {
        return major;
    }

    public void setMajor(String major) {
        this.major = major;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {

        return "Student[Major:" + getMajor() + " Id:" + getId() + " Name:"
                + getName() + " sex:" + getSex() + " Age:" + getAge()
                + " birthday:" + getBirthday() + "]";
    }
}

student.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.ecjtu.hibernate.entity">
    <class name="Student" table="TAB_STUDENT">
        <id name="id" column="id">
            <!-- 主键生成机制:自增长 -->
            <generator class="increment"/>
        </id>
        <!-- 属性字段 -->
        <property name="major"/>
        <property name="name"/>
        <property name="sex"/>
        <property name="age"/>
        <property name="birthday"/>
    </class>
</hibernate-mapping>

4.建好这些之后基本上也就大功告成了,接下来就可以测试了,在src/test/java下面新建一个测试类HibernateTest.java

package com.ecjtu.hibernate.Test;

import java.util.Date;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.Before;
import org.junit.Test;

import com.ecjtu.hibernate.entity.Student;

/**
 * @ClassName: HibernateTest
 * @Description: Hibernate测试
 * @author Zhouxh
 * @date 2017年2月19日
 */
public class HibernateTest {

    private SessionFactory sessionFactory;

    @SuppressWarnings({ "deprecation"})
    @Before
    public void init(){
        //1.创建配置对象
        Configuration configuration=new Configuration();
        //2.读取配置文件,参数可为空,Document,File,配置文件的路径或者URL
        //HibernateTest/src/main/resources/hibernate.cfg.xml
        configuration.configure();
        //3.根据配置文件,创建一级缓存(sessionFactory)
        sessionFactory=configuration.buildSessionFactory();
    }

    @Test
    public void test(){
        /**
         * 建立与数据库的连接
         * 映射文件,映射对象
         * session  代码与数据库之间的会话
         */
        //4.创建Session,打开会话
        Session session=sessionFactory.openSession();
        //5.开启事物
        Transaction transaction=session.beginTransaction();
        Student student=new Student("软件工程", 1000L, "李四", "男", 34, new Date(System.currentTimeMillis()));
        session.save(student);
        //6.事物的提交
        transaction.commit();
        //7.session的关闭
        session.close();
    }
}

5.执行完Junit单元测试成功了即可,这时控制台也会打出insert into语句,这就说明我们的配置没有问题了。
6.封装HibernateSessionFactory工具类,因为Session是线程不安全的,为了保证当前线程只有一个session对象,并且当前线程中的Session不能让其他线程来访问,需要将获取Session的方法进行封装,为了保证Session的线程安全性,需要将Session放到ThreadLocal中,ThreadLocal为线程本地变量
HibernateSessionFactory.java,主要是为了维护Session的创建和关闭。

package com.ecjtu.hibernate.util;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

/**
 * @ClassName: HibernateSessionFactory
 * @Description: 封装一个创建Session的工厂并且得到Session的方法
 * @author Zhouxh
 * @date 2017年2月20日
 */
@SuppressWarnings("deprecation")
public class HibernateSessionFactory {
    //hibernate.cfg.xml文件的位置
    //private static String configFile = "hibernate.cfg.xml";

    private static SessionFactory factory;

    // 用于存放Session,该对象是线程安全的,只有当前线程才能访问
    private static ThreadLocal<Session> threadLocal;

    static {
        Configuration config = new Configuration( );
        // 无参默认系统去找src下面的hibernate.cfg.xml"
        config.configure();
        // 重量级:该实例对象的构造和销毁消耗系统资源,所以一般在应用程序启动的时候就构造实例对象,
        // 一般一个数据库对应一个SessionFactory的实例对象,
        // 如果要访问多个数据库,就需要创建多个该实例对象。
        factory = config.buildSessionFactory();
        threadLocal = new ThreadLocal<Session>();
    }

    /**
     * @Title: getSession
     * @Description: 获取Session
     * @return
     * @throws
     */
    public static Session getSession() {
        // 该方法底层是一个map实现的Key为当前线程的对象,value为Session
        // 如果当前线程没有打开过Session,那么就返回空,意味着我们需要去创建一个Session,
        // 保证了一个线程里面对应一个Session
        Session session = threadLocal.get();
        // 第一个为空的话,第二个表达式不会执行,避免了空指针
        // session为空并且或者他没有被打开过就去创建
        if (session == null || !session.isOpen()) {
            session = factory.openSession();
            // 该方法底层通过Map来保存的,调用该方法会自动帮你创建一个map出来,
            // 并通过key-value保存Session到map中,获取session的时候通过key去取的话
            // 不同的key就会返回不同的session
            threadLocal.set(session);
        }
        return session;
    }

    /**
     * @Title: closeSession
     * @Description: 关闭Session
     * @throws
     */
    public static void closeSession() {
        Session session = threadLocal.get();
        threadLocal.set(null);
        if (session != null) {
            session.close();
        }
    }
}

到这里,Hibernate复习的第一部分也就结束了!主要就是简单介绍一下Hibernate的使用和基本的一些信息。最后附上一张Hibernate的内置映射类型,也就是Hibernate的类型—Java类型—标准SQL类型三者之间的对应关系:
技术分享

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Hibernate复习之Hibernate基本介绍