[Java]Hibernate学习笔记及实用速查手册


  • Hibernate是一款开源持久层ORM框架。
  • ORM:Object-Relational Mapping(对象关系映射),描述Java对象和关系型数据库之间的映射关系,能够自动将Java程序中的对象持久化到关系型数据库中。开发人员在配置好映射关系之后,只需要关注Java对象即可,无需编写SQL。
  • 官方文档

0. pom.xml

<!-- ORM -->
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-core</artifactId>
  <version>5.4.17.Final</version>
</dependency>
<!-- mysql依赖 -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.20</version>
</dependency>

1. 数据库表、Java对象与Hibernate配置文件

数据库的表结构和预准备数据:

CREATE TABLE student(
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT(11) NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);

INSERT INTO student (id, name, age, email) VALUES
(1, 'Tom', 28, 'Tom@outlook.com'),
(2, 'Jack', 20, 'Jack@outlook.com');

Java中的对象:

@Data // 使用了 lombok 自动完成set()/get()方法
public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String email;
}

1.1 持久化类的编写规范

其中,需要完成ORM的类有如下要求:

  1. 所有属性采用 private 修饰、 get()/set() 方法采用 public 修饰。
  2. 具有一个无参 public 构造方法。
  3. 具有一个映射数据库主键字段的OID。
  4. 不可用 final 修饰持久化类。

1.2 Higernate配置文件编写示例

编写 hibernate.cfg.xml 文件:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!-- 数据库仓库配置 -->
        <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai</property>
        <property name="connection.username">root</property>
        <property name="connection.password">$password</property>

        <!-- JDBC 连接池大小:不推荐使用,生产环境中请使用c3p0 -->
        <property name="connection.pool_size">10</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>

        <!-- 在控制台显示执行的sql语句 -->
        <property name="show_sql">true</property>

        <!-- 格式化SQL语言 -->
        <property name="format_sql">true</property>

      
        <!-- 注意 <mapping>标签要写在所有<property/>之后 -->
        <!-- 映射文件配置 -->
        <!-- 如:<mapping resource="pers/model/domain/Student.hbm.xml"/> -->
        <!-- 注解映射 -->
        <!-- 如:<mapping class="pers.model.domain.Student"/> -->

    </session-factory>

</hibernate-configuration>
  • 注意 <mapping> 标签要写在所有 <property> 之后。

1.3 配置文件:hibernate-configuration

<session-factory><property name=?> 常用值如下:(其它配置项)

名 称描 述
dialect操作数据库语言版本,如 org.hibernate.dialect.MySQL5Dialect ;正常情况下会自动指定。
show_sql在控制台输出 SQL 语句,默认为 false
format_sql格式化控制台输出的 SQL 语句,当 show_sql 为真时配置起效
hbm2ddl.auto当 SessionFactory 创建时是否根据映射文件自动验证表结构或 自动创建、自动更新数据库表结构。该参数的取值为 validateupdatecreatecreate-drop
connection.driver_class连接数据库驱动程序
connection.url连接数据库 URL
connection.username数据库用户名
connection.password数据库密码
connection.autocommit事务是否自动提交
connection.pool_size连接池大小【不推荐使用】
connection.isolation事务的隔离等级,MySQL默认是REPEATABLE_READ。
connection.provider_class向Hibernate提供JDBC的类
c3p0.timeout获得连接的超时时间,如果超过这个时间,会抛出异常,单位毫秒
c3p0.max_statements最大的PreparedStatement的数量
c3p0.idle_test_period每隔120秒(默认)检查连接池里的空闲连接 ,单位是秒
c3p0.acquire_increment当连接池里面的连接用完的时候,C3P0一次性获取的新的连接数
c3p0.validate每次都验证连接是否可用

如果要使用C3P0作为连接池,还需要引入 hibernate-c3p0

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-c3p0</artifactId>
  <version>${hibernate.version}</version>
</dependency>

然后配置连接池:

<!-- 配置C3P0连接池(这个是 Hibernate 5.x 之后的新类) -->
<property name="connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property>
<!--在连接池中可用的数据库连接的最少数目 -->
<property name="c3p0.min_size">5</property>
<!--在连接池中所有数据库连接的最大数目 -->
<property name="c3p0.max_size">20</property>
<!--设定数据库连接的过期时间,以ms为单位,如果连接池中的某个数据库连接空闲状态的时间,超过timeout时间,则会从连接池中清除 -->
<property name="c3p0.timeout">120</property>
<!--每3000s检查所有连接池中的空闲连接以s为单位 -->
<property name="c3p0.idle_test_period">3000</property>

2. ORM映射文件

2.1 使用XML配置映射关系(推荐)

  1. Student 类编写映射关系 Student.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>
    <!-- name代表的是类名,table代表的是表名 --> <!-- 类的地址请自行调整 -->
    <class name="pers.model.domain.Student" table="student">
        <!-- name代表的是User类中的id属性,column代表的是user表中的主键id -->
        <id name="id" type="java.lang.Integer" column="id" >
            <!-- 主键生成策略 -->
            <generator class="increment" />
        </id>
        <!-- 其他属性使用property标签映射 -->
        <property name="name" type="java.lang.String" column="name" />
        <property name="age" type="java.lang.Integer" column="age" />
        <property name="email" type="java.lang.String" column="email" />
    </class>
</hibernate-mapping>
  1. hibernate.cfg.xml 中标注该映射关系文件:
<mapping resource="pers/model/domain/Student.hbm.xml"/> <!-- 类的地址请自行调整 -->

2.2 使用注解配置映射关系

  1. 修改 Student 类:
@Data
@Entity
// @Table(name = "student")
public class Student {
    @Id
    @GeneratedValue(generator = "increment")
    private Integer id;
    // @Column(name = "name") // 默认映射的表名取字段名
    private String name;
    private Integer age;
    private String email;
}
  1. hibernate.cfg.xml 中标注该实体映射:
<mapping class="pers.model.domain.Student"/> <!-- 类的地址请自行调整 -->
2.2.1 注解详解
  • @Entity :【必填】声明一个实体,,默认情况下 name 映射的表名与实体类同名。
  • @Table :【可选】映射到一个表,有三个值 name (表名)、 catalog (目录)、 schema (模式/数据库)。
  • @Id :【必填】声明一个主键。
  • @Column :【可选】声明一个列,会在Entity中默认生成。
  • @Temporal :特殊的, java.util.Date 需要使用该标记来进行映射,如: @Temporal(TemporalType.TIMESTAMP) (年月日时分秒)、@Temporal(TemporalType.DATA) (年月日)、@Temporal(TemporalType.TIME) (时分秒)。
  • @GenericGenerator :声明一种主键生成策略。
  • @GenericValue :引用某种主键生成策略,需要搭配@GenericGenerator 使用(参考文献)。如:
@GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy = "uuid.hex")

3. Hibernate运行流程

在这里插入图片描述

4. 核心接口

上文中经过映射的ORM可以编写以下测试类:

public class Main {
    public static void main(String[] args){
        // 1.创建Configuration对象并加载hibernate.cfg.xml配置文件
        Configuration config = new Configuration().configure();
        // 2.获取SessionFactory
        @Cleanup SessionFactory sessionFactory = config.buildSessionFactory();
        // 3.得到一个Session
        @Cleanup Session session = sessionFactory.openSession();
        // 4.开启事务
        Transaction transaction = session.beginTransaction();
        // 5.执行持久化操作(全表查询)
        session.createQuery("FROM Student").list().forEach(System.out::println);
        // 6.提交事务
        transaction.commit();
        // 7.关闭资源
        // session.close(); // 已经由lombok的@Cleanup处理
        // sessionFactory.close();
    }
}

4.1 Configuration

配置项,默认状态下会从 src 目录下读取 hibernatecfg.xml 的配置文件:

Configuration config = new Configuration().configure();
// Configuration config = new Configuration().configure("文件的位置"); // 手动指定
  • 仅在初始化阶段存在,待到 SessionFactory 实例化后便销毁。

4.2 SessionFactory

该接口负责解析ORM文件、构建Session对象、二级缓存,且线程安全。

  • 该对象较为重量级,一般以单实例启动,除非应用中有多个数据源。
static final SessionFactory sessionFactory = config.buildSessionFactory();

4.3 Session

持久化操作的核心API,由 SessionFactory.openSession() 创建。

  • 该对象是轻量级的,可以很方便的创建和销毁,但是其线程不安全,应确保一个 Session 仅被一个线程使用。
  • 创建方法除了SessionFactory.openSession() ,还可以使用 sessionFactory.getCurrentSession() 。区别在于前者需要手动调用 close() 关闭,后者会直接跟当前线程绑定、在事务提交或回滚时自动关闭。

其提供的API有:

名称描述
save()用于执行添加对象操作
update()用于执行修改对象操作
saveOrUpdate()用于执行添加或修改对象操作
delete()用于执行删除对象操作
get()根据主键查询数据
load()根据主键查询数据
createQuery()用于数据库操作对象
createSQLQuery()用于数据库操作对象
createCriteria()面向对象的条件查询

4.4 Transaction

事务管理接口,由 session.beginTransaction() 创建并启动事务,共有三个方法:

  • commit() 方法:提交相关联的 session 实例。
  • rollback() 方法:撤销事务操作。
  • wasCommitted() 方法:检查事务是否提交。

4.5 Query(推荐使用HQL)

查询接口,通过HQL语句进行查询,如:

@Test
public void testHQLSelectById(){
    Configuration config = new Configuration().configure();
    @Cleanup SessionFactory sessionFactory = config.buildSessionFactory();
    @Cleanup Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    
    val hql = "SELECT s FROM Student s WHERE s.id = :id"; // HQL 语句
    Query query = session.createQuery(hql).setParameter("id", 2); // Query对象
    query.list().forEach(System.out::println);
    
    transaction.commit();
}

Query对象通过 list()iterator() 等方法收集结果,常用方法有:

名 称描 述
setterQuery 接口中提供了一系列的 setter 方法用于设置查询语句中的参 数,针对不同的数据类型,需要用到不同的 setter 方法
Iterator iterator()该方法用于查询语句,返回的结果是一个 Iterator 对象,在读取时只能按照顺序方式读取,它仅把使用到的数据转换成 Java 实体对象
Object uniqueResult()该方法用于返回唯一的结果,在确保只有一条记录的查询时可以 使用该方法
int executeUpdate()该方法是 Hibernate 3 的新特性,它支持 HQL 语句的更新和删除操作
Query setFirstResult(int firstResult)该方法可以设置获取第一个记录的位置,也就是它表示从第几条 记录开始查询,默认从 0 开始计算
Query setMaxResult(int maxResults)该方法用于设置结果集的最大记录数,通常与 setFirstResult() 方法结合使用,用于限制结果集的范围,以实现分页功能
4.5.1 HQL语句简析

HQL语法与SQL类似(查询时可以省略 select 关键字):

[select/update/delete...]from...[where...][group by...][having...][order by...][asc/desc]
  • 不同的是,HQL中没有表、字段的概念,用类和字段做了替换,如 from Student 而不是 from student (前者是一个类,后者是一个SQL表);再例如 select new Student(s.id, s.name, s.age, s.email) from Student as s
  • 条件查询将使用以下API设置参数:
方 法 名说 明
setString(int index, value)给映射类型为 String 的参数赋值
setDate()给映射类型为 Date 的参数赋值
setDouble()给映射类型为 double 的参数赋值
setBoolean()给映射类型为 boolean 的参数赋值
setInteger()给映射类型为 int 的参数赋值
setTime()给映射类型为 Date 的参数赋值
setParameter(String":id", value)给任意类型的参数赋值
  • 条件查询的占位符可用 ?:id,当 使用后者时,需要使用 setParameter ,如 setParameter("age", 15)

4.6 Criteria

一种面向对象的查询接口,被称为QBC查询,不关心SQL语句如何编写。(参照开头给出的用户手册或者此处)

现已被Hibernate 5弃用,转为JPA标准的CriteriaQuery,举例如下:。

// QBC查询
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Student> criteria = builder.createQuery(Student.class);
Root<Student> root = criteria.from(Student.class);
criteria.where(builder.equal(root.get("id"), 1));

System.out.println(session.createQuery(criteria).getSingleResult());

5. 持久化对象的状态与访问顺序

5.1 状态转换图

在这里插入图片描述

1)瞬时态(transient)

瞬时态也称为临时态或者自由态,瞬时态的对象是由 new 关键字开辟内存空间的对象,不存在持久化标识 OID(相当于主键值),且未与任何的 Session 实例相关联,在数据库中也没有记录,失去引用后将被 JVM 回收。瞬时对象在内存孤立存在,它是携带信息的载体,不和数据库的数据有任何关联关系。

2)持久态(persistent)

持久态的对象存在一个持久化标识 OID,当对象加入到 Session 缓存中时,就与 Session 实例相关联。它在数据库中存在与之对应的记录,每条记录只对应唯一的持久化对象。需要注意的是,持久态对象是在事务还未提交前变成持久态的。

3)脱管态(detached)

脱管态也称离线态或者游离态,当持久化对象与 Session 断开时就变成了脱管态,但是脱管态依然存在持久化标识 OID,只是失去了与当前 Session 的关联。需要注意的是,脱管态对象发生改变时 Hibernate 是不能检测到的。

5.2 一级缓存与快照

当对象处于持久态时,也就是在某个一级缓存 Session 中时,则直接从一级缓存取出而不再访问数据库。

  • 能够在某些情况下,按照缓存中对象的变化,执行相关的 SQL 语句同步更新数据库,这一过程被称为“刷出缓存(flush)”,即 session.flush() 会发出 update 指令。
  • Hibernate在往一级缓存中存入数据时还会往快照区存入数据;当调用事务的 commit 方法时会CAS快照区,如果发现数据不一致,则执行 update 方法将一级缓存的数据写入数据源并更新快照区,如:
@Test
public void test3() {
    Session session = HibernateUtils.getSession(); // 得到session对象
    session.beginTransaction();
    Goods goods = new Goods();
    goods.setName("钢笔");
    goods.setPrice(5.0);
    session.save(goods);    // 向一级缓存中存入session对象
    goods.setPrice(4.5);    // 提交价格
    session.getTransaction().commit();    // 提交事务,此时比对缓冲区发现价格发生变化,于是更新数据库
    session.close();    //关闭资源
    // 数据库中最终存入了一根4.5¥的钢笔
}

5.3 二级缓存

二级缓存属于进程级别的缓存,由 SessionFactory 进行管理。

当在配置文件中启用二级缓存对象时,当一级缓存Miss,就会从二级缓存搜寻,最后才会访问数据库获取数据。

Hibernate配置文件中的缓存属性:

  • cache.provider_class :自定义的 CacheProvider 的类名。取值 classname.of.CacheProvider
  • cache.use_minimal_puts :以频繁的读操作为代价, 优化二级缓存来最小化写操作。在Hibernate中,这个设置对的集群缓存非常有用, 对集群缓存的实现而言,默认是开启的,取值 true|false
  • cache.use_query_cache :允许查询缓存, 个别查询仍然需要被设置为可缓存的,取值 true|false
  • cache.use_second_level_cache :能用来完全禁止使用二级缓存。对那些在类的映射定义中指定 <cache> 的类,会默认开启二级缓存,取值 true|false
  • cache.query_cache_factory :自定义实现QueryCache接口的类名,默认为内建的 StandardQueryCache
  • cache.region_prefix :二级缓存区域名的前缀。
  • cache.use_structured_entries :强制Hibernate以更人性化的格式将数据存入二级缓存。取值 true|false

启用示例:

<property name ="hibernate.cache.use_second_level_cache">true</property>
<property name ="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

<mapping resource ="example/Student.hbm.xml"/>
<!-- 规定进行二级缓存的对象 -->
<class-cache usage ="read-write" class ="example.Student"/>

由于在二级缓存中也会出现并发问题,因此在 Hibernate 的二级缓存中,可以设定以下四种类型的并发访问策略,以解决这些问题。每一种访问策略对应一种事务隔离级别,具体介绍如下:

  1. 只读型(Read-Only):提供 Serializable 事务隔离级别,对于从来不会被修改的数据,可以采用这种访问策略。
  2. 读写型(Read-write):提供 Read Committed 事务隔离级别,对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为它可以防止脏读。
  3. 非严格读写(Nonstrict-read-write):不保证缓存与数据库中数据的一致性,提供 Read Uncommitted 事务隔离级别,对于极少被修改,而且允许脏读的数据,可以采用这种策略。
  4. 事务型(Transactional):仅在受管理环境下使用,它提供了 Repeatable Read 事务隔离级别。对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读。

二级缓存构建示例参考地址:Hibernate二级缓存的并发访问策略和常用插件

6. M vs. M映射关系

6.1 一对多映射关系

以一个班级含有多个学生为例:

@Data
public class Class_ { // One
    private Integer id;
    private String name; // 班级名称
    private Set<Student> students = new HashSet<Student>();
}
@Data
public class Student { // Many
    private Integer id;
    private String name; // 学生名称
    private Class_ class_; // 学生从属于某个班级
}

映射文件:

<hibernate-mapping>
    <class name="example.Class_" table="class">
        <id name="id" column="id">
            <generator class="increment" />
        </id>
        <property name="name" column="name"/>
      
        <!-- 一对多的关系使用set集合映射(对应Set对象) -->
        <set name="students" cascade="save-update"> <!-- 默认为主控方,在save 或 update操作时同步关联 -->
            <!-- 确定关联的外键列 -->
            <key column="id" /> <!-- Class.id -->
            <!-- 映射到关联类属性 -->
            <one-to-many class="example.Student" />
        </set>
      
    </class>
</hibernate-mapping>
<hibernate-mapping>
    <class name="example.Student" table="student">
        <id name="id" column="id">
            <generator class="increment" />
        </id>
        <property name="name" column="name"/>
      
        <!-- 多对一关系映射 -->
        <many-to-one name="class_" class="example.Class_"></many-to-one>
      
    </class>
</hibernate-mapping>

6.2 多对多映射关系

假设一个学生能选多个班级(比如选课时的试读):

@Data
public class Student { // Many
    private Integer id;
    private String name; // 学生名称
    private Set<Class_> class_ new HashSet<Class_>();; // 学生从属于某个班级
}
<hibernate-mapping>
    <class name="example.Student" table="student">
        <id name="id" column="id">
            <generator class="increment" />
        </id>
        <property name="name" column="name"/>
      
        <set name="class_" table="class">
            <key column="id" /> <!-- Student.id -->
            <many-to-many class="example.Class_" column="id" /> <!-- Class_.id -->
        </set>
      
    </class>
</hibernate-mapping>
<hibernate-mapping>
    <class name="example.Class_" table="class">
        <id name="id" column="id">
            <generator class="increment" />
        </id>
        <property name="name" column="name"/>
      
        <set name="student" table="student" inverse="true"> <!-- 由另一方管理关系 -->
            <key column="id" /> <!-- Class_.id -->
            <many-to-many class="example.Student" column="id" /> <!-- Student.id -->
        </set>
      
    </class>
</hibernate-mapping>

6.3 控制关系

6.3.1 反转关系
  • 默认情况下,一对多关系中由多方管理关系;多对多关系中,需要对其中一方的 <set>inverse 取值为 true (默认值为 false )。
  • inverse 只对 <set><one-to-many><many-to-many> 标签有效,对 <many-to-one><one-to-one> 标签无效。
6.3.2 级联关系

级联关系表示当主控方执行操作时,关联对象也将与其保持同步。 <set> 标签的 cascade 取值有:

属性值描 述
save-update在执行 save、update 或 saveOrUpdate 吋进行关联操作
delete在执行 delete 时进行关联操作
delete-orphan删除所有和当前解除关联关系的对象
all所有情况下均进行关联操作,但不包含 delete-orphan 的操作
all-delete-orphan所有情况下均进行关联操作
none所有情况下均不进行关联操作,这是【默认值】

7. 悲观锁与乐观锁

  • 悲观锁:先锁再访问读写数据,使用 LockMode.UPGRADE
  • 乐观锁:先尝试访问读写,如果前后数据发生异动再尝试加锁读写。

悲观锁的实现:

Student Student = (Student)session.get(Student.class, 1 , LockMode.UPGRADE); // 示例

乐观锁的实现需要对数据库进行操作,在表中增加一个 VERSION INT(10) 字段,用于乐观锁的前后比对。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值