1 CRM的概述
1.1什么是CRM
CRM(Customer Relationship Management)客户关系管理,是利用相应的信息技术以及互联网技术来协调企业与顾客之间在销售、营销和服务上的交互,向客户提供创新式的个性化的客户交互和服务的过程。其最终目标是将面向客户的各项信息和活动集成起来,组建一个以客户为中心的企业,实现对面向客户的活动的全面管理。
1.2 CRM的功能模块
CRM系统实现了对企业销售、营销、服务等各阶段的客户信息、客户活动进行统一管理。CRM系统功能涵盖企业销售、营销、用户服务等各种业务流程,业务流程中与客户相关活动都会在CRM系统统一管理。CRM的一些基本功能模块:客户信息管理、联系人管理、商机管理、统计分析。
1.3 JavaEE开发三层架构
WEB层:Servlet、JSP、Struts2、Struts1、Webwork、SpringMVC
业务逻辑层:JavaBean、Spring
持久层:JDBC、Hibernate、Mybatis、DbUtils、SpringJDBC模板
企业用框架:
SSH(Struts2+Spring+Hibernate)
SSM(SpringMVC+Spring+Mybatis)
2 Hibernate
2.1 Hibernate概述
Hibernate框架是当今主流的Java持久层框架之一,由于它具有简单易学、灵活性强、扩展性强等特点,能够大大地简化程序的代码量,提高工作效率。
Hibernate(开放源代码的对象关系映射框架)
Hibernate是一个开放源代码的ORM(Object Relational Mapping,对象关系映射)框架,它对JDBC进行了轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,Hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲地使用对象编程思维来操纵数据库。Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架构中取代CMP,完成数据持久化的重任。
Hibernate就是一个持久层的ORM的框架。
ORM:Object Relational Mapping.对象关系映射。
2.2为什么要学习Hibernate
使用传统的JDBC开发应用系统时,如果是小型应用系统,并不觉得有什么麻烦,但是对于大型应用系统的开发,使用JDBC就会显得力不从心。例如对几十、几百张包含几十个字段的表进行插入操作时,编写的SQL语句不但很长,而且繁琐,容易出错;在读取数据时,需要写多条getXxx语句从结果集中取出各个字段的信息,不但枯燥重复,并且工作量非常大。为了提高数据访问层的编程效率,Gavin King开发出了一个当今最流行的ORM框架,它就是Hibernate框架。
所谓的ORM就是利用描述对象和数据库表之间映射的元数据,自动把Java应用程序中的对象,持久化到关系型数据库的表中。通过操作Java对象,就可以完成对数据库表的操作。可以把ORM理解为关系型数据和对象的一个纽带,开发人员只需要关注纽带一端映射的对象即可。ORM原理如下图:
ORM原理
与其它操作数据库的技术相比,Hibernate具有以下几点优势:
(1)Hibernate对JDBC访问数据库的代码做了轻量级封装,大大简化了数据访问层繁琐的重复性代码,并且减少了内存消耗,加快了运行效率。
(2)Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现,它很大程度地简化了DAO(Data Access Object,数据访问对象)层的编码工作。
(3)Hibernate的性能非常好,映射的灵活性很出色。它支持很多关系型数据库,从一对多到多对多的各种复杂关系。
(4)可扩展性强,由于源代码的开源以及API的开放,当本身不够用时,可以自行编码进行扩展。
2.3 Hibernate的入门
下载Hibernate5--->hibernate-release-5.0.7.Final.zip 2016-01-13
Hibernate5.0.7版本下载后,解压完的目录结构如下:
从图可以看出,Hibernate5.0.7的解压目录中包含一系列的子目录,这些子目录分别用于存放不同功能的文件,接下来针对以上子目录进行简单介绍,具体如下:
(1)documentation文件夹:存放Hibernate的相关文档,包括参考文档的API文档。
(2)lib文件夹:存放Hibernate编译和运行所依赖的JAR包。其中required子目录下包含了运行Hibernate5项目中必须的JAR包。
(3)project文件夹:存放Hibernate各种相关的源代码。
2.4开发步骤
(1)导包
导入Hibernate/lib/required/*.jar
导入相应的数据库驱动包
导入日志记录的包
(2)创建实体(持久化类)
持久化类是应用程序中的业务实体类,这里的持久化是指类的对象能够被持久化保存到数据库中。Hibernate使用普通Java对象(Plain Old Java Object),即POJO的编程模式来进行持久化。POJO类中包含的是与数据库表相对应的各个属性,这些属性通过getter和setter方法来访问,对外部隐藏了内部的实现细节。持久化类包含与数据表中字段对应的属性。
(3)创建映射文件
实体类此时还不具备持久化操作的能力,而Hibernate需要知道实体类映射到数据库Hibernate中的哪个表,以及类中的哪个属性对应数据库表中的哪个字段,这些都需要在映射文件中配置。在实体类所在的包中,创建一个名称为实体类名.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>
<!--
class配置类和表的映射
name属性:JavaBean的类全限定名
table属性:表名(表名和类名一致的话,table可以省略)
了解:
catalog属性:数据库名称(写了以这里的为主,就近原则)
-->
<class name="cn.hibernate.customer.domain.Customer" table="cst_customer">
<!--
配置主键字段和属性的映射
column:主键的字段名
name:属性的名称
了解:
length:字段长度(不写就用默认值)
-->
<id column="cust_id" name="cust_id">
<!-- 主键的生成策略 native 本地的策略 -->
<generator class="native"/>
</id>
<!--配置非主键字段和属性的映射
column:主键的字段名(字段名和属性名一样时,column可以省略)
name:属性的名称
了解:
length:字段的长度
type:字段的类型(java类型/数据库类型/hibernate类型(默认的))
not-null:非空(取值true则非空)
unique:唯一(取值false则不唯一)
-->
<property name="cust_name" column="cust_name"/>
<property name="cust_source" column="cust_source"/>
<property name="cust_industry" column="cust_industry"/>
<property name="cust_level" column="cust_level"/>
<property name="cust_phone" column="cust_phone"/>
<property name="cust_mobile" column="cust_mobile"/>
</class>
</hibernate-mapping>
(4)创建Hibernate的核心配置文件
Hibernate的映射文件反映了持久化类和数据库表的映射信息,而Hibernate的配置文件则主要用来配置数据库连接以及Hibernate运行时所需要的各个属性的值。在项目下创建一个名为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>
<!-- 1、必须的配置(数据库的基本信息) -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///day35</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123456</property>
<!-- 2、可选的配置 -->
<!-- 数据库的方言(此处配置的是MySQL) -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 是否展示SQL语句 -->
<property name="hibernate.show_sql">true</property>
<!-- 是否格式化SQL语句 -->
<property name="hibernate.format_sql">true</property>
<!-- 是否由hibernate生成ddl语句 -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 配置C3P0连接池 -->
<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<!--在连接池中可用的数据库连接的最少数目 -->
<property name="c3p0.min_size">5</property>
<!--在连接池中所有数据库连接的最大数目 -->
<property name="c3p0.max_size">20</property>
<!--设定数据库连接的过期时间,以秒为单位,
如果连接池中的某个数据库连接处于空闲状态的时间超过了timeout时间,就会从连接池中清除 -->
<property name="c3p0.timeout">120</property>
<!--每3000秒检查所有连接池中的空闲连接 以秒为单位-->
<property name="c3p0.idle_test_period">3000</property>
<!-- 3、加载映射文件 -->
<mapping resource="cn/hibernate/customer/domain/Customer.hbm.xml"/>
</session-factory>
</hibernate-configuration>
该配置文件设置了数据库连接的相关属性以及其它的一些常用属性,并且通过mapping的resource属性将对象的映射信息加入到了Hibernate的配置文件中。
注意:映射文件通常是一个XML文件,但一般命名为类名.hbm.xml。
Hibernate的配置文件,包含了连接持久层与映射文件所需的基本信息,其配置文件有两种格式,具体如下:
一种是properties属性文件格式的配置文件,它使用键值对的形式存放信息,默认文件名称为hibernate.properties。另一种是XML格式的配置文件,XML配置文件的默认名称为hibernate.cfg.xml。
上述两种格式的配置文件是等价的,具体使用哪个可以自由选择。XML格式的配置文件更易于修改,配置能力更强,当改变底层应用配置时不需要改变和重新编译代码,只修改配置文件的相应属性即可,而properties格式的文件则不具有此优势,因此,在实际开发中,大多数情况会使用XML格式的配置文件。Hibernate.cfg.xml一般放在src文件夹下,发布后,该文件会在项目的WEB-INF/classes路径下。
2.5 Java代码
//1.加载配置文件
Configuration config = new Configuration().configure();
//2.创建SessionFactory 类似 数据源
SessionFactory factory = config.buildSessionFactory();
//3.获取Session 类似 连接
Session session = factory.openSession();
//4.开启事务
Transaction tx = session.beginTransaction();
//5.操作
session.save(对象);
//6.提交事务
tx.commit();
//7.释放资源
session.close();
3 API详解
3.1 Configuration
作用:
(1)加载核心配置文件
A、配置文件有hibernate.properties(不能加载映射文件)
new Configuration();
B、配置文件有hibernate.cfg.xml,放在src目录下(重点掌握)
new Configuration().configure();
C、若配置文件不在src目录下
configure(配置文件的路径);
(2)加载映射文件
config.addResource(resourceName);
(3)创建SessionFactory
buildSessionFactory();
3.2 SessionFactory
session工厂:SessionFactory接口负责初始化Hibernate。底层维护了内置数据源,并负责创建Session对象。
需要注意的是SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就足够。当需要操作多个数据库时,可以为每个数据库指定一个SessionFactory。底层还维护了二级缓存(需要配置才能使用,ehcache,不是Hibernate自带),开发中一般使用redis替代。
抽取工具类,整合c3p0:
(1)导入jar包--->目录\lib\optional\c3p0\*.jar
(2)配置(核心配置文件里加)
<!-- 配置C3P0连接池 -->
<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<!--在连接池中可用的数据库连接的最少数目 -->
<property name="c3p0.min_size">5</property>
<!--在连接池中所有数据库连接的最大数目 -->
<property name="c3p0.max_size">20</property>
<!--设定数据库连接的过期时间,以秒为单位,如果连接池中的某个数据库连接处于空闲状态的时间超过了timeout时间,就会从连接池中清除 -->
<property name="c3p0.timeout">120</property>
<!--每3000秒检查所有连接池中的空闲连接 以秒为单位-->
<property name="c3p0.idle_test_period">3000</property>
3.3 Session
类似于Connection
作用:和数据库交互,不是线程安全的。
相关API如下:
Serializable save(Object obj):保存 返回id
T get(Class clazz,Serializable id):通过id获取一个对象
T load(Class clazz,Serializable id):通过id获取一个对象
get和load的区别:
get:立即发送sql语句 返回值:customer对象
load:不会立即发送sql语句,需要非id属性的时候才发送sql语句 返回值:customer子类代理对象 (lazy 懒加载)
update(Object obj):修改
delete(Object obj):删除
注意:先查询再进行修改或者删除操作
3.4 Transaction
事务提交:commit()
事务回滚:rollback()
4 Hibernate持久化类的编写规则
4.1什么是持久化类
Hibernate是持久层的ORM映射框架,专注于数据的持久化工作。所谓持久化,就是将内存中的数据永久存储到关系型数据库中。其实所谓的持久化类指的是一个Java类与数据库表建立了映射关系,那么这个类称为持久化类。可以简单地理解为持久化类就是Java类有了一个映射文件与数据库的表建立了关系。
4.2持久化类的编写规则
(1)持久化类需要提供无参数的构造方法。因为在Hibernate的底层需要使用反射生成类的实例。
(2)持久化类的属性需要私有,对私有的属性提供公有的get和set方法。因为在Hibernate底层会将查询到的数据进行封装。
(3)持久化类的属性要尽量使用包装类的类型。因为包装类和基本数据类型的默认值不同,包装类的类型语义描述更清晰,而基本数据类型不容易描述。(以员工工资为0为例)
(4)持久化类要有一个唯一标识OID与表的主键对应。因为Hibernate中需要通过这个唯一标识OID区分在内存中是否是同一个持久化类。在Java中通过地址区分是否是同一个对象的,在关系型数据库的表中是通过主键区分是否是同一条记录。那么Hibernate就是通过这个OID来进行区分的。Hibernate是不允许在内存中出现两个OID相同的持久化对象的。
(5)持久化类尽量不要使用final进行修饰。因为Hibernate中有延迟加载的机制,这个机制中会产生代理对象,Hibernate产生代理对象使用的是字节码的增强技术完成的,其实就是产生了当前类的一个子类对象实现的。如果使用了final修饰了持久化类,那么就不能产生子类,从而不会产生代理对象,那么Hibernate的延迟加载策略(是一种优化手段)就会失效。
注意:在持久类中有一个唯一标识OID与表的主键去建立映射关系。主键一般不会手动录入,一般是由程序生成。
5 Hibernate主键生成策略
5.1主键的类型
自然主键:把具有业务含义的字段作为主键,称之为主键。其前提条件是不能为空且唯一,并且不允许修改。自然主键是有意义的字段,不建议使用它作为表的主键。
代理主键:把不具备业务含义的字段作为主键,称之为代理主键。该字段一般取名为ID,通常为整数类型,因为整数类型比字符串类型要节省更多的数据库空间。
5.2主键的生成策略
Hibernate中,提供了几个内置的主键生成策略,其常用主键生成策略的名称和描述如下表:
名称 | 描述 |
increment | 用于long、short或int类型,由Hibernate自动以递增的方式生成唯一标识符,每次增量为1。只有当没有其它进程向同一张表中插入数据时才可以使用,不能在集群环境下使用。适用于代理主键。(只适用于单线程) |
identity | 采用底层数据库本身提供的主键生成标识符,条件是数据库支持自动增长数据类型。在DB2、MySQL、MS SQL Server、Sybase和HypersonicSQL数据库中可以使用该生成器,该生成器要求在数据库中把主键定义成自增长类型。适用于代理主键。(MySql) |
sequence | Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列。适用于代理主键。(Oracle) |
native | 根据底层数据库对自动生成标识符的能力来选择identity、sequence、hilo三种生成器中的一种,适合跨数据库平台开发。适用于代理主键。(根据本地数据库类型判断使用哪一种) |
uuid | Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,其UUID被编码为一个长度为32位的十六进制字符串。这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据空间。适用于代理主键。 |
assigned | 由Java程序负责生成标识符,如果不指定ID元素的generator属性,则默认使用该主键生成策略。适用于自然主键。 |
6 Hibernate的持久化对象的三种状态
6.1持久化对象三种状态的概述
Hibernate为了更好地来管理持久化类,特将持久化类的对象分成了瞬时态、持久态、脱管态三种状态。一个持久化类的实例可能处于三种状态中的某一种。
(1)瞬时态(transient)
瞬时态也称为临时态或者自由态,瞬时态的实例是由new命令创建、开辟内存空间的对象,不存在持久化标识OID(相当于主键值),尚未与Hibernate Session关联,在数据库中也没有记录,失去引用后将被JVM回收。瞬时状态的对象在内存中是孤立存在的,与数据库中的数据无任何关联,仅是一个信息携带的载体。
(2)持久态(persistent)
持久态的对象存在持久化标识OID,加入到了Session缓存中,并且相关联的Session没有关闭,在数据库中有对应的记录,每条记录只对应唯一的持久化对象,需要注意的是,持久化对象是在事务还未提交前变成持久态的。
(3)脱管态(detached)
脱管态也称为离线态或者游离态,当某个持久化状态的实例与Session的关联被关闭时就变成了脱管态。脱管态对象存在持久化标识OID,并且仍然与数据库中的数据存在关联,只是失去了与当前Session的关联,脱管状态对象发生改变时Hibernate不能检测到。
6.2区分对象的三种状态
瞬时态:没有OID,也没有被session管理
持久态:有OID,且被session管理
脱管态:有OID,没有被session管理
6.3Hibernate持久化对象的三种状态转换
(1)瞬时态转换到其他状态
瞬时态的对象创建由new关键字创建。
瞬时态--->持久态:执行Session的save()或saveOrUpdate()方法
瞬时态--->脱管态:可以认为是为瞬时态对象设置持久化标识OID
(2)持久态转换到其他状态
持久化对象由Session的get()、load()方法,或者Query查询从数据库中获得。
持久态--->瞬时态:可以认为是执行Session的delete()方法
持久态--->脱管态:执行Session的evict()、close()、clear()方法。
(3)脱管态转换到其他状态
可以认为是通过创建一个对象并且设置OID获得。
脱管态--->持久态:执行Session的update()、saveOrUpdate()或lock()方法。
脱管态--->瞬时态:可以认为是将对象的OID设置为null。
6.4 Hibernate的一级缓存
持久化对象能够自动更新数据库是因为依赖了Hibernate的一级缓存。
缓存是计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存。
Hibernate的缓存分为一级缓存和二级缓存,Hibernate的这两级缓存都位于持久化层,存储的都是数据库数据的备份。其中第一级缓存为Hibernate的内置缓存,不能被卸载。
6.4.1什么是Hibernate的一级缓存
Hibernate的一级缓存就是指Session缓存,Session缓存是一块内存空间,用来存放相互管理的java对象,在使用Hibernate查询对象的时候,首先会使用对象属性的OID值在Hibernate的一级缓存中进行查找,如果找到匹配OID值的对象,就直接将该对象从一级缓存中取出使用,不会再查询数据库;如果没有找到相同的OID值的对象,则会去数据库中查找相应数据。当从数据库中查询到所需数据时,该数据信息也会放置到一级缓存中。Hibernate的一级缓存的作用就是减少对数据库的访问次数。
在Session接口的实现中包含了一系列的Java集合,这些Java集合构成了Session缓存。只要Session实例没有结束生命周期,存放在它缓存中的对象也不会结束生命周期。所以一级缓存也被称为Session基本的缓存。
6.4.2 Hibernate的一级缓存的特点
当应用程序调用Session接口的save()、update()、saveOrUpdate()时,如果Session缓存中没有相应的对象,Hibernate就会自动把从数据库查询到的相应对象信息加入到一级缓存中去。
当调用Session接口的load()、get()方法,以及Query接口的list()、iterator()方法时,会判断缓存中是否存在该对象,有则返回,不会查询数据库,如果缓存中没有要查询的对象,再去数据库中查询对应对象,并添加到一级缓存中。
当调用Session的close()方法时,Session缓存会被清空。
6.5一级缓存的内部结构(快照区)
一级缓存之所以可以去更新数据库,其实是因为一级缓存的一块特殊的区域,也就是快照区。
Hibernate向一级缓存放入数据时,同时复制一份数据放入到Hibernate快照中,当使用commit()方法提交事务时,同时会清理Session的一级缓存,这时会使用OID判断一级缓存中的对象和快照中的对象是否一致,如果两个对象中的属性发生变化,则执行update语句,将缓存的内容同步到数据库,并更新快照;如果一致,则不执行update语句。Hibernate快照的作用就是确保一级缓存中的数据和数据库中的数据一致。
7 Hibernate的事务控制
Hibernate是对JDBC的轻量级封装,其主要功能是操作数据库。
7.1什么是事务
在数据库操作中,一项事务(transaction)是由一条或多条操作数据库的SQL语句组成的一个不可分割的工作单元。当事务中的所有操作都正常完成时,整个事务才能被提交到数据库中,如果有一项操作没有完成,则整个事务会被回滚。
其实事务总结起来理解为:逻辑上的一组操作,组成这组操作的各个单元,要么一起成功,要么一起失败。
7.2事务的四个特性
事务有很严格的定义,需要同时满足四个特性,即原子性、一致性、隔离性、持久性。这四个特性通常称之为ACID特性,具体如下:
(1)原子性(Atomic):表示将事务中所做的操作捆绑成一个不可分割的单元,即对事务所进行的数据修改等操作,要么全部执行,要么全都不执行。
(2)一致性(Consistency):表示事务完成时,必须使所有的数据都保持一致状态。
(3)隔离性(Isolation):指一个事务的执行不能被其它事务干扰。即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相干扰。
(4)持久性(Durability):持久性也称为永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。提交后的其它操作或故障不会对其有任何影响。
7.3事务的并发问题
在实际应用过程中,数据库是要被多个用户共同访问的,在多个事务同时使用相同的数据时,可能会发生并发的问题。具体如下:
(1)脏读:一个事务读取到另一个事务未提交的数据。
(2)不可重复读:一个事务读到了另一个事务已经提交的update的数据,导致在同一个事务中的多次查询结果不一致。
(3)虚读/幻读:一个事务读到了另一个事务已经提交的insert数据,导致在同一个事务中的多次查询结果不一致。
7.4事务的隔离级别
读未提交(Read Uncommitted,1级):以上问题都会发生
读已提交(Read Committed,2级):避免脏读(oracle默认)
可重复读(Repeatable Read,4级):避免脏读和不可重复读(mysql默认)
序列化/串行化(Serializable,8级):避免所有问题
隔离级别越高,安全性越高,性能越低,效率越低。
7.5 Hibernate中的事务管理
保证在Service中开启的事务时使用的Session对象和DAO中多个操作使用的是同一个Session对象。有两种方法可以实现:
(1)可以在业务层获取到Session,并将Session作为参数传递给DAO。
(2)可以使用ThreadLocal将业务层获取的Session绑定到当前线程中,然后在DAO中获取Session的时候,都从当前线程中获取。
推荐第二种方式,因为Hibernate的内部已经完成了这个事情。程序人员只需配置即可。
Hibernate5中自身提供了三种管理Session对象的方法:
Session对象的生命周期与本地线程绑定。
Session对象的生命周期与JTA事务绑定。
Hibernate委托程序管理Session对象的生命周期。
在Hibernate的核心配置文件中,hibernate.current_session_context_class 属性用于指定 Session 管理
方式, 可选值包括
thread:Session 对象的生命周期与本地线程绑定
jta:Session 对象的生命周期与 JTA 事务绑定
managed:Hibernate 委托程序来管理 Session 对象的生命周期
在 hibernate.cfg.xml 中进行如下配置:
<!-- 开启与线程绑定的session -->
<property name="hibernate.current_session_context_class">thread</property>
hibernate 提供 sessionFactory.getCurrentSession()创建一个 session 和 ThreadLocal 绑定方法。在 HibernateUtil 工具类中更改 getCurrentSession 方法:
//获取当前线程绑定的会话
public static Session getCurrentSession(){
return factory.getCurrentSession();
}
而且Hibernate中提供的这个与线程绑定的session可以不用关闭,当线程执行结束后,session就会自动关闭了。
8 Hibernate的其它API
Query代表面向对象的一个Hibernate查询操作。在Hibernate中,通常使用session.createQuery()
方法接受一个HQL语句,然后调用Query的list()或 uniqueResult()方法执行查询。所谓的HQL是Hibernate Query Language缩写,其语法很像SQL语法,但它是完全面向对象的。
Query query = session.createQuery("HQL语句");
Query中除了使用list()方法查询全部数据外,还有其它一些常用方法,具体如下:
setter方法:Query接口中提供了一系列的setter方法用于设置查询语句中的参数,针对不同的数据类型,需要用到不同的setter方法。
iterator()方法:该方法用于查询语句,返回的结果是一个 Iterator对象,在读取时只能按照顺序方式读取,它仅把使用到的数据转换成Java实体对象。
uniqueResult()方法:该方法用于返回唯一的结果,在确保只有一条记录的查询时可以使用该方法。
executeUpdate()方法:该方法是Hibernate3的新特性,它支持 HQL语句的更新和删除操作。
setFirstResult()方法:该方法可以设置获取第一个记录的位置,也就是它表示从第几条记录开始查询,默认从0开始计算。
setMaxResult()方法:该方法用于设置结果集的最大记录数,通常与setFirstResult()方法结合使用,用于限制结果集的范围,以实现分页功能。
9 Criteria
Criteria是一个完全面向对象,可扩展的条件查询API,通过它完全不需要考虑数据库底层如何实现,以及SQL语句如何编写,它是Hibernate框架的核心查询对象。Criteria查询,又称为QBC查询,它是Hibernate的另一种对象检索方式。更加面向对象。
org.hibernate.criterion.Criterion是Hibernate提供的一个面向对象查询条件接口,一个单独的查询就是Criterion接口的一个实例,用于限制Criteria对象的查询,在Hibernate中 Criterion对象的创建通常是通过Restrictions工厂类完成的,它提供了条件查询方法。
获取qbc对象:session.createCriteria(Class clazz);
查询所有:list();
查询唯一:uniqueResult();
条件查询:add(Restrictions.xx(属性名称,实际值));
xx取值:like、eq、lt、gt、le、ge、in...
分页查询:setFirstResult(开始索引);
setMaxResults(每页显示的条数);
10 SQLQuery
SQLQuery这个接口用于接收一个sql语句进行查询,然后调用 list()或者uniqueResult()方法进行查询。但是sql语句不会直接封装到实体对象中,需要写代码才可以封装到实体中。
编写sql语句;
获取:session.createSqlQuery("sql语句");
查询所有:list();
封装成指定的实体:addEntity(Class clazz);
11表关系的映射关系
Hibernate框架实现了ORM的思想,将关系数据库中表的数据映射成对象,使开发人员把对数据库的操作转化为对对象的操作,Hibernate的关联关系映射主要包括多表的映射配置、数据的增加、删除等。
数据库中表之间存在着三种关系,也就是系统设计中的三种实体关系。分别是多对多、一对多、一对一的关系。在数据库中,实体表之间的关系映射是采用外键来描述的。
12表与表的三种关系
(1)一对多
建表原则:在多的一方创建外键指向一的一方的主键。
(2)多对多
建表原则:创建一个中间表,中间表中至少两个字段作为外键分别指向多对多双方的主键。
(3)一对一
建表原则有两种:
一种:唯一外键对应,假设一对一中的任意一方为多,在多的一方创建外键指向一的一方的主键,然后将外键设置为唯一。
另一种:主键对应,一方的主键作为另一方的主键。
数据库表能够描述的实体数据之间的关系,通过对象也可以进行描述,所谓的关联映射就是将关联关系映射到数据库里,在对象模型中就是一个或多个引用。在Hibernate中采用Java对象关系来描述数据表之间的关系。
一对一
在各自的类中定义对方类型的对象。
一对多
在一的一方放入了多的一方的集合
在多的一方放入了一的一方的对象
多对多
在各自的类中放入对方的对象
13 Hibernate的一对多关联关系映射
13.1创建表
创建一的一方客户表cst_customer,创建多的一方联系人表 cst_linkman,联系人表中存在外键(lkm_cust_id 即所属客户id),外键指向客户表。
ALTER TABLE cst_linkman ADD CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
数据模型如下:
13.2创建映射
(1)一的一方:
<hibernate-mapping>
<class name="cn.demo.hibernate.domain.Customer" table="cst_customer">
<id name="cust_id">
<generator class="native" />
</id>
<property name="cust_name" length="32" />
<property name="cust_source" column="cust_source" />
<property name="cust_industry" column="cust_industry" />
<property name="cust_level" column="cust_level" />
<property name="cust_phone" column="cust_phone" />
<property name="cust_mobile" column="cust_mobile" />
<!-- 配置关联对象 -->
<!--set 标签: * name 属性:多的一方的集合的属性名称. -->
<set name="linkMans">
<!-- key 标签 : * column 属性:多的一方的外键的名称. -->
<key column="lkm_cust_id"></key>
<!-- one-to-many 标签: * class 属性:多的一方的类全路径 -->
<one-to-many class="cn.demo.hibernate.domain.LinkMan" />
</set>
</class>
</hibernate-mapping>
使用set集合来描述Customer.java类中的属性linkMans。在Hibernate的映射文件中,使用<set>标签用来描述被映射类中的Set集合,<key>标签的column属性值对应文件多的一方的外键名称,在Customer.java客户类中,客户与联系人是一对多的关系,Hibernate的映射文件中,使用<one-to-many>标签来描述持久化类的一对多关联,其中class属性用来描述映射的关联类。
(2)多的一方:
<hibernate-mapping>
<class name="cn.demo.hibernate.domain.LinkMan" table="cst_linkman">
<id name="lkm_id" column="lkm_id">
<generator class="native" />
</id>
<property name="lkm_name" />
<property name="lkm_gender" />
<property name="lkm_phone" />
<property name="lkm_mobile" />
<property name="lkm_email" />
<property name="lkm_qq" />
<property name="lkm_position" />
<property name="lkm_memo" />
<!-- 配置关联对象: -->
<!-- many-to-one:标签.代表多对一. * name :一的一方的对象的名称. * class :一的一方的类的全路径. * column
:表中的外键的名称. -->
<many-to-one name="customer" class="cn.demo.hibernate.domain.Customer"
column="lkm_cust_id" />
</class>
</hibernate-mapping>
<many-to-one>标签定义两个持久化类的关联,这种关联是数据表间的多对一关联,联系人与客户就是多对一的关系,所以用<many-to-one>标签来描述。<many-to-one>标签的name属性用来描述customer在LinkMan.java类中的属性的名称,class属性用来指定映射的类,column属性值对应表中的外键列名。
(3)将映射添加到核心配置文件
<mapping resource="cn/demo/domain/Customer.hbm.xml"/>
<mapping resource="cn/demo/domain/LinkMan.hbm.xml"/>
14一对多的相关操作
级联操作是指当主控方执行保存、更新或者删除操作时,其关联对象(被控方)也执行相同的操作。在映射文件中通过对cascade属性的设置来控制是否对关联对象采用级联操作,级联操作对各种关联关系都是有效的。
14.1级联保存或更新
级联是有方向性的,所谓的方向性指的是:在保存一的一方级联多的一方、在保存多的一方级联一的一方。
(1)保存一的一方
首先要确定要保存的主控方是哪一方,例如要保存客户,所以客户是主控方,那么需要在客户的映射文件中进行如下配置:
<!-- 配置关联关系映射 -->
<!--配置一个set标签:代表一个集合 name属性:集合的属性名称 -->
<set name="linkMans" cascade="save-update">
<!-- 配置一个key标签 column属性:多的一方的外键的名称 -->
<key column="lkm_cust_id"></key>
<!-- 配置一个one-to-many标签 class 属性:多的一方的类全路径 -->
<one-to-many class="cn.demo.hibernate.domain.LinkMan" />
</set>
(2)保存多的一方
同样需要确定主控方,现在的主控方是联系人。所以需要在联系人的映射文件中进行如下配置:
<!-- 关联关系的映射配置:联系人关联客户 -->
<!-- 在多的一方配置many-to-one:代表多对一. name属性:一的一方的对象的名称. class属性:一的一方的类的全路径. column属性:表中的外键的名称. -->
<many-to-one name="customer" cascade="save-update" class="cn.demo.hibernate.domain.Customer" column="lkm_cust_id" />
14.2 Hibernate的级联删除
级联删除也是有方向性的,删除客户同时级联删除联系人,也可以删除联系人同时级联删除客户(这种需求极少)。
原来JDBC中删除客户和联系人的时候,如果有外键的关系是不可以删除的,但是现在使用了Hibernate,其实Hibernate也可以实现这样的功能,但是Hibernate的默认情况是如果客户下还有联系人,Hibernate会将联系人的外键置为null,然后删除客户。但有时还需要在删除客户时,将客户关联的联系人也一并删除,此时需要用到Hibernate的级联删除操作。
(1)删除客户同时删除客户的联系人
确定删除的主控方是客户,所以需要在客户方配置,如果还想有之前的级联保存或更新,则可以进行如下配置:
<!-- 配置关联关系映射 -->
<!--配置一个set标签:代表一个集合 name属性:集合的属性名称 -->
<set name="linkMans" cascade="delete,save-update">
<!-- 配置一个key标签 column属性:多的一方的外键的名称 -->
<key column="lkm_cust_id"></key>
<!-- 配置一个one-to-many标签 class 属性:多的一方的类全路径 -->
<one-to-many class="cn.demo.hibernate.domain.LinkMan" />
</set>
(2)删除联系人时删除客户
联系人是主控方,需要在联系人方做如下配置:
<!-- 关联关系的映射配置:联系人关联客户 -->
<!-- 在多的一方配置many-to-one:代表多对一. name属性:一的一方的对象的名称. class属性:一的一方的类的全路径. column属性:表中的外键的名称. -->
<many-to-one name="customer" cascade="delete,save-update" class="cn.demo.hibernate.domain.Customer" column="lkm_cust_id" />
14.3双向关联会产生多余的SQL语句
由于双向维护了关系,而且持久态对象可以自动更新数据库,更新客户的时候会修改一次外键,更新联系人的时候同样会修改一次外键。这样就产生了多余的SQL语句。
解决办法:需要一的一方放弃外键维护权。也就是说关联关系不需要双方都维护,只需要交给某一方去维护即可。通常交给多的一方去维护,因为多的一方才是维护关系的最好的地方。举个例子,一个老师对应多个学生,一个学生对应一个老师,这是典型的一对多。那么一个老师如果要记住所有学生的名字很难的,但如果让每个学生记住老师的名字应该不难。所以在一对多的关联关系中, 一的一方都会放弃外键的维护权(关系的维护)。配置如下:
<!-- 配置关联关系映射 -->
<!--配置一个set标签:代表一个集合 name属性:集合的属性名称 -->
<set name="linkMans" cascade="delete,save-update" inverse="true">
<!-- 配置一个key标签 column属性:多的一方的外键的名称 -->
<key column="lkm_cust_id"></key>
<!-- 配置一个one-to-many标签 class 属性:多的一方的类全路径 -->
<one-to-many class="cn.demo.hibernate.domain.LinkMan" />
</set>
inverse的默认值是false,代表不放弃外键维护权,配置值为true,代表放弃了外键的维护权。配置之后不会再出现多余的SQL语句。
14.4区分cascade和inverse
如果在set集合上配置cascade="delete,save-update" inverse="true"了,那么执行保存客户的操作,会发现客户和联系人都进入到了数据库,但是客户一方放弃了外键维护权,所以联系人插入到数据库以后是没有外键的。
15 Hibernate的多对多关联关系映射
15.1创建表
创建sys_user、sys_role、sys_user_role三张表
数据模型如下:
15.2创建映射
(1)用户的映射
<?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>
<class name="cn.demo.hibernate.domain.User" table="sys_user">
<id name="user_id">
<generator class="native" />
</id>
<property name="user_code" />
<property name="user_name" />
<property name="user_password" />
<property name="user_state" />
<!-- 关联关系的映射的配置 -->
<!-- set 标签: name :关联的另一方的集合的属性名称. table :中间表的名称 -->
<set name="roles" table="sys_user_role">
<!-- key 标签: column :当前对象在中间表中的外键的名称. -->
<key column="user_id" />
<!-- many-to-many 标签 class :关联另一方的类的全路径. column :关联的另一方在中间表的外键名称 -->
<many-to-many class="cn.demo.hibernate.domain.Role"
column="role_id" />
</set>
</class>
</hibernate-mapping>
(2)角色的映射
<?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>
<class name="cn.demo.hibernate.domain.Role" table="sys_role">
<id name="role_id">
<generator class="native" />
</id>
<property name="role_name" />
<property name="role_memo" />
<set name="users" table="sys_user_role">
<key column="role_id" />
<many-to-many class="cn.demo.hibernate.domain.User"
column="user_id" />
</set>
</class>
</hibernate-mapping>
15.3在核心配置文件中加入映射文件
<!-- 加载映射 -->
<mapping resource="cn/deomo/domain/User.hbm.xml"/>
<mapping resource="cn/demo/domain/Role.hbm.xml"/>
在多对多的保存操作中,如果进行了双向维护关系,就必须有一方放弃外键维护权。一般由被动方放弃,用户主动选择角色,角色是被选择的,所以角色要放弃外键维护权。但如果只进行单向维护关系,就不需要放弃外键维护权了。
16多对多的相关操作
16.1级联保存或更新
(1)保存用户级联角色
主控方是用户,需要在用户方做如下配置:
<!-- 关联关系的映射的配置 -->
<!-- set 标签: name :关联的另一方的集合的属性名称. table :中间表的名称 -->
<set name="roles" table="sys_user_role" cascade="save-update">
<!-- key 标签: column :当前对象在中间表中的外键的名称. -->
<key column="user_id" />
<!-- many-to-many 标签 class :关联另一方的类的全路径. column :关联的另一方在中间表的外键名称 -->
<many-to-many class="cn.demo.hibernate.domain.Role"
column="role_id" />
</set>
(2)保存角色级联用户
保存的主控方是角色,就需要在角色方做如下配置:
<set name="users" table="sys_user_role" cascade="save-update">
<key column="role_id" />
<many-to-many class="cn.demo.hibernate.domain.User"
column="user_id" />
</set>
16.2级联删除
在多对多中级联删除是不会使用的,因为我们不会有这类的需求,比如删除用户,将用户关联的角色一起删除,或者删除角色的时候将
用户删除掉。这是不合理的。但是级联删除的功能 Hibernate 已经提供了该功能。
(1)删除用户级联角色
主控方是用户,需要在用户方做如下配置:
<set name="roles" table="sys_user_role" cascade="delete">
<!-- key 标签: column :当前对象在中间表中的外键的名称. -->
<key column="user_id" />
<!-- many-to-many 标签 class :关联另一方的类的全路径. column :关联的另一方在中间表的外键名称 -->
<many-to-many class="cn.demo.hibernate.domain.Role"
column="role_id" />
</set>
用户被删除了,同时用户所关联的角色也一起被删除了。
(2)删除角色级联删除用户
主控方是角色,需要在角色方做如下配置:
<set name="users" table="sys_user_role" cascade="delete">
<key column="role_id" />
<many-to-many class="cn.demo.hibernate.domain.User"
column="user_id" />
</set>
角色被删除了,同时角色关联的用户也一起被删除了。
16.3多对多的其他操作
多对多的关系主要是靠中间表来维护的,在Hibernate中多对多主要是靠关联的集合来维护的。
17 Hibernate的检索方式
在实际开发中,对数据进行最多的操作就是查询,数据的查询在所有ORM框架中都占有极其重要的地位。
Hibernate的检索方式主要有5种,分别是导航对象图检索方式、OID检索方式、HQL检索方式、QBC 检索方式和 SQL 检索方式。
17.1对象导航图检索
对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。譬如要查找一个联系人对应的客户,就可以由联系人对象自动导航找到联系人所属的客户对象。当然,前提是必须在对象关系映射文件上配置了多对一的关系。其检索方式如下:
LinkMan linkMan = (LinkMan)session.get(LinkMan.class,1l);
Customer customer = linkMan.getCustomer();
17.2 OID检索方式
OID 检索方式主要指用 Session 的 get()和 load()方法加载某条记录对应的对象。如下面两种加载客户对象的方式,就是 OID 检索方式,具体如下:
Customer customer = (Customer )session.get(Customer.class,1);
Customer customer = (Customer )session.load(Customer.class,1);
17.3 HQL检索
HQL(Hibernate Query Language)是面向对象的查询语言,它和SQL查询语言有些相似,但它使用的是类、对象和属性的概念,而没有表和字段的概念。在Hibernate提供的各种检索方式中,HQL是官方推荐的查询语言,也是使用最广泛的一种检索方式。它具有如下功能:
1、在查询语句中设定各种查询条件
2、支持投影查询,即仅检索出对象的部分属性
3、支持分页查询
4、支持分组查询,允许使用 group by 和 having 关键字
5、提供内置聚集函数,如 sum()、min()和 max()
6、能够调用用户定义的 SQL 函数
7、支持子查询,即嵌套查询
8、支持动态绑定参数
Hibernate提供的Query接口是专门的HQL查询接口,它能够执行各种复杂的HQL查询语句。完整的HQL语句结构如下:select…from…where…group by…having…order by…asc/desc
可见HQL查询非常类似于标准SQL查询。通常情况下,当检索数据表中的所有记录时,查询语句中可以省略select关键字,示例如下所示:String hql = "from Customer";
如果执行该查询语句,则会返回应用程序中的所有Customer对象,需要注意的是Customer是类名,而不是表名,类名需要区分大小写,而关键字from不区分大小写。
17.4 QBC检索
QBC(Query By Criteria)是Hibernate提供的另一种检索对象的方式,它主要由Criteria接口、Criterion接口和Expression类组成。Criteria接口是Hibernate API中的一个查询接口,它需要由session进行创建。Criterion是Criteria的查询条件,在Criteria中提供了add(Criterion criterion)方法来添加查询条件。使用QBC检索对象的示例代码,如下所示:查询id为1的Customer对象
//创建criteria对象
Criteria criteria = session.createCriteria(Customer.class);
//设定查询条件
Criterion criterion = Restrictions.eq("id", 1);
//添加查询条件
criteria.add(criterion);
//执行查询,返回查询结果
List<Customer> cs = criteria.list();
QBC检索是使用Restrictions对象编写查询条件的,在Restrictions类中提供了大量的静态方法来创建查询条件。其常用的方法如表所示:
方法名 | 说明 |
Restrictions.eq | 等于 |
Restrictions.allEq | 使用 Map,使用 key/value 进行多个等于的比较 |
Restrictions.gt | 大于> |
Restrictions.ge | 大于等于>= |
Restrictions.lt | 小于 |
Restrictions.le | 小于等于<= |
Restrictions.between | 对应 SQL 的 between 子句 |
Restrictions.like | 对应 SQL 的 like 子句 |
Restrictions.in | 对应 SQL 的 IN 子句 |
Restrictions.and | and 关系 |
Restrictions.or | or 关系 |
Restrictions.sqlRestriction | SQL 限定查询 |
离线条件检索
DetachedCriteria翻译为离线条件查询,因为它是可以脱离Session来使用的一种条件查询对象, Criteria 对象必须由 Session 对象来创建。
也就是说必须先有Session才可以生成Criteria对象。而 DetachedCriteria对象可以在其他层对条件进行封装。
这个对象也是比较有用的,尤其在SSH整合以后这个对象经常会使用。它的主要优点是做一些特别复杂的条件查询的时候,往往会在WEB层向业务层传递很多的参数,业务层又会将这些参数传递给DAO层。最后在DAO中拼接SQL完成查询。有了离线条件查询对象后,那么这些工作都可以不用关心了,可以在WEB层将数据封装好,传递到业务层,再由业务层传递给DAO完成查询。
17.5本地SQL检索方式
采用HQL或QBC检索方式时,Hibernate生成标准的SQL查询语句,适用于所有的数据库平台,因此这两种检索方式都是跨平台的。但有的应用程序可能需要根据底层数据库的SQL方言,来生成一些特殊的查询语句。在这种情况下,可以利用Hibernate提供的SQL检索方式。
18 Hibernate的多表查询
18.1交叉连接
交叉连接返回的结果是被连接的两个表中所有数据行的笛卡尔积,也就是返回第一个表中符合查询条件的数据行数乘以第二个表中符合查询条件的数据行数。
交叉连接的语法格式如下:
SELECT * from 表1 CROSS JOIN 表2;
也可以写为如下格式:
SELECT * from 表1,表2;
交叉连接的结果就是两个表中所有数据的组合。需要注意的是,在
实际开发中这种业务需求是很少见的,一般不会使用交叉连接,而是使用具体的条件对数据进行有目的的查询。
18.2内连接
内连接(INNER JOIN)又称简单连接或自然连接,是一种常见的连接查询。内连接使用比较运算符对两个表中的数据进行比较,并列出与连接条件匹配的数据行,组合成新的记录,也就是说在内连接查询中,只有满足条件的记录才能出现在查询结果中。内连接查询的语法格式如下所示:
SELECT 查询字段 FROM 表1 [INNER] JOIN 表2 ON 表1.关系字段 = 表2.关系字段;
在上述语法格式中,INNER JOIN用于连接两个表,ON来指定连接条件,其中INNER可以省略。内连接其实还可以细分为如下两类:
隐式内连接:顾名思义隐式的就是我们看不到inner join的关键字。而使用where关键字替代。
SELECT * from 表1 ,表2 where 表1.关系字段 = 表2.关系字段;
显式内连接:显示的就是在语句中明显的调用了 inner join 的关键字。
SELECT * from 表1 inner join 表2 on 表1.关系字段 = 表2.关系字段;
SELECT * from 表1 join 表2 on 表1.关系字段 = 表2.关系字段;
18.3外连接
内连接查询中,返回的结果只包含符合查询条件和连接条件的数据,然而有时还需要包含没有关联的数据,即返回查询结果中不仅包含符合条件的数据,而且还包括左表(左连接或左外连接)、右表(右连接或右外连接)或两个表(全外连接)中的所有数据,此时就需要使用外连接查询,外连接分为左连接和右连接。外连接的语法格式如下:
SELECT 所查字段 FROM 表1 LEFT|RIGHT [OUTER] JOIN 表2 ON 表 1.关系字段 = 表2.关系字段 WHERE 条件;
外连接的语法格式和内连接类似,只不过使用的是LEFT JOIN、RIGHT JOIN关键字,其中关键字左边的表被称为左表,关键字右边的表被称为右表。
在使用左连接和右连接查询时,查询结果是不一致的,具体如下:
LEFT JOIN(左连接):返回包括左表中的所有记录和右表中符合连接条件的记录。
SELECT * from 表1 left outer join 表2 on 表1.关系字段 = 表2.关系字段;
SELECT * from A left join 表2 on 表1.关系字段 = 表2.关系字段;
RIGHT JOIN(右连接):返回包括右表中的所有记录和左表中符合连接条件的记录。
SELECT * from 表1 right outer join 表2 on 表1.关系字段 = 表2.关系字段;
SELECT * from A right join 表2 on 表1.关系字段 = 表2.关系字段;
18.4 HQL连接查询
Hibernate 进行多表查询与 SQL 其实是很相似的, 但是 HQL 会在原来 SQL 分类的基础上又多出来一些操作。
HQL 的多表连接查询的分类如下:
(1)交叉连接,(2)内连接(显示内连接、隐式内连接、迫切内连接),
(3)外连接(左外连接、迫切左外连接、右外连接)
SQL 连接查询:
SELECT * FROM cst_customer c INNER JOIN cst_linkman l ON c.cust_id = l.lkm_cust_id;
HQL 连接的查询:
from Customer c inner join c.linkMans
在HQL中,不用写关联字段了,因为客户中的联系人的集合其实对应的就是外键,所以在inner join的后面直接可以写c.linkMans。
迫切内连接:from Customer c inner join fetch c.linkMans
无论是内连接或是迫切内连接发送的底层SQL都是一样的,而且在生成的SQL语句中也没有fetch关键字,当然fetch本身就不是SQL语句的关键字。所以一定要注意,fetch只能在HQL中使用的,生成了 SQL语句以后,fetch就消失了。
fetch的作用:Hibernate发现HQL中有fetch就会将数据封装到一个对象中,把属于客户的数据封装到Customer对象中,将属于联系人的部分封装到Customer中的联系人的集合中,这样最后封装完成以后是一个List<Customer>中。
内连接和迫切内连接的主要区别就在与封装数据。
迫切内连接封装以后会出现重复的数据,因为我们查询到目前有三条记录,就会被封装到三个对象中,其实真正的客户对象只有两个,所以往往在手动编写迫切内连接的时候会使用distinct去掉重复值。
19 Hibernate的抓取策略
19.1什么是抓取策略
抓取策略是当应用程序需要在(Hibernate 实体对象图的)关联关系间进行导航的时候,Hibernate如何获取关联对象的策略。
Hibernate的抓取策略是Hibernate提升性能的一种手段,可以在获取关联对象的时候,对发送的语句进行优化,但是往往抓取策略需要和延迟加载一起使用来提升性能。
19.2延迟加载的分类
延迟加载(lazy load)是(也称懒加载)Hibernate关联关系对象默认的加载方式,延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当真正需要数据的时候,才真正执行数据加载操作。
通常将延迟加载分为两类:一类叫做类级别延迟,另一类叫做关联级别的延迟。类级别的延迟指的是查询某个对象的时候,是否采用有延迟,这个通常在<class>标签上配置lazy属性。关联级别的延迟指的是,查询一个对象的关联对象的时候是否采用延迟加载。这个通常在<set>或<many-to-one>上配置lazy属性。
类级别的延迟加载:
使用load方法检索某个对象的时候,这个类是否采用延迟加载的策略,就是类级别的延迟。类级别的延迟一般在<class>上配置lazy属性,lazy的默认值是true。默认是延迟加载的,所以使用load方法去查询的时候,不会马上发送SQL语句,当真正使用该对象的时候,才会发送SQL语句。
如果不想使用延迟加载也有很多种方法,最简单的就是将这个类的映射文件上的lazy设置为false,当然也可以将这个持久化类改为final修饰,如果改为final修饰的话,就无法生成代理类,就会使延迟加载失效。
这是类级别的延迟加载,类级别的延迟加载一般我们不进行修改,采用默认值lazy=”true”就可以了。主要的是关联级别的延迟加载,关键级别的延迟加载指的是查询到某个对象以后,检索它的关联对象的时候是否采用延迟加载。
关联级别的延迟加载:
关联级别的延迟通常是在<set>和<many-to-one>上来进行配置。
<set>标签上的lazy通常有三个取值:
true:默认值,采用延迟加载
false:检索关联对象的时候,不采用延迟加载
extra:及其懒惰的
<many-to-one>标签上的lazy通常有三个取值:
proxy:默认值,是否采用延迟取决于一的一方类上的 lazy 属性的值
false:检索关联对象的时候,不采用延迟加载
no-proxy:不用研究
fetch 如果设置为 join,lazy 就会失效了
19.3 <set> 集合上的 fetch 和 和 lazy
fetch:控制的是查询其关联对象的时候采用的 SQL 语句的格式
select:默认值. 发送一条 select 语句查询其关联对象
join:发送 一条迫切左外连接查询关联对象
subselect:发送 一条子查询查询其关联对象
lazy:控制的是查询其关联对象的时候是否采用延迟加载的策略
true:默认值. 默认查询联系人的时候采用延迟加载
false:查询关联对象的时候 不采用延迟加载
extra:及其懒惰. 查询关联对象的时候 采用比延迟加载更懒惰的方式进行查询
fetch 主要控制抓取关联对象的时候的发送,SQL 语句的格式的。lazy 主要控制查询其关联对象的时候是否采用延迟加载的。
19.4批量抓取
在抓取的策略中有一种叫做批量抓取,就是同时查询多个对象的关联对象的时候,可以采用批量抓取进行优化。