Hibernate

Posted by 余腾 on 2019-03-26
Estimated Reading Time 41 Minutes
Words 10.8k In Total
Viewed Times

一、持久化框架

ORM(Object/Relation Mapping):对象/关系映射

面向对象概念 面向关系概念
对象 表的行(记录)
属性 表的列(字段)

ORM的思想

将关系数据库中表中的记录映射成为对象,以对象的形式展现,程序员可以把对数据库的操作转化为对对象的操作。ORM 采用元数据来描述 对象-关系 映射细节,元数据通常采用 XML 格式,并且存放在专门的 对象-关系 映射文件中。

hb.png

添加 jar 包



二、Hibernate 配置文件

Hibernate 配置文件主要用于配置数据库连接和 Hibernate 运行时所需的各种属性。

每个 Hibernate 配置文件对应一个 Configuration 对象。

  • Hibernate配置文件可以有两种格式:
    • hibernate.properties
    • hibernate.cfg.xml (推荐)

1、创建 hibernate.cfg.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<hibernate-configuration>
<session-factory>
<!-- 配置连接数据库的基本信息 -->
<property name="connection.username">root</property>
<property name="connection.password">root</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/hibernate5</property>

<!-- 配置 hibernate 的基本信息 -->
<!-- hibernate 所使用的数据库方言 MySQL5InnoDBDialect 注意是MySQL5 -->
<property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>

<!-- 执行操作时是否在控制台打印 SQL -->
<property name="show_sql">true</property>

<!-- 是否对 SQL 进行格式化 -->
<property name="format_sql">true</property>

<!-- 指定自动生成数据表的策略 -->
<property name="hbm2ddl.auto">update</property>

<!-- 指定关联的 .hbm.xml 文件 -->
<mapping resource="com/hibernate/News.hbm.xml"/>

</session-factory>
</hibernate-configuration>
1
2
3
4
5
6
对于Oracle有效 对于 MySQL无效
<!-- 设定 JDBC 的 Statement 读取数据的时候每次从数据库中取出的记录条数 -->
<property name="hibernate.jdbc.fetch_size">100</property>

<!-- 设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小 -->
<property name="jdbc.batch_size">30</property>

2、创建持久化 Java 类

  • Hibernate 不要求持久化类继承任何父类或实现接口,这可以保证代码不被污染。这就是Hibernate被称为低侵入式设计的原因

1、提供一个无参的构造器: 使Hibernate可以使用Constructor.newInstance() 来实例化持久化类。
2、提供一个标识属性(identifier property): 通常映射为数据库表的主键字段。如果没有该属性,一些功能将不起作用,如:Session.saveOrUpdate()。
3、为类的持久化类字段声明访问方法(get/set): Hibernate对JavaBeans 风格的属性实行持久化。
4、使用非 final 类: 在运行时生成代理是 Hibernate 的一个重要的功能。如果持久化类没有实现任何接口, Hibnernate 使用 CGLIB 生成代理。如果使用的是 final 类, 则无法生成 CGLIB 代理。
5、重写 eqauls 和 hashCode 方法: 如果需要把持久化类的实例放到 Set 中(当需要进行关联映射时), 则应该重写这两个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.Date;
public class News {
private Integer id; // field
private String title;
private String author;
private Date date;
public News() {
}
public News(String title, String author, Date date) {
super();
this.title = title;
this.author = author;
this.date = date;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
@Override
public String toString() {
return "News [id=" + id + ", title=" + title + ", author=" + author + ", date=" + date + "]";
}
}

3、创建对象-关系映射文件

  • Hibernate 采用 XML 格式的文件来指定对象和关系数据之间的映射。
  • 在运行时 Hibernate 将根据这个映射文件来生成各种 SQL 语句。
  • 映射文件的扩展名为 .hbm.xml

4、测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import java.util.Date;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.junit.Test;

public class HibernateTest {

@Test
public void test() {

// 1. 创建一个 SessionFactory 对象
SessionFactory sessionFactory = null;

// 1). 创建 Configuration 对象: 对应 hibernate 的基本配置信息和 对象关系映射信息
Configuration configuration = new Configuration().configure();

// 4.0 之前这样创建
// sessionFactory = configuration.buildSessionFactory();

// 2). 创建一个 ServiceRegistry 对象: hibernate 4.x 新添加的对象
// hibernate 的任何配置和服务都需要在该对象中注册后才能有效.
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(configuration.getProperties())
.buildServiceRegistry();

// 3).
sessionFactory = configuration.buildSessionFactory(serviceRegistry);

// 2. 创建一个 Session 对象
Session session = sessionFactory.openSession();

// 3. 开启事务
Transaction transaction = session.beginTransaction();

// 4. 执行保存操作
News news = new News("Java", "Hibernate", new Date(new java.util.Date().getTime()));
session.save(news);//save后,news变成持久态
//news.setTitle("HB"); ``对持久态的改变,会在session提交时自动更新数据库``

// 5. 提交事务
transaction.commit();

// 6. 关闭 Session
session.close();
//session关闭后 news.setTitle("HB"); //这时改变其属性,将不会自动更新数据库

// 7. 关闭 SessionFactory 对象
sessionFactory.close();
}
}


三、Hibernate 属性

1、Configuration 类

Configuration 类负责管理 Hibernate 的配置信息。

包括如下内容:

  • Hibernate 运行的底层信息:数据库的URL、用户名、密码、JDBC驱动类,数据库方言Dialect,数据库连接池等(对应 hibernate.cfg.xml 文件)。持久化类与数据表的映射关系(*.hbm.xml)

创建 Configuration 的方式

  • 推荐使用XML方式 Xml文件(hibernate.cfg.xml)

Configuration cfg = new Configuration().configure();


2、SessionFactory 接口

针对单个数据库映射关系经过编译后的内存镜像,是线程安全的。

  • SessionFactory 对象一旦构造完毕,即被赋予特定的配置信息

SessionFactory是生成Session的工厂

  • 构造 SessionFactory 很消耗资源,一般情况一个应用中只初始化一个 SessionFactory 对象。
    Hibernate4 新增了一个 ServiceRegistry 接口,所有基于 Hibernate 的配置或者服务都必须统一向这个 ServiceRegistry 注册后才能生效。

Hibernate4 中创建 SessionFactory 的步骤

1
2
3
4
5
6
7
8
9
10
// 1. 创建 Configuration 对象: 对应 hibernate 的基本配置信息和 对象关系映射信息
Configuration configuration = new Configuration().configure();

// 2.创建一个 ServiceRegistry 对象: hibernate 4.x 新添加的对象
// hibernate 的任何配置和服务都需要在该对象中注册后才能有效。
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(configuration.getProperties())
.buildServiceRegistry();
// 3. 得到一个 SessionFactory 对象
SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);

3、Session (接口)

Session 是应用程序与数据库之间交互操作的一个单线程对象,是 Hibernate 运作的中心

  • 所有持久化对象必须在 session 的管理下才可以进行持久化操作,相当于 JDBC 中的 Connection。

此对象的生命周期很短

  • Session 对象有一个一级缓存,显式执行 flush 之前,所有的持久层操作的数据都缓存在 session 对象处。

持久化类与 Session 关联起来后就具有了持久化的能力
Session 类的方法:

  • 取得持久化对象的方法: get() load()
  • 持久化对象保存,更新和删除:save(),update(),saveOrUpdate(),delete()
  • 开启事务: beginTransaction()
  • 管理 Session 的方法:isOpen(),flush(), clear(), evict(), close()等

4、Transaction(事务)

代表一次原子操作,它具有数据库事务概念。所有持久层都应在事务管理下进行,即使是只读操作。

  • Transaction tx = session.beginTransaction();

    常用方法:

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

四、Hibernate 配置文件的配置项

1、hbm2ddl.auto

  • 该属性可实现正向工程, 即由 java 代码生成数据库脚本, 进而生成具体的表结构。
1
2
<!-- 指定自动生成数据表的策略 -->
<property name="hbm2ddl.auto">update</property>

取值 create | update | create-drop | validate

  • create: 会根据 .hbm.xml 文件来生成数据表, 但是每次运行都会删除上一次的表 ,重新生成表,哪怕二次没有任何改变。
  • create-drop: 会根据 .hbm.xml 文件生成表,但是SessionFactory一关闭,表就自动删除。
  • update: 最常用的属性值,也会根据 .hbm.xml 文件生成表,但若 .hbm.xml 文件和数据库中对应的数据表的表结构不同,Hiberante 将更新数据表结构,但不会删除已有的行和列。
    validate: 会和数据库中的表进行比较,若 .hbm.xml 文件中的列在数据表中不存在,则抛出异常。

2、format_sql

  • 是否将 SQL 转化为格式良好的 SQL。 取值 true | false
    1
    2
    <!-- 是否对 SQL 进行格式化 -->
    <property name="format_sql">true</property>

3、在 Hibernate 中设置隔离级别

  • JDBC 数据库连接使用数据库系统默认的隔离级别。

在 Hibernate 的配置文件中可以显式的设置隔离级别。 每一个隔离级别都对应一个整数:

  • READ UNCOMMITED —-> 1
  • READ COMMITED —-> 2
  • REPEATABLE READ —-> 4
  • SERIALIZEABLE —-> 8

Hibernate 通过 映射文件指定 hibernate.connection.isolation 属性来设置事务的隔离级别。

1
2
<!-- 设置 Hibernate 的事务隔离级别 -->
<property name="connection.isolation">2</property>

4、在 hibernate 中使用 C3P0 数据源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 配置 C3P0 数据源 -->
//可配可不配
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
//数据库连接池的最大连接数
<property name="hibernate.c3p0.max_size">10</property>
//数据库连接池的最小连接数
<property name="hibernate.c3p0.min_size">5</property>
//当数据库连接池中的连接耗尽时, 同一时刻获取多少个数据库连接
<property name="c3p0.acquire_increment">2</property>
//表示连接池检测线程多长时间检测一次池内的所有链接对象是否超时
<property name="c3p0.idle_test_period">2000</property>
//数据库连接池中连接对象在多长时间没有使用过后,就应该被销毁
<property name="c3p0.timeout">2000</property>
//缓存 Statement 对象的数量
<property name="c3p0.max_statements">10</property>

五、通过 Session 操纵对象


  • Session 接口是 Hibernate 向应用程序提供的操纵数据库的最主要的接口, 它提供了基本的保存, 更新, 删除和加载 Java 对象的方法。
  • Session 具有一个缓存, 位于缓存中的对象称为持久化对象, 它和数据库中的相关记录对应。
  • Session 能够在某些时间点, 按照缓存中对象的变化来执行相关的 SQL 语句, 来同步更新数据库, 这一过程被称为刷新缓存(flush)。

站在持久化的角度, Hibernate 把对象分为 4 种状态:
Session 的特定方法能使对象从一个状态转换到另一个状态。

  • 持久化状态
  • 临时状态
  • 游离状态
  • 删除状态

1、持久化对象 ( 托管 ) (Persist)

  • 在 OID 不为 null Session 缓存中叫做持久化态 数据库中已经有和其对应的记录,持久化对象和数据库中的相关记录对应。
  • Session 在 flush 缓存时, 会根据持久化对象的属性变化, 来计划SQL语句同步更新数据库。
  • 在事务提交后 更新数据库表中数据,数据才会永久保存下来,也就是被持久化了。
  • 在同一个 Session 实例的缓存中, 数据库表中的每条记录只对应唯一的持久化对象。

2、临时对象(Transient)

  • 在使用代理主键情况下,OID 通常为 null 不处于 Session 的缓存中在数据库中没有对应记录。

3、游离对象(也叫 脱管)(Detached)

  • OID 不为 null 不再处于 Session 缓存中
  • 一般情况下,游离对象由持久化对象转变过来的,因此数据库中可能还存在与它对应的记录。

4、删除对象(Removed)

  • 在数据库中没有和其 OID 对应的记录 不再处于 Session 缓存中 一般情况下, 应用程序不该再使用被删除的对象。

👉对象的状态转换图

Session 的 save() 方法

Session 的 save() 方法使一个临时对象转变为持久化对象。
Session 的 save() 方法完成以下操作:

  • 把一个临时对象加入到 Session 缓存中, 使它进入持久化状态。
  • 选用映射文件指定的标识符生成器, 为持久化对象分配唯一的 OID。 在使用代理主键的情况下, setId() 方法为对象设置 OID 使无效的。
  • 计划执行一条 insert 语句:在 flush 缓存的时候。
  • Hibernate 通过持久化对象的 OID 来维持它和数据库相关记录的对应关系。
  • 当一个对象处于持久化状态时, 不允许程序随意修改它的 ID。

persist() 和 save() 区别:

  • 当对一个 OID 不为 Null 的对象执行 save() 方法时, 会把该对象以一个新的 oid 保存到数据库中。但执行 persist() 方法时会抛出一个异常。

Session 的 get() 和 load() 方法

都可以根据跟定的 OID 从数据库中加载一个持久化对象。


get() 和 load()区别:

  • 1、 get 是 立即检索, load 是延迟检索。
    • 执行 get 方法: 会立即加载对象,不支持延迟加载。
    • 执行 load 方法:若不使用该对象, 则不会立即执行查询操作, 而返回一个代理对象。
  • 2、异常: 在需要初始化代理对象之前已经关闭了 Session。
    • load 方法会抛出 LazyInitializationException
  • 3、 若数据表中不存在与 OID 对应的记录时, Session 也没有被关闭。

get 返回 null —-> load 若不使用该对象的任何属性, 没问题。若需要初始化了, 抛出 ObjectNotFoundException 异常。


Session 的 update() 方法

  • 1、若更新一个持久化对象, 不需要显示 调用 update 方法。

    • 因为在调用 Transaction 的 commit() 方法时, 会先执行 session 的 flush 方法。
  • 2、更新一个游离对象, 需要显式 调用 session 的 update 方法。

    • 可以把一个游离对象变为持久化对象。

需要注意:

  • 对于当前Session是 游离对象 无论要更新的 数据和数据表的记录 是否一致, 都会发送 UPDATE 语句。

Session 的 update() 方法使一个游离对象转变为持久化对象, 并且计划执行一条 update 语句。

若希望 Session 仅当修改了对象的属性时, 才执行 update() 语句 可以把映射文件中 <class> 元素的select-before-update 设为 true。该属性的默认值为 false 但通常不需要设置该属性。

  • 当 update() 方法关联一个游离对象时, 如果在 Session 的缓存中已经存在相同 OID 的持久化对象, 会抛出异常。
  • 当 update() 方法关联一个游离对象时, 如果在数据库中不存在相应的记录, 也会抛出异常。

Session 的 saveOrUpdate() 方法

Session 的 saveOrUpdate() 方法同时包含了 save() 与 update() 方法的功能。

  • 判定对象为临时对象的标准
    • Java 对象的 OID 为 null
    • 映射文件中为 设置了 unsaved-value 属性
    • 并且 Java 对象的 OID 取值与这个 unsaved-value 属性值匹配

Session 的 merge() 方法


Session 的 delete() 方法

Session 的 delete() 方法既可以删除一个游离对象, 也可以删除一个持久化对象。

Session 的 delete() 方法处理过程:

  • 计划执行一条 delete 语句。
  • 把对象从 Session 缓存中删除, 该对象进入删除状态。
    • Hibernate 的 cfg.xml 配置文件中有一个 hibernate.use_identifier_rollback 属性,其默认值为 false。
    • 若把它设为 true, 将改变 delete() 方法的运行行为:
      • delete() 方法会把持久化对象或游离对象的 OID 设置为 null, 使它们变为临时对象。

Session 的 Evict() 方法

  • 从 session 缓存中把指定的持久化对象移除。

Session 的 refresh() 方法

  • 会强制发送 SELECT 语句,使 Session 缓存中对象的状态和数据表中对应的记录保持一致!但是有事务隔离级别限制。

Session 的 clear() 方法

  • 清理缓存

Session 缓存

  • 在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存。
  • 只要Session实例没有结束生命周期, 且没有清理缓存,则存放在它缓存中的对象也不会结束生命周期。
  • Session 缓存可减少 Hibernate 应用程序访问数据库的频率。

flush 缓存

flush:使数据库表中的记录和 Hibernate Session 缓存中的对象的状态保持一致。为了保持一致, 则可能会发送对应的 SQL 语句。

  • Session 按照缓存中对象的属性变化来同步更新数据库。

默认情况下 Session 在以下时间点刷新缓存:

  • 1、显式调用 Session 的 flush() 方法
  • 2、应用程序调用 Transaction 的 commit() 方法,该方法先 flush ,然后向数据库提交事务。
  • 3、flush() 方法会可能会发送 SQL 语句, 但不会提交事务。

flush 缓存的例外情况:
注意: 在未提交事务或显式的调用 session.flush() 方法之前, 也有可能会进行 flush() 操作。

  • 执行 HQL 或 QBC 查询, 如果缓存中持久化对象的属性已经发生了变化,会先进行 flush() 操作, 以得到数据表的最新的记录。
  • 如果对象使用 native 生成器生成 OID, 那么当调用 Session 的 save() 方法保存对象时, 会立即执行向数据库插入该实体的 insert 语句。
    • 因为 save 方法后, 必须保证对象的 ID 是存在的!

commit() 和 flush() 方法的区别:

  • flush 执行一系列 sql 语句,但不提交事务;
  • commit() 方法先调用 flush() 方法,然后提交事务。提交事务意味着对数据库操作永久保存。

六、对象关系映射文件

POJO 类 和 数据库的映射文件 *.hbm.xml

  • POJO 类和关系数据库之间的映射可以用一个XML文档来定义。
  • 通过 POJO 类的数据库映射文件,Hibernate可以理解持久化类和数据表之间的对应关系,也可以理解持久化类属性与数据库表列之间的对应关系。
  • 在运行时 Hibernate 将根据这个映射文件来生成各种 SQL 语句。
  • 映射文件的扩展名为 .hbm.xml

hibernate-mapping

  • 类层次:class
  • 主键:id
  • 基本类型:property
  • 实体引用类: many-to-one | one-to-one
  • 集合:set | list | map | array
    • one-to-many
    • many-to-many
  • 子类:subclass | joined-subclass
  • 其它:component | any 等
  • 查询语句:query(用来放置查询语句,便于对数据库查询的统一管理和优化)

每个Hibernate-mapping中可以同时定义多个类。 但更推荐为每个类都创建一个单独的映射文件


1、hibernate-mapping

hibernate-mapping 是 hibernate.hbm.xml 映射文件的根元素。

  • schema: 指定所映射的数据库schema的名称。若指定该属性, 则表明会自动添加该 schema 前缀。
  • catalog:指定所映射的数据库catalog的名称。
  • default-cascade(默认为 none): 设置hibernate默认的级联风格。
    • 若配置 Java 属性, 集合映射时没有指定 cascade 属性, 则 Hibernate 将采用此处指定的级联风格。
  • default-access (默认为 property): 指定 Hibernate 的默认的属性访问策略。
    • 默认值为 property, 即使用 getter, setter 方法来访问属性. 若指定 access, 则 Hibernate 会忽略 getter/setter 方法, 而通过反射访问成员变量。
  • default-lazy(默认为 true): 设置 Hibernat morning的延迟加载策略。
    • 该属性的默认值为 true, 即启用延迟加载策略。 若配置 Java 属性映射, 集合映射时没有指定 lazy 属性, 则 Hibernate 将采用此处指定的延迟加载策略。
  • auto-import (默认为 true): 指定是否可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。
  • package (可选): 指定一个包前缀,如果在映射文档中没有指定全限定的类名,就使用这个作为包名。

2、class

class 是 hibernate-mapping 的子元素,class 元素用于指定类和表的映射。

  • name:指定该持久化类映射的持久化类的类名。
  • table:指定该持久化类映射的表名, Hibernate 默认以持久化类的类名作为表名。
  • dynamic-insert: 若设置为 true, 表示当保存一个对象时, 会动态生成 insert 语句,
    • insert 语句中仅包含所有取值不为 null 的字段. 默认值为 false
  • dynamic-update: 若设置为 true, 表示当更新一个对象时, 会动态生成 update 语句,
    • update 语句中仅包含所有取值需要更新的字段。 默认值为 false
  • select-before-update:设置 Hibernate 在更新某个持久化对象之前是否需要先执行一次查询。 默认值为 false。
  • batch-size:指定根据 OID 来抓取实例时每批抓取的实例数。
  • lazy: 指定是否使用延迟加载。
  • mutable: 若设置为 true, 等价于所有的 元素的 update 属性为 false, 表示整个实例不能被更新。 默认为 true。
  • discriminator-value: 指定区分不同子类的值. 当使用 元素来定义持久化类的继承关系时需要使用该属性。

3、id

id 是 class 的子元素。

  • id:设定持久化类的 OID 和表的主键的映射。
    • Hibernate 推荐在数据表中使用代理主键, 即不具备业务含义的字段。
    • 代理主键通常为整数类型, 因为整数类型比字符串类型要节省更多的数据库空间。
  • name: 标识持久化类 OID 的属性名。
  • column: 设置标识属性所映射的数据表的列名(主键字段的名字)。
  • unsaved-value:若设定了该属性, Hibernate 会通过比较持久化类的 OID 值和该属性值来区分当前持久化类的对象是否为临时对象。
  • type:指定 Hibernate 映射类型。 Hibernate 映射类型是 Java 类型与 SQL 类型的桥梁。
    • 如果没有为某个属性显式设定映射类型, Hibernate 会运用反射机制先识别出持久化类的特定属性的 Java 类型, 然后自动使用与之对应的默认的 Hibernate 映射类型。
  • Java 的基本数据类型和包装类型对应相同的 Hibernate 映射类型. 基本数据类型无法表达 null, 所以对于持久化类的 OID 推荐使用包装类型。
  • generator:设定持久化类设定标识符生成器。
    • class:指定使用的标识符生成器全限定类名或其缩写名。

主键生成策略generator

标识符 描述
increment 适用于代理主键。由Hibernate 自动以递增方式生成。
identity 适用于代理主键。由底层数据库生成标识符。
sequence 适用于代理主键。Hibernate根据底层数据库的序列生成标识符,这要求底层数据库支持序列。
hilo 适用于代理主键。Hibernate分局high/low算法生成标识符。
seqhilo 适用于代理主键。使用一个高/低位算法来高效的生成long, short或者int类型的标识符。
native 适用于代理主键。根据底层数据库对自动生成标识符的方式,自动选择identity. sequence或hilo。
uuid.hex 适用于代理主键。Hibernate采用128位的UUID算法生成标识符。
uuid.string 适用于代理主键。UUID被编码成一个16字符长的字符串。
assigned 适用于自然主键。由Java应用程序负责生成标识符。
foreign 适用于代理主键。使用另外一个相关联的对象的标识符。

increment 标识符生成器

  • increment 标识符生成器由 Hibernate 以递增的方式为代理主键赋值。
  • Hibernate 会先读取表中的主键的最大值, 而接下来向表中插入记录时, 就在 max(id) 的基础上递增, 增量为 1。
  • 适用范围:
    1、由于 increment 生存标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统。
    2、适用于只有单个 Hibernate 应用进程访问同一个数据库的场合, 在集群环境下不推荐使用它 测试使用。
    3、OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常。

identity 标识符生成器

  • identity 标识符生成器由底层标数据库来负责生成识符, 它要求底层数据库把主键定义为自动增长字段类型。

  • 适用范围:

    1、由于 identity 生成标识符的机制依赖于底层数据库系统, 因此, 要求底层数据库系统必须支持自动增长字段类型。
    2、OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常。


sequence 标识符生成器

  • sequence 标识符生成器利用底层数据库提供的序列来生成标识符。
  • Hibernate 在持久化一个对象时, 先从底层数据库的表 序列中获得一个唯一的标识号, 再把它作为主键值。
  • 适用范围:
    1、由于 sequence 生成标识符的机制依赖于底层数据库系统的序列,因此,要求底层数据库系统必须支持序列。支持序列的数据库包括:DB2,Oracle
    2、OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常。

hilo 标识符生成器

  • hilo 标识符生成器由 Hibernate 按照一种 high/low 算法*生成标识符, 它从数据库的特定表的字段中获取 high 值。
    • 根据下面的公式计算值:hi*(max_lo+1)+lo
    • 但是有一种特殊情况,就是hi是0的时候,那么第一个值不是0*(max_lo+1)+0=0 而是跳过0,直接就是1
  • Hibernate 在持久化一个 News 对象时, 由 Hibernate 负责生成主键值。
  • hilo 标识符生成器在生成标识符时, 需要读取并修改 HI_TABLE 表中的 NEXT_VALUE 值。
  • 适用范围:
    1、由于 hilo 生存标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统。
    2、OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常。

native 标识符生成器

  • native 标识符生成器依据底层数据库对自动生成标识符的支持能力, 来选择使用 identity, sequence 或 hilo 标识符生成器。
  • 适用范围:
    1、由于 native 能根据底层数据库系统的类型, 自动选择合适的标识符生成器, 因此很适合于跨数据库平台开发。
    2、OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常。

4、Property

property 元素用于指定类的属性和表的字段的映射。

  • name:指定该持久化类的属性的名字。
  • type:指定 Hibernate 映射类型。 Hibernate 映射类型是 Java 类型与 SQL 类型的桥梁。
    • 如果没有为某个属性显式设定映射类型, Hibernate 会运用反射机制先识别出持久化类的特定属性的 Java 类型, 然后自动使用与之对应的默认的 Hibernate 映射类型。
  • column:指定与类的属性映射的表的字段名。
    • 如果没有设置该属性, Hibernate 将直接使用类的属性名作为字段名。
  • not-null:若该属性值为 true, 表明不允许为 null, 默认为 false。
  • access:指定 Hibernate 的默认的属性访问策略。
    • 默认值为 property, 即使用 getter, setter 方法来访问属性。
    • 若指定 field, 则 Hibernate 会忽略 getter/setter 方法, 而通过反射访问成员变量。
  • unique:设置是否为该属性所映射的数据列添加唯一约束。
  • update:设置是否为该属性所映射的数据列能否被修改。
  • length:指定该属性所映射数据列的字段的长度。
  • index:指定一个字符串的索引名称。当系统需要 Hibernate 自动建表时, 用于为该属性所映射的数据列创建索引, 从而加快该数据列的查询。
  • scale:指定该属性所映射数据列的小数位数, 对 double, float, decimal 等类型的数据列有效。
  • formula:设置一个 SQL 表达式, Hibernate 将根据它来计算出派生属性的值。
    • 派生属性:并不是持久化类的所有属性都直接和表的字段匹配, 持久化类的有些属性的值必须在运行时通过计算才能得出来 这种称为派生属性
      • 使用 formula 属性时
    • formula=“(sql)” 的英文括号不能少
    • Sql 表达式中的列名和表名都应该和数据库对应, 而不是和持久化对象的属性对应
    • 如果需要在 formula 属性中使用参数, 这直接使用 where cur.id=id 形式
    • 其中 id 就是参数, 和当前持久化对象的 id 属性对应的列的 id 值将作为参数传入

5、映射 Java 的时间, 日期类型

两个基础知识:
1、在 Java 中, 代表时间和日期的类型包括: java.util.Datejava.util.Calendar

  • 此外, 在 JDBC API 中还提供了 3 个扩展了 java.util.Date 类的子类:
    • java.sql.Date
    • java.sql.Time
    • java.sql.Timestamp

2、这三个类分别和标准 SQL 类型中的 DATE, TIME 和 TIMESTAMP 类型对应。

  • 所以 java.util.Date 可以对应标准 SQL 类型中的 DATE, TIME 和 TIMESTAMP

基于上述, 所以在设置持久化类的 Date 类型是, 设置为 java.util.Date


如何进行映射

在标准 SQL 中, DATE 类型表示日期, TIME 类型表示时间, TIMESTAMP 类型表示时间戳, 同时包含日期和时间信息。


如何把 java.util.Date 映射为 DATE, TIME 和 TIMESTAMP?

  • 可以通过 property 的 type 属性来进行映射
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <property name="date" type="timestamp">
    <column name="DATE" />
    </property>

    <property name="date" type="data">
    <column name="DATE" />
    </property>

    <property name="date" type="time">
    <column name="DATE" />
    </property>
  • 其中 timestamp, date, time 既不是 Java 类型, 也不是标准 SQL 类型, 而是 hibernate 映射类型。*

7、Java 大对象类型的 Hiberante 映射

1
2
3
4
5
6
7
8
9
<!-- 映射大对象 -->
<!-- 若希望精确映射 SQL 类型, 可以使用 sql-type 属性. -->
<property name="content">
<column name="CONTENT" sql-type="mediumtext"></column>
</property>

<property name="image">
<column name="IMAGE" sql-type="mediumblob"></column>
</property>
  • Mysql 不支持标准 SQL 的 CLOB 类型, 在 Mysql 中, 用 TEXT, MEDIUMTEXT 及 LONGTEXT 类型来表示长度操作 255 的长文本数据
  • 在持久化类中, 二进制大对象可以声明为 byte[] 或 java.sql.Blob 类型 字符串可以声明为 java.lang.String 或 java.sql.Clob
  • 实际上在 Java 应用程序中处理长度超过 255 的字符串, 使用 java.lang.String 比 java.sql.Clob 更方便

8、映射组成关系 component

Hibernate 把持久化类的属性分为两种:

  • 值(value)类型:没有 OID, 不能被单独持久化, 生命周期依赖于所属持久化类的对象生命周期。
  • 实体(entity)类型:有 OID, 可以被单独持久化, 有独立的生命周期。

显然无法直接用 property 映射 POJO 属性。
Hibernate 使用 元素来映射组成关系, 该元素表名属性是另一个类一个组成部分, 在 Hibernate 中称之为组件。

  • 元素来映射组成关系

    • class:设定组成关系属性的类型, 此处表明 pay 属性为 Pay 类型
  • 元素指定组件属性所属的整体类

    • name:整体类在组件类中的属性名

七、通过 Hibernate 调用存储过程

  • Work 接口:直接通过 JDBC API 来访问数据库的操作。
  • Session 的 doWork(Work) 方法用于执行 Work 对象指定的操作, 即调用 Work 对象的 execute() 方法。
  • Session 会把当前使用的数据库连接传递给 execute() 方法。

八、Hibernate 与触发器协同工作

Hibernate 与数据库中的触发器协同工作时, 会造成两类问题:

1、触发器使 Session 的缓存中的持久化对象与数据库中对应的数据不一致;
2、触发器运行在数据库中, 它执行的操作对 Session 是透明的
Session 的 update() 方法盲目地激发触发器:无论游离对象的属性是否发生变化, 都会执行 update 语句, 而 update 语句会激发数据库中相应的触发器

解决方案:
在执行完 Session 的相关操作后, 立即调用 Session 的 flush() 和 refresh() 方法
迫使 Session 的缓存与数据库同步(refresh() 方法重新从数据库中加载对象)

在映射文件的的 元素中设置 select-before-update 属性 (和触发器协同工作时设置):
当 Session 的 update 或 saveOrUpdate() 方法更新一个游离对象时, 会先执行 Select 语句,
获得当前游离对象在数据库中的最新数据, 只有在不一致的情况下才会执行 update 语句


九、映射一对多关联关系

  • 在领域模型中, 类与类之间最普遍的关系就是关联关系。

  • 在 UML 中, 关联是有方向的。

  • 以 Customer 和 Order 为例:

    • 一个用户能发出多个订单, 而一个订单只能属于一个客户
    • 从 Order 到 Customer 的关联是多对一关联
    • 而从 Customer 到 Order 是一对多关联

1、单向 N-1

单向 N-1 关联只需从 N 的一端可以访问 1 的一端。

域模型:

  • 从 Order 到 Customer多对一单向关联需要在Order 类中定义一个 Customer 属性
  • 而在 Customer 类中无需定义存放 Order 对象的集合属性

关系数据模型:ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键

  • 显然无法直接用 property 映射 customer 属性
  • Hibernate 使用 <many-to-one> 元素来映射多对一关联关系
    • 元素来映射组成关系
      • name: 设定待映射的持久化类的属性的名字
      • class:设定待映射的持久化类的属性的类型
        * column: 设定和持久化类的属性对应的表的外键
        <many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one>

插入

  • 先插入 1 的一端, 再插入 n 的一端, 只有 INSERT 语句
    session.save(customer);
    session.save(order1);
    session.save(order2);

  • 先插入 n 的一端, 再插入 1 的一端, 会多出 UPDATE 语句
    session.save(order1);
    session.save(order2);
    session.save(customer);
    因为在插入多的一端时, 无法确定 1 的一端的外键值。所以只能等 1 的一端插入后, 再额外发送 UPDATE 语句。

推荐先插入 1 的一端, 后插入 n 的一端


获取

  • 若查询多的一端的一个对象, 则默认情况下, 只查询了多的一端的对象。 而没有查询关联的的那一端的对象!
  • 在需要使用到关联的对象时, 才发送对应的 SQL 语句 懒加载。
    • 在查询 Customer 对象时, 由多的一端导航到 1 的一端时, 若此时 session 已被关闭, 则默认情况下 会发生 LazyInitializationException 异常
  • 获取 Order 对象时, 默认情况下, 其关联的 Customer 对象是一个代理对象!

删除

  • 在不设定级联关系的情况下, 且 1 这一端的对象有 n 的对象在引用, 不能直接删除 1 这一端对象

更新

  • 一切正常

2、双向 1-N

双向 1-N 与 双向 N-1 是完全相同的两种情形

  • 双向 1-N 需要在 1 的一端可以访问 N 的一端, 反之依然。

域模型:

  • 从 Order 到 Customer 的多对一双向关联需要在Order 类中定义一个 Customer 属性
  • 而在 Customer 类中需定义存放 Order 对象的集合属性
  • 声明集合类型时, 需使用接口类型, 因为 hibernate 在获取集合类型时, 返回的是 Hibernate 内置的集合类型, 而不是 JavaSE 一个标准的集合实现。
  • 在定义集合属性时, 通常把它初始化为集合实现类的一个实例。
    • 这样可以提高程序的健壮性, 避免应用程序访问取值为 null 的集合的方法抛出 NullPointerException

关系数据模型:ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 映射 一对多的集合属性 -->
set: 映射 set 类型的属性
name: 设定待映射的持久化类的属性的。
table: set 中的元素对应的记录放在哪一个数据表中。 该值需要和多对一的 多 的那个表的名字一致。
inverse: 指定由哪一方来维护关联关系。通常设置为 true, 来使 1 的一端放弃维护关联关系 以指定由多的一端来维护关联关系。
cascade: 设定级联操作。开发时不建议设定该属性。建议使用手工的方式来处理。
order-by: 在查询时对集合中的元素进行排序, order-by 中使用的是表的字段名, 而不是持久化类的属性名。

<set name="orders" table="ORDERS" inverse="true" order-by="ORDER_NAME DESC">
<!-- 执行多的表中的外键列的名字 -->
<key column="CUSTOMER_ID"></key>
<!-- 指定关联的持久化类的类名 映射类型 -->
<one-to-many class="Order"/>
</set>

<set> 元素的 inverse 属性

  • 在hibernate中通过对 inverse 属性的来决定是由双向关联的哪一方来维护表和表之间的关系。
    • inverse = false 的为主动方维护关联关系,inverse = true 的为被动方维护关联关系
  • 在 1-N 关系中,将 n 方设为主控方将有助于性能改善
    • (如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
  • 在 1-N 关系中,若将 1 方设为主控方
    • 会额外多出 update 语句 插入数据时无法同时插入外键列,因而无法为外键列添加非空约束

插入

order1.setCustomer(customer);
order2.setCustomer(customer);
customer.getOrders().add(order1);
customer.getOrders().add(order2);

  • 执行save 操作: 先插入 Customer, 再插入 Order, 会多出 UPDATE 语句
  • 因为 1 的一端和 n 的一端都维护关联关系。所以会多出 UPDATE

建议设定 set 的 inverse=true, 使 1 放弃维护关系。建议先插入 1 的一端, 后插入多的一端


获取

  • 若查询多的一端的一个对象, 则默认情况下, 只查询了多的一端的对象。 而没有查询关联的的那一端的对象!
  • 在需要使用到关联的对象时, 才发送对应的 SQL 语句 懒加载。
    • 在需要使用集合中元素的时候进行初始化。
    • 若此时 session 已被关闭 会发生 LazyInitializationException 异常。

删除

  • 在不设定级联关系的情况下, 且 1 这端的对象有 n 的对象在引用, 不能直接删除 1 这一端的对象

更新

  • 一切正常

映射一对一关联关系

域模型:



基于外键映射的 1-1 关联

  • 其外键可以存放在任意一边,在需要存放外键一端,增加many-to-one元素。为many-to-one元素增加unique=“true”属性来表示为1-1关联。
1
2
<!-- 使用 many-to-one 的方式来映射 1-1 关联关系 -->
<many-to-one name="mgr" class="Manager" column="MGR_ID" unique="true"></many-to-one>
1
2
3
<!-- 映射 1-1 的关联关系: 在对应的数据表中已经有外键了, 当前持久化类使用 one-to-one 进行映射 -->
<!-- 没有外键的一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段-->
<one-to-one name="dept" class="Department" property-ref="mgr"></one-to-one>

基于主键映射的 1-1 关联

基于主键的映射策略:

  • 指一端的主键生成器使用 foreign 策略,表明根据”对方”的主键来生成自己的主键,自己并不能独立生成主键。
  • <param> 子元素指定使用当前持久化类的哪个属性作为 “对方”

采用foreign主键生成器策略的一端增加 one-to-one 元素映射关联属性,其one-to-one属性还应增加 constrained=“true” 属性;

  • 另一端增加one-to-one元素映射关联属性。
  • constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(“对方”)所对应的数据库表主键
1
2
3
4
5
6
7
8
9
10
<id name="deptId" type="java.lang.Integer">
<column name="DEPT_ID" />
<!-- 使用外键的方式来生成当前的主键 -->
<generator class="foreign">
<!-- property 属性指定使用当前持久化类的哪一个属性的主键作为外键 -->
<param name="property">mgr</param>
</generator>
</id>

<one-to-one name="mgr" class="Manager" constrained="true"></one-to-one>

映射多对多关联关系

单向 n-n

n-n 的关联必须使用连接表

  • 与 1-n 映射类似,必须为 set 集合元素添加 key 子元素,指定 CATEGORIES_ITEMS 表中参照 CATEGORIES 表的外键为 CATEGORIY_ID。
  • 与 1-n 关联映射不同的是,建立 n-n 关联时, 集合中的元素使用 many-to-many。
  • many-to-many 子元素的 class 属性指定 items 集合中存放的是 Item 对象
  • column 属性指定 CATEGORIES_ITEMS 表中参照 ITEMS 表的外键为 ITEM_ID
1
2
3
4
5
6
7
8
9
10
<!-- table: 指定中间表 -->
<class name="Category" table="CATEGORIES">
<set name="items" table="CATEGORIES_ITEMS">
<key>
<column name="C_ID" />
</key>
<!-- 使用 many-to-many 指定多对多的关联关系. column 执行 Set 集合中的持久化类在中间表的外键列的名称 -->
<many-to-many class="Item" column="I_ID"></many-to-many>
</set>
</class>
1
2
3
4
5
6
<class name="hibernate.n2n.Item" table="ITEMS">
<set name="categories" table="CATEGORIES_ITEMS" inverse="true">
<key column="I_ID"></key>
<many-to-many class="hibernate.n2n.Category" column="C_ID"></many-to-many>
</set>
</class>

双向 n-n

双向 n-n 关联需要两端都使用集合属性
双向n-n关联必须使用连接表

  • 集合属性应增加 key 子元素用以映射外键列, 集合元素里还应增加many-to-many子元素关联实体类。
  • 在双向 n-n 关联的两边都需指定连接表的表名及外键列的列名。两个集合元素 set 的 table 元素的值必须指定,而且必须相同。
    • set元素的两个子元素:key 和 many-to-many 都必须指定 column 属性,其中,
    • key 和 many-to-many 分别指定本持久化类和关联类在连接表中的外键列名,因此两边的 key 与 many-to-many 的column属性交叉相同。
    • 也就是说,一边的set元素的key的 cloumn值为a,many-to-many 的 column 为b;
    • 则另一边的 set 元素的 key 的 column 值 b,many-to-many的 column 值为 a.

对于双向 n-n 关联, 必须把其中一端的 inverse 设置为 true, 否则两端都维护关联关系可能会造成主键冲突。

感谢阅读


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !