Hibernate学习笔记-主键生成机制、持久化对象、映射一对多关联关系

  1. 主键生成机制

表示符生成器

描述

Increment

由hibernate自动以递增的方式生成表识符,每次增量为1

Identity

由底层数据库生成表识符。条件是数据库支持自动增长数据类型。

Sequence

Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列。

Native

根据底层数据库对自动生成表示符的能力来选择identity、sequence、hilo

Uuid.hex

Hibernate采用128位的UUID算法来生成标识符。该算法

能够在网络环境中生成唯一的字符串标识符,这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。

如果主键用字符类型,并且不代表任何含义。

assigned

适用于自然主键。由java程序负责生成标识符。不能把setID()方法声明为

Private的。尽量避免使用自然主键。

 

increment 标识符生成器

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

identity 标识符生成器

  1. identity 标识符生成器由底层数据库来负责生成标识符, 它要求底层数据库把主键定义为自动增长字段类型(加1带走)
  2. 适用范围:
    • 由于 identity 生成标识符的机制依赖于底层数据库系统, 因此, 要求底层数据库系统必须支持自动增长字段类型. 支持自动增长字段类型的数据库包括: DB2, Mysql, MSSQLServer, Sybase 等
    • OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常

sequence 标识符生成器

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

native 标识符生成器

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

assigned 标识符生成器

  1. hibernate和底层数据库都不帮助你生成主键,也就是说得自己在程序中手动的设置主键的值。
  2. 适用范围:

       主键有一定的含义,需要根据业务产生的情况。

Uuid标识符生成器

  1. Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间
  2. 使用范围:

       主键是字符串,而且必须是唯一

  1. Myeclipse开发hibernate

开发步骤

  1. 新建一个JavaProject工程  myeclipsehibernate

  1. 添加Hibernate到myeclipsehibernate.
  2. 生成以后的结构如图所示:

  1. 利用hibernate自带的工具自动生成持久化类和映射文件。

 

具体方法在课堂上进行演示。

 

  1. 建立客户端进行编辑

HibernateSessionFactory类

利用ThreadLocal类保证了Session的线程安全。具体内容看代码是怎么样形成的。

  1. 持久化对象的状态

持久化对象有3种状态:

持久化状态

临时状态

游离状态

Session 的特定方法能使对象从一个状态转换到另一个状态

临时对象(transient)

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

持久化对象(也叫”托管”)(Persist)

    • OID 不为 null
    • 位于 Session 缓存中
    • 持久化对象和数据库中的相关记录对应
    • Session 在清理缓存时, 会根据持久化对象的属性变化, 来同步更新数据库
    • 在同一个 Session 实例的缓存中, 数据库表中的每条记录只对应唯一的持久化对象

游离对象(也叫”脱管”)(Detached)

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

Session使用以下方法可以使持久化对象转变成游离对象:

测试hibernate中对象变化的状态:

程序代码

生命周期

状态

tx = session.beginTransaction();

Customer c = new Customer);

开始生命周期

临时状态

Session.save(c)

处于生命周期中

转变为持久化状态

Long id=c.getId();

c = null;

Customer c2 = (Customer)session.load(Customer.class,id);

tx.commit();

处于生命周期中

处于持久化状态

session.close();

处于生命周期中

转变为游离态

c2.getName();

处于生命周期中

处于游离态

c2 = null;

结束生命周期

结束生命周期

 

对象状态转化图

 

 

对象状态的总结

操纵持久化对象-save()

  1. Session 的 save() 方法使一个临时对象转变为持久化对象
  2. Session 的 save() 方法完成以下操作:
  3. 把 News 对象加入到 Session 缓存中, 使它进入持久化状态
  4. 选用映射文件指定的标识符生成器, 为持久化对象分配唯一的 OID. 在使用代理主键的情况下, setId() 方法为 News 对象设置 OID 使无效的.
  5. 计划执行一条 insert 语句,把Customer对象当前的属性值组装到insert语句中
  6. Hibernate 通过持久化对象的 OID 来维持它和数据库相关记录的对应关系. 当 News 对象处于持久化状态时, 不允许程序随意修改它的 ID

操纵持久化对象-update()

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

操纵持久化对象-saveOrupdate()

saveOrUpdate:

  该方法同时包含save和update方法,如果参数是临时对象就用save方

   法,如果是游离对象就用update方法,如果是持久化对象就直接返回。

如果参数是临时对象就用save方法

如果是游离对象就用update方法

如果是持久化对象就直接返回,不执行操作

 

  1. 映射一对多关联关系

6.1单向关联

仅仅建立从Order到Customer的多对一关联,即仅仅在Order类中定义customer属性。或者仅仅建立从Customer到Order的一对多关联,即仅仅Customer类中定义orders集合。

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

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

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

Hibernate 使用 <many-to-one> 元素来映射多对一关联关系

6.1.1保存操作

实验1、

先保存订单,再保存客户

从这里可以看出执行了两条insert语句,一条update语句

先保存客户,再保存订单

可以看出这种情况程序是执行了两条insert语句,而没有执行update语句。

查询订单

先保存客户,再保存订单,在下面的代码中注释掉session.save(c),会有什么后果

级联保存和更新

当hibernate持久化一个临时对象时,在默认情况下,他不会自动持久化所关联的其他临时对象,会抛出TransientObjectException.如果设定many-to-one元素的cascade属性为save-update的话,可实现自动持久化所关联的对象。

 

6.2双向关联

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

双向 1-n 需要在 1 的一端可以访问 n 的一端, 反之依然.

域模型:

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

关系数据模型:

ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键

建立一对多的双向关联关系

Hibernate使用set元素来映射一对多关联关系

保存客户和订单(客户和订单建立双向关联)

保存客户和不保存订单

在下面的代码中注释掉session.save(order1),会有什么后果

级联保存和更新

当hibernate持久化一个临时对象时,在默认情况下,他不会自动持久化所关联的其他临时对象,会抛出TransientObjectException.如果设定set元素的cascade属性为save-update的话,可实现自动持久化所关联的对象。

查询客户和订单

查询客户和订单(图)

保存客户或订单的区别

保存订单时会发出两条insert语句。

Hibernate: select max(id) from customers

Hibernate: insert into customers (name, id) values (?, ?)

Hibernate: insert into orders (order_number, price, customer_id) values (?, ?, ?)

而保存客户时会发出两条insert语句和一条update语句

Hibernate: select max(id) from customers

Hibernate: insert into customers (name, id) values (?, ?)

Hibernate: insert into orders (order_number, price, customer_id) values (?, ?, ?)

Hibernate: update orders set customer_id=? where id=?

因为订单是多的一方,而客户是一的一方,所以在多对一的关系中,应该使用一的一方进行操作,这样效率更高。

订单变更客户

上述例子产生了两条update语句:

Hibernate: update orders set order_number=?, price=?, customer_id=? where id=?

Hibernate: update orders set customer_id=? where id=?

原因为:

Hibernate会自动清理缓存中的所有持久化对象,按照持久化对象的改变来同步更新数据库,因此执行了上述的两条更新语句所以会产生两条update语句

c4.getOrders().add(o6)执行了一条update语句

o6.setCustomer(c4)执行了一条update语句

级联删除

inverse属性

Inverse来源

在hibernate中通过对 inverse 属性的值决定是由双向关联的哪一方来维护表和表之间的关系. inverse=false 的为主动方,inverse=true 的为被动方, 由主动方负责维护关联关系

Inverse设值

在没有设置 inverse=true 的情况下,父子两边都维护父子关系

Inverse设值原则

在 1-n 关系中,将 n 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)

在 1-N 关系中,若将 1 方设为主控方 会额外多出 update 语句

在一的一方设值inverse为TRUE表明一的一方不维护其关系,这样就会发出一条update语句,这样效率也就提高了。

Inverse结论

1.在映射一对多的双向关联关系时,应该在one方把inverse属性设为true,

  这可以提高性能。

2.在建立两个对象的关联时,应该同时修改关联两端的相应属性:

   customer.getOrders().add(order);

   order.setCustomer(customer);

这样才会使程序更加健壮,提高业务逻辑层的独立性,使业务逻辑层的程序代码

不受Hibernate实现类的影响。同理,当删除双向关联的关系时,也应该修改

关联两端的对象的相应属性:

Customer.getOrders().remove(order);

Order.setCustomer(null);

解除关联关系

解除某个订单与某个客户的关系

这样在order表中,ID为6的相应的外键为3的那行的外键会置为null;

级联删除

 

 

 

cascade属性

 

在数据库中对集合进行排序

<set> 元素有一个 order-by 属性, 如果设置了该属性, 当 Hibernate 通过 select 语句到数据库中检索集合对象时, 利用 order by 子句进行排序

 

  1. 映射多对多关联关系

 

  1. 深入Session

session概述

Session 接口是 Hibernate 向应用程序提供的操纵对数据库的最主要的接口, 它提供了基本的保存, 更新, 删除和加载Java 对象的方法.

理解session的缓存

  1. 在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存. 只要 Session 实例没有结束生命周期, 存放在它缓存中的对象也不会结束生命周期
  2.     当session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,只要缓存不清空,该对象仍然处于生命周期中。当试图load()对象时,会判断缓存中是否存在该对象,有则返回。没有在查询数据库

清理session的缓存

  1. Session 具有一个缓存, 位于缓存中的对象称为持久化对象, 它和数据库中的相关记录对应. Session 能够在某些时间点, 按照缓存中对象的变化来执行相关的 SQL 语句, 来同步更新数据库, 这一过程被称为清理缓存(flush)
  2. 默认情况下 Session 在以下时间点清理缓存:
    • 当应用程序调用 Transaction 的 commit()方法的时, 该方法先清理缓存(session.flush()),然后在向数据库提交事务(tx.commit())
    • 当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先清理缓存,以保证查询结果能够反映持久化对象的最新状态
    • 显式调用 Session 的 flush() 方法.
  3. 区别:

     flush: 进行清理缓存(此时缓存中的数据并不丢失)的操作,让缓存和数据库同步 执行一些列sql语句,但不提交事务,;

     commit:先调用flush() 方法,然后提交事务. 则意味着提交事务意味着对数据库操作永久保存下来。

     reresh:刷新,让session和数据库同步,执行查询,把数据库的最新信息显示出来,更新本地缓存的对象状态.

     clear:清空缓存,等价于list.removeAll();

利用Session缓存读取持久化对象的数据

1   Customer c = new Customer(“TOM”,new HashSet());

2   session.save(c); //customer对象被持久化,并且加入到session的缓存中

3   Long id = c.getId();

4   c = null; //c变量不再引用customer对象

5   //从session缓存中读取customer对象,使c2变量引用customer对象

6   Customer c2 = (Customer)session.load(Customer.class,id);

7   tx.commit();  //缓存中的对象和数据库同步

8   session.close(); //关闭session 清空缓存

9   System.out.println(c2.getName()); //访问customer对象

10 C2 = null; //C2对象不再引用customer对象,customer对象结束生命周期

---------------------------------------------------------------------------------------------

缓存的作用:

1。减少访问数据库的频率。

2。保证缓存中的对象与数据库中的相关记录保持同步。

Session执行批量操作

可以写一个for循环,Session可以批量插入上万条数据。如下面的代码:

             For(int i=0;i<10000;i++){

            Session.save(object);

}

这样写的代码会产生一个什么问题?会使一万个对象的缓存全部存在于内存中,这样做加大了内存的压力。所以应该定期清理session的缓存,也就是flush一下,这样内存才能保证足够的空间。

 

 

  1. Session.load与session.get方法

不同场合的不同解决方案

       场合一:当用户要取数据库的一张表的一个字段,这个字段很可能就是一个字符,总而言之长度是比较短的。

       场合二:当用户要取数据库的一张表的一个字段的值,而这个值很可能是blob类型,也许存取的是一个很大的视频文件。

        两种场合的取数据的方法一样吗?是用load还是用get方法?

延迟加载

类的延迟加载

lazy 为true或者为false

集合的延迟加载

True   false   extra

extra为更进一步的延迟加载策略。

当调用getStudents()时不会加载hql语句,当加载student的属性的时候才会发出SQL语句

调用getStudents().size()方法的时候,会触发类似于:Hibernate: select count(id) from T_Student where classid =? 这样的SQL查询语句(这是一种很聪明的做法,如果lazy=”true”,getStudents().size()将会使得hibernate加载所有集合的数据到内存中)。

调用getStudents().contains()方法的时候(即判断是否包含某个对象),会触发类似于:select 1 from T_Student where classid =? and id =? 这样的SQL查询语句。

单端关联

         False   proxy   no-proxy

proxy:当前对象的単值相关对象只有在调用它的主键外的其他属性的get方法时才加载它。

no-proxy:当前对象的単值相关对象只有在调用它的属性时才加载,需要字节码增强。

  1. Hibernate检索策略

初始化数据

类级别检索策略

立即检索

延迟检索

默认的检索策略是立即检索。在Hibernate映射文件中,通过在<class>上配置 lazy属性来确定检索策略。对于Session的检索方式,类级别检索策略仅适用于load方法;也就说,对于get、qurey检索,持久化对象都会被立即加载而不管lazy是false还是true.一般来说,我们检索对象就是要访问它,因此立即检索是通常的选择。由于load方法在检索不到对象时会抛出异常(立即检索的情况下),因此我个人并不建议使用load检索;而由于<class>中的lazy属性还影响到多对一及一对一的检索策略,因此使用load方法就更没必要了。

 

关联级别检索策略

fetch

(默认值select)

Lazy

(默认值是true)

策略

Join

False

采用迫切左外联接检索。

Join

True

采用迫切左外联接检索。

join

Extra

采用迫切左外联接检索。

select

False

采用立即检索

select

True

采用延迟检索

select

Extra

采用延迟检索

c.getOrders().size()  执行 select count(id) from orders where customer_id =?

for(Order o:set){ o.getOrderNumber();} 将执行:

select customer_id , id,order_number ,price from orders  where customer_id=?

subselect

false/true/extra

也分为3中情况

嵌套子查询(检索多个customer对象时) Lazy属性决定检索策略)

select customer_id,order_number,price from orders  where customer_id

         in (select id from customers)

 

检索策略

优点

缺点

优先考虑使用的场合

立即检索

对应用程序完全透明,不管对象处于持久化状态还是游离状态,应用程序都可以从一个对象导航到关联的对象

(1)select语句多

(2)可能会加载应用程序不需要访问的对象,浪费许多内存空间。

(1)类级别

(2)应用程序需要立即访问的对象

(3)使用了二级缓存

延迟检索

由应用程序决定需要加载哪些对象,可以避免执行多余的select语句,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并节省内存空间。

应用程序如果希望访问游离状态的代理类实例,必须保证她在持久化状态时已经被初始化。

(1)一对多或者多对多关联

(2)应用程序不需要立即访问或者根本不会访问的对象

迫切左外连接检索

(1)对应用程序完全透明,不管对象处于持久化状态还是游离状态,都可从一个对象导航到另一个对象。

(2)使用了外连接,select语句少

(1)可能会加载应用程序不需要访问的对象,浪费内存。

(2)复杂的数据库表连接也会影响检索性能。

(1)多对一

(2)需要立即访问的对象

(3)数据库有良好的表连接性能。

 

  1. Hibernate的检索方式

HQL(Hibernate Query Language)

  1. HQL(Hibernate Query Language) 是面向对象的查询语言, 它和 SQL 查询语言有些相似. 在 Hibernate 提供的各种检索方式中, HQL 是使用最广的一种检索方式. 它有如下功能:
    1. 在查询语句中设定各种查询条件
    2. 支持投影查询, 即仅检索出对象的部分属性
    3. 支持分页查询
    4. 支持连接查询
    5. 支持分组查询, 允许使用 HAVING 和 GROUP BY 关键字
    6. 提供内置聚集函数, 如 sum(), min() 和 max()
    7. 能够调用 用户定义的 SQL 函数或标准的 SQL 函数
    8. 支持子查询
    9. 支持动态绑定参数

OID 检索方式

 按照对象的 OID 来检索对象

QBC 检索方式

使用 QBC(Query By Criteria) API 来检索对象. 这种 API 封装了基于字符串形式的查询语句, 提供了更加面向对象的查询接口.

简单的查询

使用别名

 

 

对查询结果排序

分页查询

  1. 分页查询:
  2. setFirstResult(int firstResult): 设定从哪一个对象开始检索, 参数 firstResult 表示这个对象在查询结果中的索引位置, 索引位置的起始值为 0. 默认情况下, Query 从查询结果中的第一个对象开始检索
  3. setMaxResult(int maxResults): 设定一次最多检索出的对象的数目. 在默认情况下, Query 和 Criteria 接口检索出查询结果中所有的对象

检索单个对象

绑定参数的形式,按参数名称绑定

绑定参数的形式,按参数位置绑定

在映射文件中定义命名查询语句

hibernate检索方式

短语

含义

Expression.eq

等于=

Expression.allEq

使用Map,使用key/value进行多个等于的判断

Expression.gt

大于>

Expression.ge

大于等于>=

Expression.lt

小于<

Expression.le

小于等于<=

Expression.between

对应sql的between子句

Expression.like

对应sql的like子句

Expression.in

对应sql的in子句

Expression.and

and 关系

Expression.or

or关系

Expression.sqlRestriction

Sql限定查询

Expression.asc()

根据传入的字段进行升序排序

Expression.desc()

根据传入的字段进行降序排序

HQL和QBC支持的各种运算

运算类型

HQL运算符

QBC运算方法

含义

比较运算

=

Express.eq()

 

<>

Express.not(Express.eq())

 

>=

Express.ge()

 

<

Express.lt()

 

<=

Express.le()

 

is null

Express.isNull()

 

is not null

Express.isNotNull()

 

范围运算符

In

Express.in()

 

not in

Express.not(Express.in())

 

Between

Express.between()

 

not between

Express.not(Express.between())

 

 

HQL和QBC支持的各种运算

运算类型

HQL运算符

QBC运算方法

含义

字符串模式匹配

Like

Expression.like()

 

逻辑

And

Expression.and()|

Expression.conjunction()

 

Or

Expression.or()|

Expression.disjunction()

 

Not

Expression.not()

 
     

迫切左外连接

  1. 二级缓存

缓存的概念

计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存

Hibernate中提供了两个级别的缓存

 Session 级别的缓存

它是属于事务范围的缓存。这一级别的缓存由 hibernate 管理的,一般情况下无需进行干预

SessionFactory 级别的缓存

它是属于进程范围的缓存

 

启用二级缓存的条件

很少被修改

很多系统模块都要用到

不是私有的数据,是共享的

二级缓存的供应商

配置二级缓存

 

 

例子

从二级缓存中取数据

        注:当二级缓存关闭以后会是一个怎么样的情况?

把数据同步到到二级缓存

改变值的时候同步二级缓存

缓存到临时目录

Session.iterate方法

查询缓存

猜你喜欢

转载自blog.csdn.net/a5421901/article/details/88363519