一、Hibernae的学习路线
无
二、Hibernate的概述
框架概述:EE经典架构
EE三层结构(web 层 业务层 持久层)
三、Hibernate的入门
Hibernate是什么?
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的JaveEE架构中取代CMP,完成数据持久化的重任。[引用自百度百科]
总结:Hibernate就是一个持久层的ORM框架,它的存在就是通过建立Java对象和关系型数据库的映射关系以达到简化持久层的设计。
ORM是什么?
Object Relational Mapping (对象关系映射):指的是将一个Java中的对象与关系型数据库的表建立一种映射关系,从而操作对象就可以操作数据库中的表
Hibernate 优点:
1.编写测试类
Hibernate 环境搭建:下载Hibernate5
Hibernate3.x | 旧版 | |
Hibernate4.x | 过渡版本 | |
Hibernate5.x | 【官网下载地址】 | 【-5.0.7-百度云下载地址】【提取码:hgp0】 |
Hibernate 环境搭建:解压Hibernate5
Hibernate 环境搭建:创建第一个项目
步骤:
- 引入jar包
- 创建表
- 创建实体类
- 创建映射(类.hbm.xml)(和类在同一个文件夹下)
- 创建核心配置文件(hibernate.cfg.xml)(在src目录下)
- 编写测试类
分步图示:
1、引入jar包:【jar包百度云:2vyd】
2、创建表(``反引号)
CREATE TABLE `cst_customer`(
`cust_id` BIGINT(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
`cust_name` VARCHAR(32) NOT NULL COMMENT '客户名称(公司名称)',
`cust_source` VARCHAR(32) DEFAULT NULL COMMENT '客户信息来源',
`cust_industry` VARCHAR(32) DEFAULT NULL COMMENT '客户所属行业',
`cust_level` VARCHAR(32) DEFAULT NULL COMMENT '客户级别',
`cust_sphone` VARCHAR(64) DEFAULT NULL COMMENT '固定电话',
`cust_mobile` VARCHAR(16) DEFAULT NULL COMMENT '移动电话',
PRIMARY KEY (`cust_id`)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
3、创建实体类
package com.itheima.domain;
/**
* 客户管理的实体类
* @author 93615
*
*/
public class Customer {
private long cust_id;
private String cust_name;
private String cust_source;
private String cust_industry;
private String cust_level;
private String cust_phone;
private String cust_mobile;
--省略get/set--
}
4、创建映射文件( “类.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="com.itheima.domain.Customer" table="cst_customer">
<!-- 建立类中的属性与表中主键对应 -->
<id name="cust_id" column="cust_id">
<generator class="native"/>
</id>
<!-- 建立类中的普通的属性和表的字段的对应 -->
<property name="cust_name" column="cust_name" length="32" type="java.lang.String"/>
<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>
错误描述:org.hibernate.boot.InvalidMappingException: Could not parse mapping document: com/itheima/hibernate/demo1/Customer.hbm.xml (RESOURCE) 。
问题所在:少了个反斜杠。
错误分析:错误提示只能定位到这个文件一直知道是映射不成功,看映射地址和类名、表名都没问题。突然看到少个反斜杠一试就没有Error了(错误提示中Caused by 会给一个坐标 一定程度上会给一些帮助 去纠错) 。
5、创建核心配置文件(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="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernate01</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<!-- 配置Hibernate的方言 -->
<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>
<!-- 自动创建表 -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 加载ROM文件 -->
<mapping resource="com/itheima/domain/Customer.hbm.xml"/>
</session-factory>
</hibernate-configuration>
6、编写测试类
package com.itheima.domain;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.Test;
public class HibernateDemo01 {
@Test
public void demo01(){
//1、加载Hibernate的核心配置文件
Configuration configuration = new Configuration().configure();
//2、创建一个SessionFactory对象:类似于JDBC的连接池
SessionFactory sessionFactory = configuration.buildSessionFactory();
//3、通过SessionFactory创建一个Session对象:类似于JDBC的Connection
Session session = sessionFactory.openSession();
//4、手动开启事务
Transaction transaction = session.beginTransaction();
//5、编写代码
Customer customer = new Customer();
customer.setCust_name("张勇帅");
session.save(customer);
//6、关闭事务
transaction.commit();
//7、资源释放
session.close();
}
}
以上就是一个Hibernate的一个小例子
四、Hibernate的常见配置
1.XML的提示的配置
hibernate-mapping-3.0.dtd | hibernate-release-5.2.5.Final\project\hibernate-core\src\main\resources\org\hibernate |
hibernate-configuration-3.0.dtd | hibernate-release-5.2.5.Final\project\hibernate-core\src\main\resources\org\hibernate |
hibernate.properties | hibernate-release-5.2.5.Final\project\etc |
2.映射的配置
映射的配置
【class 标签的配置】:标签用来建立类与表的映射关系
- name :类的全路径
- table :表名(类名与表名一致,table可省略)
- catalog :数据库名
【id 标签的配置】:标签用来建立类中属性与表中主键的对应关系
- name :类中的属性名
- column :表中的字段名(属性名和字段名一致。column可省略)
- length :长度
- type :类型(类型分为三种:java hibernate sql)
【property标签的配置】:标签用来建立类中的普通属性与表的字段的对相应关系
- name :类中的属性名
- column :表中的字段名
- length :长度
- type :类型
- not-null :设置非空
- unique :设置为一
3.核心配置
核心的配置
- 核心配置的方式
- 属性文件:hibernate.properties 属性文件不能引入映射文件需编写代码加载映射文件
- XML文件:hibernate.cfg.xml
- 必须的配置
- 连接数据库的基本参数
- 驱动类、url路径、用户名、密码
- 方言
- 可选的配置
- 显示SQL :hibernate.show_sql
- 格式化SQL :hibernate.format_sql
- 自动建表 :hibernate.hbm2ddl.auto
none :不使用hibernate的自动建表
create :如果数据库中已经有表,删除原有表,重新创建,如果没有表,新建表(测试)
creat-drop :如果数据库中已经有表,删除原有表,执行操作,删除这个表。如果没有表,新建一个。使用完了删除该表(测试)
update :如果数据库中有表,使用原有表,如果没有表,创建新表(更新表结构)
validate :如果没有表,不会创建表,只会使用数据库中存在的表(校验映射和表结构)
- 映射的配置:引入映射文件的位置
五、Hibernate的核心API
1.Configuration
Configuration 类的作用是对Hibernate 进行配置,以及对它进行启动。在Hibernate 的启动过程中,Configuration 类的实例首先定位映射文档的位置,读取这些配置,然后创建一个SessionFactory对象。虽然Configuration 类在整个Hibernate 项目中只扮演着一个很小的角色,但它是启动hibernate 时所遇到的第一个对象。
作用:加载核心配置文件
hibernate.properties 属性映射文件方式不能加入映射文件
Configuration cfg = new Configuration();
cfg.addResource("com/itheima/domain/Customer.hbm.xml"); //手动加载映射
作用:手动加载核心配置文件
//如果系统配置文件中的加载ROM文件代码被注销可以选择手动加载映射文件
Configuration cfg = new Configuration().config(); //加载Hibernate的核心配置文件
cfg.addResource("com/itheima/domain/Customer.hbm.xml"); //手动加载映射
2.SessionFactory
SessionFactory接口负责初始化Hibernate。它充当数据存储源的代理,并负责创建Session对象。这里用到了工厂模式。需要注意的是SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就够,当需要操作多个数据库时,可以为每个数据库指定一个SessionFactory。
SessionFactory内部维护了Hibernate的连接池和二级缓存时线程安全的对象【一个项目创建一个对象即可】
抽取工具类:
package com.itheima.domain;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
public static final Configuration cfg;
public static final SessionFactory sf;
static{
cfg=new Configuration().configure();
sf= cfg.buildSessionFactory();
}
public static Session openSession(){
return sf.openSession();
}
}
测试工具类:
@Test
public void demo02(){
Session session = HibernateUtil.openSession();//获取连接对象
Transaction transaction = session.beginTransaction(); //开始事务
//操作保存名字
Customer customer = new Customer();
customer.setCust_name("zhangshan");
session.save(customer);
transaction.commit(); //关闭事务
session.close(); //资源释放
}
3.Session
Session:类似Connection对象是连接对象
Session接口负责执行被持久化对象的CRUD操作(CRUD的任务是完成与数据库的交流,包含了很多常见的SQL语句)。但需要注意的是Session对象是非线程安全的。同时,Hibernate的session不同于JSP应用中的HttpSession。这里当使用session这个术语时,其实指的是Hibernate中的session,而以后会将HttpSession对象称为用户session。
Session代表的是Hibernate与数据库的链接对象。不是线程安全的,与数据库交互桥梁。
Session中的API(ID作为唯一标识)
- 保存方法:
Serializable save(Object obj);
- 查询方法
T get(Class c,Serializable id);
T load(Class c,Serializable id);
get 方法 | load 方法 |
---|---|
采用立即加载,执行这行代码的时候,立即发送sql语句去查询 | 采用延迟加载(lazy懒加载),执行到这行代码时,不会发送sql语句,当真正使用这个对象的时候才会发送sql语句 |
查询后返回的是真实对象本身 | 查询后返回的是代理对象(javassist-3.18.1-GA.jar)利用javassist技术产生的代理 |
查询一个找不到的对象的时候,返回null | 查询一个找不到的对象的时候,返回ObjectNotFoundException |
- 修改方法
void update(Object obj);
直接创建对象进行修改 | 先查询再修改(推荐) |
没有赋值的属性将会根据数据库字段属性被置空 |
|
- 删除方法
void delete(Object obj)
直接创建对象进行删除 | 先查询再删除(推荐) |
| 级联删除 |
- 保存或更新
saveorupdate(Object obj)
- 拓展:查询所有(HQL语言、SQL语言)
4.Transaction
commit()
rollback()
六、Hibernate持久化类的编写规范
- 什么是持久化类?
持久化:将内存中的一个对象持久化到数据库中过程。Hibernate框架就是用来进行持久化的框架。
持久化类:一个Java对象与数据库的表建立了映射关系,那么这个类在Hibernate中称为持久化类。
持久化类=Java类+映射文件
- 持久化类的编写规则
对持久化类提供一个无参数的构造方法:Hibernate底层需要使用反射生成实例
属性需要私有化,对私有属性提供public的get和set方法:Hibernate中获取设置对象的值
对持久化类提供一个唯一标识OID与数据库主键对应:Java中通过对象的地址区分是否是同一对象,数据库中通过主键确定是否是同一个记录,在Hibernate中通过持久化类的OID的属性区分是否是同一个对象。
持久化类中属性尽量使用包装类类型:因为基本数据类型默认是0,那么0就会有很多歧义。包装类类型默认值为null。
持久化类不要使用final进行修饰:延迟加载本身就是hibernate一个优化的手段。返回的是一个代理对象(javassist可以对没有实现接口的的类产生代理--使用了非常底层的字节码增强技术,继承这个类进行代理)。如果不能被继承,不能产生代理对象,延迟加载也就失效。那么load方法和get方法就没有区别了。
七、Hibernate主键生成策略
主键的分类
自然主键:主键的本身就是表中的一个字段(实体中的一个具体的属性)
例如创建一个人员表,人员都会有一个身份证号(唯一的不可重复的),使用了身份证号作为主键,这种主键称为自然主键。(开发中不会使用这种方式)
代理主键:主键的本身不是表中必须的一个字段(不是实体中的某个具体的属性)
例如创建一个人员表,没有使用人员的身份证号,用了一个与这个表不相关的字段ID,(PND).这种主键称为时代理主键。(开发中推荐使用这种方式)
在实际开发中,尽量使用代理主键。一旦自然主键参与到业务逻辑中后期可能需要修改源代码。好的程序设计满足OCP原则,对程序的扩展是open的,对修改源码是close的。
1.生成策略概述
在实际开发中一般是不允许用户手动设置主键,一般将主键交给数据库,手动编写程序进行设置,在Hibernate中提供了很多主键的生成策略
incerment :Hibernate中提供的自动增长机制,适用于使用short、int、long类型的主键,没有使用数据库的自动增长机制。
在单线程程序中使用首先先发送一条语句(select max(id)from 表)然后让ID+1作为下一跳记录的主键(不能在集群环境下或者有并发访问的情况下使用,在查询时,有可能有两个用户几乎同时得到相同的 id,再插入时就有可能主键冲突!)
identity:适用于使用short、int、long类型的主键,使用的是数据库底层的自动增强(auto_increment)机制。
适用于自动增强机制数据库(Mysql、MSSQL)但是Oracle是没有自动增长
sequence :适用于使用short、int、long类型的主键。底层采用的是序列的增长方式。
(Oracle支持序列)像Mysql就不能使用sequence,Oracle 数据库底层没有自动增长,若想自动增长需要使用序列。
uuid :适用于 char,varchar 类型的作为主键。使用hibernate中的随机方式生成字符串主键。
native :本地策略。根据底层的数据库不同,自动选择适用于该种数据库的生成策略(short,int,long)。可以在identity和sequence之间进行自动切换。
assigned :hibernate放弃主键的管理,需要通过手动编写程序或者用户自己设置
foreign : 外部的。一对一的一种关联映射的情况下使用(两个表主键相同)(了解)
2.生成策略演示
略...
八、Hibernate持久化类的三种状态
Hibernate是持久层框架,通过持久化类完成ORM操作。Hibernate为了更好地管理持久化类,将持久化类分成了三种状态。
1.认识三种状态
瞬时态:这种对象没有唯一的标识OID,没有被session管理,称为是瞬时态对象。
持久态:这种对象有唯一的标识OID,被session管理,称为是持久态对象。持久化类的持久态对象可以自动更新数据库。
托管态:这种对象有唯一的标识OID,没有被session管理,称为是托管态对象
package com.itheima.domain; 《区分三种状态对象》
import java.io.Serializable;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.itheima.utils.HibernateUtil;
/**
* Hibernate的持久化类的三种状态
*/
public class HibernateDemo02 {
@Test
public void demo01(){
Session session = HibernateUtil.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();//customer瞬时态对象,没有唯一标识OID,没有被session管理
customer.setCust_name("小仙女");
Serializable id = session.save(customer);//customer持久态对象,有唯一标识OID,被session管理
session.get(Customer.class, id);
transaction.commit();
session.close();
System.out.println("客户名称"+customer.getCust_name());//customer托管态对象,有唯一标识OID,没有被session管理
}
}
2.三种状态的转换
瞬时态对象【OID×session×】 | 持久态对象【OID✓session✓】 | 托管态对象【OID✓session×】 | |
获得 | Customer customer = new Customer() 创建了持久化类的对象,该对象还没有 OID,也和 Session 对象无关,所以是瞬时态。 | get()、load()、 | Customer customer = new Customer() customer.setCust_id(1l) 创建了持久化类的对象,手动设置了 OID,但没有和 Session 对象发生关系,故为托管态。 |
转换 | 瞬时->持久: save(Object obj) saveOrUpdate(Object obj) 对象进入缓存,且自动生成了 OID,故为持久态。 | 持久->瞬时: delete() | 托管->持久: update() saveOrUpdate(Object obj) |
转换 | 瞬时->托管: customer.setCust_id(1l) 手动设置了 OID,但没有和 Session 对象发生关系,故为托管态。 | 持久->托管: close()、clear()、evict(Object obj) Session 对象被销毁,所以持久化类的对象没有被 session 管理,所以为托管态。 | 托管->瞬时: customer.setCust_id(null) |
saveOrUpdate何时save何时update呢是需要看他的对象状态的,如果对象是瞬时态对象就是save如果是托管态对象就是Update |
3.持久态对象特性
持久化类对象的持久态对象可以自动更新数据库
@Test
/**
* 持久态对象自动更新数据库
*/
public void demo02(){
Session session = HibernateUtil.openSession();
Transaction transaction = session.beginTransaction();
//获得持久态对象
Customer customer = session.get(Customer.class, 1l);
customer.setCust_name("love you");
//自动更新数据库 当值不一样时自动更新 值一样时只做查询 原理(底层一级缓存)
transaction.commit();
session.close();
}
九、Hibernate的一级缓存
Hibernate框架中提供了优化手段:缓存、抓取策略。
Hibernate中提供了两种缓存机制机制:一级缓存、二级缓存
1.一级缓存的概述
什么是缓存
缓存:是一种优化的方式,将数据存到内存中,使用的时候直接从缓存中获取,不用通过储存源。可以提升程序的性能!
Hibernate的一级缓存:称为是Session级别的缓存,一级缓存生命周期与Session一致(一级缓存是由Session中的一系列的Java集合构成)一级缓存是自带的不可卸载的。
Hibernate的二级缓存:默认没有开启,需要手动配置才可以使用的。二级缓存可以在多个 session 中共享数据,二级缓存称为是 sessionFactory 级别的缓存。
证明缓存的存在
/**
* hibernate证明一级缓存的测试
* @author 93615
*
*/
public class HibernateDemo03 {
@Test
public void demo01(){
Session session = HibernateUtil.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = session.get(Customer.class, 1l); //立即发送SQL
System.out.println(customer);
Customer customer1 = session.get(Customer.class, 1l); //不发送SQL 从缓存获取数据
System.out.println(customer1);
//以上两步操作由于一级缓存的存在相当于查询一次 打印两次
Customer customer2 = new Customer();
customer2.setCust_name("Java");
Serializable id = session.save(customer2);
Customer customer3 = session.get(Customer.class, id);
//不发送SQL 从缓存获取,当事务提交时才会同步数据如果注销事务提交代码那么虽然会显示该条name=java数据但是不会持久化到数据库证明了一级缓存的存在。
System.out.println(customer3);
transaction.commit();
session.close();
}
}
Session.clear() -- 清空缓存。
Session.evict(Object entity) -- 从一级缓存中清除指定的实体对象。
Session.flush() -- 刷新缓存(比较快照和缓存数据)。
2.一级缓存的快照区
十、Hibernate事务管理
1.事务的回顾
什么是事务
- 事务就是逻辑上的一组操作,组成事务的各个执行单元,操作要么全都成功,要么全都失败。
例如银行存钱输入密码存钱两个操作组成了一个事务!
事务特性
- 原子性 -- 事务不可分割。
- 一致性 -- 事务执行的前后数据的完整性保持一致。
- 隔离性 -- 一个事务执行的过程中,不应该受到其他的事务的干扰。
- 持久性 -- 事务一旦提交,数据就永久保持到数据库中。
如果不考虑隔离性,引发读安全性问题
读问题:
- 脏读 -- 一个事务读到了另一个事务未提交的数据(数据库隔离中最重要的问题)
- 不可重复读 -- 一个事务读到了另一个事务已经提交的 update 数据,导致多次查询结果不一致.
- 虚读 -- 一个事务读到了另一个事务已经提交的 insert 数据,导致多次查询如果不一致.
写问题:
- 引发两类丢失更新
读问题的解决
设置事务的隔离级
- Read uncommitted isolation 未提交读--以上读问题都有可能发生
- Read committed isolation 已提交读:解决脏读,但是不可重复读,虚读都有可能发生.
- Repeatable read isolation 可重复读:解决脏读,不可重复读.但是虚读是有可能发生.
- Serializable isolation 串行化:以上读的情况都可以解决。
设置事务的隔离级
需要在 hibernate.cfg.xml 的配置文件中通过标签来配置
- 通过:<property name="hibernate.connection.isolation">4</property> 来配置
- 取值:
- 1—Read uncommitted isolation
- 2—Read committed isolation
- 4—Repeatable read isolation
- 8—Serializable isolation
2.线程绑定的
Service层事务
- dao层封装的是对数据源的单个操作(查询、修改、保存)方法。(如果将事务封装进Dao层肯定最为方便,但是却不能放在这里。)
- 业务层封装业务逻辑操作。
例如一个转账aaa方法,需要调用Dao1、Dao2才能完成一个业务操作,所以说我们的事务应该放在业务层来保证这个两个单个操作同时成功或同时失败。
当前业务下的Dao操作都会创建链接(connection、Session)怎么保证是同一个session呢?
JDBC中两种方发来进行处理。
一是通过在外部获取一个连接然后向下传递dao操作直接使用传过来的连接。
二是通过使用ThreadLocal(绑定线程的一个对象)对象(图中有步骤)
完成Session线程绑定需要
1、重写工具类进行线程绑定并且该 Session 对象不用手动关闭,线程结束了,会自动关闭。
package com.itheima.utils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/**
* Hibernate工具类
* @author 93615
*
*/
public class HibernateUtil {
public static final Configuration cfg;
public static final SessionFactory sf;
static{//静态代码块
cfg=new Configuration().configure();//加载Hibernate的核心配置文件
sf= cfg.buildSessionFactory();// 创建一个SessionFactory对象:类似于JDBC的连接池
}
public static Session openSession(){//从工厂中获取Session对象:类似于JDBC的Connection
return sf.openSession();
}
public static Session getCurrentSession(){//从ThreadLocal类中获取到session的对象
return sf.getCurrentSession();//该方法提供了Session和线程的绑定
}
}
2、hibernate.cfg.xml配置当前线程绑定的Session
<property name="hibernate.current_session_context_class">thread</property>
十一、Hibernate的其他的API
Query语句示例:
package com.itheima.domain;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Restrictions;
import org.junit.Test;
import com.itheima.utils.HibernateUtil;
public class HibernateDemo04 {
@Test
/**
* Query语句
*/
public void demo(){
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 1.简单查询--查询所有记录
String hql ="from Customer";
Query query = session.createQuery(hql);//通过Session获得Query接口
List <Customer>list = query.list();
for (Customer customer : list) {
System.out.println(customer);
}
// 2.条件查询--查询所有记录中小字开头数据(模糊查询)
String hql = "from Customer where cust_name like ?";
Query query = session.createQuery(hql);
query.setParameter(0,"小%");
List<Customer> list = query.list();
for (Customer customer : list) {
System.out.println(customer);
}
// 3.分页查询
String hql = "from Customer";
Query query = session.createQuery(hql);
//设置分页
query.setFirstResult(0);//起始位置
query.setMaxResults(2);//显示数量
List<Customer> list = query.list();
for (Customer customer : list) {
System.out.println(customer);
}
transaction.commit();
}
}
注意:
setParameter动态绑定有两种方式为参数名字、参数位置。
在 JDBC 中,记录从1开始!在 HQL 中,记录从0开始!
Criteria 查询接口(做条件查询非常合适)
@Test
/**
* Criteria
*/
public void demo04(){
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
//1、查询所有记录
Criteria criteria = session.createCriteria(Customer.class);//通过Session获得Criteria接口
//2、条件查询1
//criteria.add(Restrictions.eq("cust_name", "小可爱"));//eq:等于
//2、条件查询2
//criteria.add(Restrictions.like("cust_name", "小%"));//like:模糊查询
//2、条件查询3
//criteria.add(Restrictions.gt("cust_name", "小%"));//gt:大于
//分页显示
criteria.setFirstResult(0);
criteria.setMaxResults(2);
List<Customer> list = criteria.list();
for (Customer customer : list) {
System.out.println(customer);
}
transaction.commit();
}
十二、Hibernate 关联关系映射
Hibernate表与表之间关系的分析
数据库表与表之间的关系:
一对一关系:一个人只能有一个身份证号,一个身份证号只能被一个人使用。
一对多关系:一个部门对应多个员工,一个员工属于一个部门。一个客户对应多个联系人,一个联系人只能属于某一个客户。
多对多关系:一个学生可以选择多门课程,一门课程也可以被多个学生选择。一个客户可以选择多个角色,一个角色也可以被多个用户选择。
1、Hibernate的一对多关联关系映射
假设客户和联系人是一对多的关系,一个客户对应多个联系人,一个联系人只能属于某一个客户。
创建数据库和表
客户表:
CREATE TABLE `cst_customer` (
`cust_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
`cust_name` varchar(32) NOT NULL COMMENT '客户名称(公司名称)',
`cust_user_id` bigint(32) DEFAULT NULL COMMENT '负责人id',
`cust_create_id` bigint(32) DEFAULT NULL COMMENT '创建人id',
`cust_source` varchar(32) DEFAULT NULL COMMENT '客户信息来源',
`cust_industry` varchar(32) DEFAULT NULL COMMENT '客户所属行业',
`cust_level` varchar(32) DEFAULT NULL COMMENT '客户级别',
`cust_linkman` varchar(64) DEFAULT NULL COMMENT '联系人',
`cust_phone` varchar(64) DEFAULT NULL COMMENT '固定电话',
`cust_mobile` varchar(16) DEFAULT NULL COMMENT '移动电话',
PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
联系人表:
CREATE TABLE `cst_linkman` (
`lkm_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)',
`lkm_name` varchar(16) DEFAULT NULL COMMENT '联系人姓名',
`lkm_cust_id` bigint(32) NOT NULL COMMENT '客户id',
`lkm_gender` char(1) DEFAULT NULL COMMENT '联系人性别',
`lkm_phone` varchar(16) DEFAULT NULL COMMENT '联系人办公电话',
`lkm_mobile` varchar(16) DEFAULT NULL COMMENT '联系人手机',
`lkm_email` varchar(64) DEFAULT NULL COMMENT '联系人邮箱',
`lkm_qq` varchar(16) DEFAULT NULL COMMENT '联系人qq',
`lkm_position` varchar(16) DEFAULT NULL COMMENT '联系人职位',
`lkm_memo` varchar(512) DEFAULT NULL COMMENT '联系人备注',
PRIMARY KEY (`lkm_id`),
KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
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
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
1、Hibernate的一对多关系配置
- 创建实体和映射
创建实体:客户表
public class Customer {
private Long cust_id;
private String cust_name;
private Long cust_user_id;
private Long cust_create_id;
private String cust_source;
private String cust_industry;
private String cust_level;
private String cust_linkman;
private String cust_phone;
private String cust_mobile;
// 注意:在“一”方的 JavaBean 中要添加 set 集合!
//通过ROM方式表示:一个客户对应多个联系人。
//放置的多的一方的集合。Hibernate默认使用的时set集合。
private Set<LinkMan> linkmans = new HashSet<LinkMan>();
//省略get/set方法
}
创建实体:联系人表
public class LinkMan {
private Long lkm_id;
private String lkm_name;
private String lkm_gender;
private String lkm_phone;
private String lkm_mobile;
private String lkm_email;
private String lkm_qq;
private String lkm_position;
private String lkm_memo;
//通过ORM方式表示:一个联系人只能属于某一个客户。
//放置的是一方的对象
// 注意:这里不写外键字段而写 Customer 对象,方便访问客户对象,而且是 Hibernate 要求的!
private Customer customer; // 注意:千万不要 new!
//省略get/set方法
}
创建映射文件:客户(一的一方)
<?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="com.cn.Hibernate.Customer" table="cst_customer">
<!-- 建立OID与主键的映射 -->
<id name="cust_id" column="cust_id">
<generator class="native"/>
</id>
<!-- 建立普通属性与表字段的映射 -->
<property name="cust_name" column="cust_name"/>
<property name="cust_user_id" column="cust_user_id"/>
<property name="cust_create_id" column="cust_create_id"/>
<property name="cust_source" column="cust_source"/>
<property name="cust_industry" column="cust_industry"/>
<property name="cust_level" column="cust_level"/>
<property name="cust_linkman" column="cust_linkman"/>
<property name="cust_phone" column="cust_phone"/>
<property name="cust_mobile" column="cust_mobile"/>
<!-- 配置一对多的映射:放置的多的一方的集合 -->
<!-- set标签name属性:表示多的一方的对象集合的属性名称 -->
<set name="linkmans">
<key column="lkm_cust_id"/>//多的一方的外键的名称
<one-to-many class="com.cn.Hibernate.LinkMan"/>//多的一方的类的全路径
</set>
</class>
</hibernate-mapping>
创建映射文件:联系人
<?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="com.cn.Hibernate.LinkMan" table="cst_linkman">
<!-- 建立OID与主键的映射 -->
<id name="lkm_id" column="lkm_id">
<generator class="native"/>
</id>
<!-- 建立普通属性与表字段的映射 -->
<property name="lkm_name" column="lkm_name"/>
<property name="lkm_gender" column="lkm_gender"/>
<property name="lkm_phone" column="lkm_phone"/>
<property name="lkm_mobile" column="lkm_mobile"/>
<property name="lkm_email" column="lkm_email"/>
<property name="lkm_qq" column="lkm_qq"/>
<property name="lkm_position" column="lkm_position"/>
<property name="lkm_memo" column="lkm_memo"/>
<!-- 配置多对一 关系:放置的是一的一方的对象 -->
<!-- 先配置多方
name 一的一方中的属性名称。
class 一的一方类的全路径
column 在多的一方的表的外键的名称
-->
<many-to-one name="customer" class="com.cn.Hibernate.Customer" column="lkm_cust_id"/>
</class>
</hibernate-mapping>
核心配置文件
<?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="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernate02</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<!-- 配置Hibernate的方言 -->
<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>
<!-- 自动创建表 -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 设置事务隔离级别 -->
<property name="hibernate.connection.isolation">4</property>
<!-- 配置当前线程绑定的Session -->
<property name="hibernate.current_session_context_class">thread</property>
<!-- 加载ROM文件 -->
<mapping resource="com/cn/Hibernate/Customer.hbm.xml"/>
<mapping resource="com/cn/Hibernate/LinkMan.hbm.xml"/>
</session-factory>
</hibernate-configuration>
- 编写测试
@Test
//保存两个客户和三个联系人并且建立好关系
public void demo(){
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
//创建两个用户
Customer customer1 = new Customer();
customer1.setCust_name("客户1");
Customer customer2 = new Customer();
customer2.setCust_name("客户2");
//创建三个联系人
LinkMan linkMan1 = new LinkMan();
linkMan1.setLkm_name("联系人A");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("联系人B");
LinkMan linkMan3 = new LinkMan();
linkMan3.setLkm_name("联系人C");
//建立联系
linkMan1.setCustomer(customer1);
linkMan2.setCustomer(customer1);
linkMan3.setCustomer(customer2);
customer1.getLinkmans().add(linkMan1);
customer1.getLinkmans().add(linkMan2);
customer2.getLinkmans().add(linkMan3);
//保存数据
session.save(linkMan1);
session.save(linkMan2);
session.save(linkMan3);
session.save(customer1);
session.save(customer2);
transaction.commit();
}
@Test
//一对多关系只保存一边是否可以
//报错--object references an unsaved transient instance - save the transient instance before flushing
public void democ(){
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setCust_name("客户1");
LinkMan linkMan = new LinkMan();
linkMan.setLkm_name("联系人A");
customer.getLinkmans().add(linkMan);
linkMan.setCustomer(customer);
session.save(customer);
//session.save(linkman)
transaction.commit();
}
注意事项:
双向关联:如上保存客户的同时保存联系人字段。
(要注意的是:customer 对象一定要最后保存,否则会报错。因为瞬时态对象 customer 持有的是瞬时态 linkman 对象、只有将 linkman 对象转变为持久态,customer 才能转变为持久态。否则报错 )
单向关联:如果只想在客户表新增记录,而联系人表不变,这就叫单向关联。
(要注意的是只保存一边可以吗?当未配置级联保存的时候会报一个瞬时对象异常。)
2、Hibernate的一对多关系操作
级联指的是操作一个对象的时候,是否会同时操作其关联的对象。
级联是有方向性的。
1.级联保存或更新
保存客户级联联系人(一的一方)
在 Customer.hbm.xml 中 set 属性添加 cascade="save-update"
保存一方同时可以把关联的对象也保存到数据库中。
执行 session.save(customer) 结果保存一个客户、级联保存一个联系人。
保存联系人级联客户(多的一方)
在 LinkMan.hbm.xml 中 many-to-one 属性添加 cascade="save-update"
保存一方同时可以把关联的对象也保存到数据库中。
执行 session.save(linkman) 结果保存一个联系人、级联保存一个客户。
客户与联系人互相级联
如果在两个配置文件中都配置
cascade="save-update"
,那么两者互相级联。linkMan1.setCustomer(customer1);
customer1.getLinkmans().add(linkMan2);
session.save(linkMan1);结果客户表新增一条记录,联系人表新增两条记录。
2.级联删除
删除一边的时候,同时将另一方的数据也一并删除。
(假设现在要删除某个客户,在含有外键约束的情况下,是不会成功的)
如果没有设置级联删除执行 session.delete(customer),默认情况是先查询再删除--先修改了联系人的外键=null,然后再删除客户。
删除客户级联删除联系人
在 Customer.hbm.xml 中 set 属性添加 cascade="save-update,delete"
public void demo4(){ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //(先查询在删除):删除客户同时删除联系人 Customer customer = session.get(Customer.class, 1l); session.delete(customer); transaction.commit(); }
删除联系人级联删除客户(基本不用)
在 LinkMan.hbm.xml 中 set 属性添加 cascade="save-update,delete"
public void demo4(){ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); //(先查询在删除):删除联系人同时删除客户 LinkMan linkman = session.get(LinkMan.class, 4l); session.delete(linkman); transaction.commit(); }
同样也可以互相级联删除--实现删除一个联系人从而删除该联系人所属客户下所有的联系人
在实际情况中,级联删除是假删除,即 update 添加一个字段,用该字段来表示这条记录是否删除了,而不是真正的 delete 。
3.inverse的配置
在一对多设置中由于双向关联双方都维护外键,会产生多余的SQL语句。
@Test
/**
* 双向维护关系会产生多余的SQL
* 将2号联系人原来归1号客人,现在改为2号客人
*/
public void demo5(){
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
//查询2号联系人
LinkMan linkMan = session.get(LinkMan.class, 2l);
//查询2号客户
Customer customer = session.get(Customer.class, 2l);
//双向关联
linkMan.setCustomer(customer);
customer.getLinkmans().add(linkMan);
transaction.commit();
}
产生问题的原因:是由于Session 的一级缓存中的快照机制,会让双方都更新数据库。
解决方法:
- 单向维护
- ”一“ 方放弃外键维护权
通常是“一”方来放弃外键的维护,在“一”方的配置文件Customer.hbm.xml 中 set 属性添加 inverse="true"
区分cascade 和 inverse
cascade 用来级联操作(保存、修改和删除)
inverse 用来维护外键
若 Customer 单独配置
cascade="save-update"
inverse="true"
当保存一个 Customer 对象时,数据库会同时保存 Linkman 对象(级联保存),但是该 Linkman 对象的外键为null。当Customer 只配置
inverse="true" LinkMan
配置cascade="save-update" 可以正常使用。
补充:cascade的属性
- none 不使用级联
- save-update 级联保存或更新
- delete 级联删除
- delete-orphan 级联孤儿删除只能用在一对多关系中
- all 除了级联孤儿删除的所有情况
- all-delete-orphan 包含了delete-orphan的所有情况
孤儿删除( 只有在一对多的环境下才有孤儿删除):当解除了父子关系的时候,将子方记录直接删除。
在一对多的关系中,可以将“一”的一方认为是父方,将“多”的一方认为是子方。
可以将配置文件Customer.hbm.xml 中 set 属性添加 cascade="delete-orphan"
若想在解除关系时,把联系人的记录从数据库删除,则应该在客户的配置文件中中这样编写:cascade="delete-orphan"
public void demo7(){ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); Customer customer = session.get(Customer.class, 1l); LinkMan linkMan = session.get(LinkMan.class, 1l); customer.getLinkmans().remove(linkMan); transaction.commit(); }
2、Hibernate的多对多关联关系映射
假设用户与角色是多对多的关系,一个用户可能有多种角色,而某种角色可能被多个用户共享。
创建表-用户表:
CREATE TABLE `sys_user` (
`user_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`user_code` varchar(32) NOT NULL COMMENT '用户账号',
`user_name` varchar(64) NOT NULL COMMENT '用户名称',
`user_password` varchar(32) NOT NULL COMMENT '用户密码',
`user_state` char(1) NOT NULL COMMENT '1:正常,0:暂停',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
创建表-角色表:
CREATE TABLE `sys_role` (
`role_id` bigint(32) NOT NULL AUTO_INCREMENT,
`role_name` varchar(32) NOT NULL COMMENT '角色名称',
`role_memo` varchar(128) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
1、Hibernate的多对多关系配置
- 创建实体和映射
创建实体:用户表
public class User {
private long user_id;
private String user_code;
private String user_name;
private String user_password;
private String user_state;
//设置多对多关系:表示一个用户选择多个角色?
//放置的是角色的集合
private Set<Role>roles = new HashSet<Role>();
//省略get/set方法
}
创建实体:角色表
public class Role {
private long role_id;
private String role_name;
private String role_memo;
//一个角色被多个用户选择;
//放置的是用户的集合
private Set<User>users = new HashSet<User>();
//省略get/set方法
}
创建实体:中间表
在 Hibernate 中,不需要手动创建中间表,只需要按照 Hibernate 的规范编写 JavaBean 和其对应的配置文件即可。
创建映射文件:用户
<?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="com.cn.Hibernate.User" table="sys_user">
<!-- 建立OID与主键的映射 -->
<id name="user_id" column="user_id">
<generator class="native"/>
</id>
<!-- 建立普通属性与表字段的映射 -->
<property name="user_code" column="user_code"/>
<property name="user_name" column="user_name"/>
<property name="user_password" column="user_password"/>
<property name="user_state" column="user_state"/>
<!-- 建立与角色的多对多的映射关系 -->
<!--
set 标签
*name:对方集合名称
*table:中间表名称
key 标签
*column:当前的对象对应中间表的外键的名称
many-to-many 标签
*class :对方的类的全路径
*column:对方的对象在中间表中的外键的名称
-->
<set name="roles" table="sys_user_role">
<key column="user_id"/>
<many-to-many class="com.cn.Hibernate.Role" column="role_id"/>
</set>
</class>
</hibernate-mapping>
创建映射文件:角色
在多对多进行双向关联的时候:必须有一方(被动方放弃外键维护权)去放弃外键维护权,如果不放弃中间表会被更新两次,在set中加入inverse="true"!
<?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="com.cn.Hibernate.Role" table="sys_role">
<!-- 建立OID与主键的映射 -->
<id name="role_id" column="role_id">
<generator class="native"/>
</id>
<!-- 建立普通属性与表字段的映射 -->
<property name="role_name" column="role_name"/>
<property name="role_memo" column="role_memo"/>
<set name="users" table="sys_user_role" inverse="true">
<key column="role_id"/>
<many-to-many class="com.cn.Hibernate.User" column="user_id"/>
</set>
</class>
</hibernate-mapping>
核心配置文件
<?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="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernate02</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<!-- 配置Hibernate的方言 -->
<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>
<!-- 自动创建表 -->
<property name="hibernate.hbm2ddl.auto">create</property>
<!-- 设置事务隔离级别 -->
<property name="hibernate.connection.isolation">4</property>
<!-- 配置当前线程绑定的Session -->
<property name="hibernate.current_session_context_class">thread</property>
<!-- 加载ROM文件 -->
<mapping resource="com/cn/Hibernate/Customer.hbm.xml"/>
<mapping resource="com/cn/Hibernate/LinkMan.hbm.xml"/>
<mapping resource="com/cn/Hibernate/User.hbm.xml"/>
<mapping resource="com/cn/Hibernate/Role.hbm.xml"/>
</session-factory>
</hibernate-configuration>
- 编写测试
public class Test2 {
@Test
public void demo(){
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
User u1 = new User();
u1.setUser_name("张三");
User u2 = new User();
u2.setUser_name("李四");
Role r1 = new Role();
r1.setRole_name("CFA");
Role r2 = new Role();
r2.setRole_name("CPA");
Role r3 = new Role();
r3.setRole_name("CMA");
u1.getRoles().add(r1);
u1.getRoles().add(r2);
u2.getRoles().add(r2);
u2.getRoles().add(r3);
r1.getUsers().add(u1);
r2.getUsers().add(u1);
r2.getUsers().add(u2);
r3.getUsers().add(u2);
session.save(u1);
session.save(u2);
session.save(r1);
session.save(r2);
session.save(r3);
transaction.commit();
}
}
注意事项:
多对多操作只保存一边的话会出现瞬时对象异常。
2、Hibernate的多对多关系操作
1.级联保存
保存用户级联角色
在 User.hbm.xml 中 set 属性添加 cascade="save-update"
保存角色级联用户
在 Role.hbm.xml 中 many-to-one 属性添加 cascade="save-update"
客户与联系人互相级联
如果在两个配置文件中都配置
cascade="save-update"
,那么两者互相级联。
2.级联删除(基本不用)
3.其他操作
给用户添加角色
@Test
public void demo(){
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
//添加角色
User user = session.get(User.class, 1l);
Role role = session.get(Role.class, 3l);
user.getRoles().add(role);
session.save(user);
transaction.commit();
}
更改用户角色
@Test
public void demo(){
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
//更改角色
User user2 = session.get(User.class, 2l);
Role role3 = session.get(Role.class, 3l);
Role role2 = session.get(Role.class, 2l);
user2.getRoles().remove(role2);
user2.getRoles().add(role3);
session.save(user2);
transaction.commit();
}
十七、Hibernate的查询方式
Hibernate的查询方式:OID检索
OID检索:Hibernate根据唯一标识 OID(主键) 的检索方式。
get方法:Customer customer = session.get(Customer.class, 1l);
load方法:Customer customer = session.get(Customer.class, 1l);
Hibernate的查询方式:对象导航检索
对象导航检索:Hibernate根据一个已经查询到的对象,获得其关联的对象的一种查询方式。
LinkMan linkman = session.get(LinkMan.class,1l);
Customer customer = linkman.getCustomer();
Customer customer = session.get(Customer.class,1l);
Set <LinkMan> linkMans = customer.getLinkmans();
Hibernate的查询方式:HQL检索
HQL查询:Hibernate Query Language -- Hibernate 的查询语言,是一种面象对象的方式的查询语言,语法类似SQL,通过session.createQuery() 用于接收一个HQL进行查询方式。
HQL查询:简单查询
- SQL中支持*号的写法但是在HQL中不支持。
- HQL 查询语句中的主体是域模型中的类及类的属性
- HQL 查询语句支持方法链的编程,即直接调用
list()
方法List <Customer>list = session.createQuery("from Customer").list();
@Test
public void demo01(){
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
//简单查询
Query query = session.createQuery("from Customer");
List <Customer>list = query.list();
for (Customer customer : list) {
System.out.println(customer);
}
transaction.commit();
}
HQL查询:别名查询
session.createQuery("from Customer c")
session.createQuery("select c from Customer c")
HQL查询:排序查询
//升序
List <Customer>list = session.createQuery("from Customer order by cust_id").list();
//降序
List <Customer>list = session.createQuery("from Customer order by cust_id desc").list();
HQL查询:条件查询
- 按位置绑定:根据参数的位置
- 一个条件
- 多个条件
- 按名称绑定:根据参数的名称
//一个条件
Query query = session.createQuery("from Customer where cust_name = ?");
query.setParameter(0, "张三");
//多个条件
Query query = session.createQuery("from Customer where cust_name like ? and cust_source = ?");
query.setParameter(0, "张%");
query.setParameter(1, "推荐");
//按名称查询
Query query = session.createQuery("from Customer where cust_name like :a and cust_source = :b");
query.setParameter("a", "张%");
query.setParameter("b", "推荐");
HQL查询:投影查询
- 查询对象的某个或某些属性
//单个属性
List <Object>list = session.createQuery("select c.cust_name from Customer c").list();
//多个属性
List <Object[]>list = session.createQuery("select c.cust_name,c.cust_source from Customer c").list();
//查询多个属性,并封装到对象中需要先在持久化类中提供对应字段的构造方法
public Customer() {
super();
// TODO Auto-generated constructor stub
}
public Customer(String cust_name, String cust_source) {
super();
this.cust_name = cust_name;
this.cust_source = cust_source;
}
List <Customer>list = session.createQuery("select new Customer(cust_name,cust_source) from Customer").list();
HQL查询:分页查询
- Hibernate 框架提供了分页的方法,可以调用方法来完成分页
public void demo10(){
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
Query query = session.createQuery("from LinkMan");
query.setFirstResult(5);
query.setMaxResults(5);
List <LinkMan>list = query.list();
for (LinkMan linkMan : list) {
System.out.println(linkMan);
}
transaction.commit();
}
HQL查询:分组统计查询
- 聚合函数的使用:count(),avg(),max(),min(),sum(),返回的是一个数值
@Test
public void demo11(){
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
Query query = session.createQuery("select count(*) from Customer");
List<Object> list = query.list();
System.out.println(list);
transaction.commit();
}
Hibernate的查询方式:QBC检索
简单查询
使用的是Criteria接口
Criteria criteria = session.createCriteria(Customer.class);
List <Customer>list = criteria.list();
for (Customer customer : list) {
System.out.println(customer);
}
排序查询
需要使用
addOrder()
的方法来设置参数,参数使用Order 对象的 asc() or desc() 方法
Criteria criteria = session.createCriteria(Customer.class);
criteria.addOrder(Order.desc("cust_id"));//降序
//criteria.addOrder(Order.asc("cust_id"));//升序
List <Customer>list = criteria.list();
for (Customer customer : list) {
System.out.println(customer);
}
分页查询
需要使用 setFirstResult();setMaxResults();方法
Criteria criteria = session.createCriteria(Customer.class);
criteria.setFirstResult(0);
criteria.setMaxResults(5);
List <Customer>list = criteria.list();
for (Customer customer : list) {
System.out.println(customer);
}
条件查询
(Criterion 是查询条件的接口,Restrictions 类是 Hibernate 框架提供的工具类,使用该工具类来设置查询条件)条件查询使用 Criteria 接口的 add 方法,用来传入条件,使用 Restrictions 的添加条件的方法,来添加条件。
Restrictions.eq -- 相等
Restrictions.gt -- 大于号
Restrictions.ge -- 大于等于
Restrictions.lt -- 小于
Restrictions.le -- 小于等于
Restrictions.between -- 在之间,闭区间
Restrictions.like -- 模糊查询
Restrictions.in -- 范围
Restrictions.and -- 并且
Restrictions.or -- 或者
- Restrictions.isNull -- 判断某个字段是否为空
Criteria criteria = session.createCriteria(Customer.class);
//criteria.add(Restrictions.eq("cust_name", "张三"));//条件查询 等于
criteria.add(Restrictions.like("cust_name", "%三"));//模糊查询 等于
List <Customer>list = criteria.list();
for (Customer customer : list) {
System.out.println(customer);
}
统计查询
(Projection 的聚合函数的接口,而 Projections 是 Hibernate 提供的工具类,使用该工具类设置聚合函数查询)使用 QBC 的聚合函数查询,需要使用
criteria.setProjection()
方法
Criteria criteria = session.createCriteria(Customer.class);
criteria.setProjection(Projections.rowCount());
Long num = (Long)criteria.uniqueResult();
System.out.println(num);
离线条件查询
使用的是 DetachedCriteria 接口进行查询,离线条件查询对象在创建的时候不需要使用 Session 对象,在添加条件 时也不需要 Session 对象,只有在查询的时候使用 Session 对象即可,所以叫做离线条件查询。
一般情况下,在业务层开启 Session 后,在持久层对数据进行操作,而在 web 层需要接收条件查询的若干条件,所以在 web 层就设置条件会很方便,又因为 Criteria 需要由 Session 创建,所以无法在 web 层设置条件,于是离线条件查询出现了。
@Test
public void demo6() {
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(LinkMan.class);
// 设置查询条件
detachedCriteria.add(Restrictions.like("lkm_name", "联%"));
Session session = HibernateUtil.getCurrentSession();
Transaction tr = session.beginTransaction();
//绑定session
Criteria criteria = detachedCriteria.getExecutableCriteria(session);
// 查询数据
List<LinkMan> list = criteria.list();
for (LinkMan linkman : list) {
System.out.println(linkman);
}
tr.commit();
}
HQL 多表查询
SQL的多表查询
1、连接查询
a:交叉连接-笛卡尔积 select * from A,B;
b:内连接-inner join(inner可以省略)(l两个表的公共部分)
隐式内连接 select * from A,B where A.id = B.aid;
显示内连接 select * from A inner join B on A.id = B.aid
c:外连接- left/right outer join(outer可以省略)
左外连接 select * from A left outer join B on A.id = B.aid
右外连接 select * from A right outer join B on A.id = B.aid
全外连接 select * from A fullouter join B on A.id = B.aid2、子查询
HQL的多表查询
1、连接查询
a:交叉连接-笛卡尔积 select * from A,B;
b:内连接-inner join(inner可以省略)(l两个表的公共部分)
隐式内连接 select * from A,B where A.id = B.aid;
显示内连接 select * from A inner join B on A.id = B.aid
迫切内连接
c:外连接- left/right outer join(outer可以省略)
左外连接 select * from A left outer join B on A.id = B.aid
右外连接 select * from A right outer join B on A.id = B.aid
迫切左外连接
HQL内连接:非迫切内连接使用 inner join ,默认返回的是 Object 数组
public void demo7() {
Session session = HibernateUtil.getCurrentSession();
Transaction tr = session.beginTransaction();
Query query = session.createQuery("from Customer c inner join c.linkmans");
List<Object[]>list = query.list();
for (Object[] objects : list) {
System.out.println(Arrays.toString(objects));
}
tr.commit();
}
HQL迫切内连接:迫切内连接使用 inner join fetch ,返回的是实体对象
@Test
public void demo8() {
Session session = HibernateUtil.getCurrentSession();
Transaction tr = session.beginTransaction();
Query query = session.createQuery("select distinct c from Customer c inner join fetch c.linkmans");
List<Customer>list = query.list();
for (Customer objects : list) {
System.out.println(objects);
}
tr.commit();
}
Hibernate的查询方式:SQL检索
@Test
public void demo9() {
Session session = HibernateUtil.getCurrentSession();
Transaction tr = session.beginTransaction();
//普通查询--数组显示
SQLQuery sqlQuery = session.createSQLQuery("select * from cst_customer");
List<Object[]>list = sqlQuery.list();
for (Object[] objects : list) {
System.out.println(Arrays.toString(objects));
}
//addEntity封装进对象
/* SQLQuery sqlQuery = session.createSQLQuery("select * from cst_customer");
sqlQuery.addEntity(Customer.class);
List<Customer>list = sqlQuery.list();
for (Customer objects : list) {
System.out.println(objects);
}*/
tr.commit();
}
十八、Hibernate 的查询(抓取)策略(优化)
延迟加载:lazy(懒加载)。到执行到改代码的时候,不会发送语句去进行查询,当真正使用到该对象中的属性的时候,才会发送 SQL 语句,是 Hibernate 框架提升性能的方式
延迟加载的分类:
类级别的延迟加载:指的是通过load方法查询某个对象的时候是否采用延迟加载 session.load(Customer.class)
关联级别的延迟加载:指的是在查询到某个对象的时候,查询其关联的对象的时候是否采用延迟加载
Customer customer = session.get(Customer.class,1l)
customer.getLinkMans();(查询某个客户,当查看该客户下的所有联系人是是否采用延迟加载)
类级别的延迟加载的测试
Session 对象的 load 方法默认就是延迟加载 Customer customer = session.load(Customer.class,1l)
通过断点调试,执行该行代码时,控制台输出为空,即没有发送 SQL 语句,执行完毕后会看到SQL语句。
在映射配置文件的
<class>
标签上配置lazy="false" 默认为True load的延迟加载就会失效
Session.get(Class,id) 本身就没有延迟加载
1、抓取策略
抓取策略概述
抓取策略往往会和关联级别的延迟加载一起使用,优化语句
通过一个对象抓取到关联对象需要发送SQL语句,SQL语句如何发送,发送成什么样的格式通过策略进行配置。
通过<set>或<many-to-one>上通过fetch属性进行设置
Fetch和这些标签上的lazy图和设置优化发送的SQL语句
<set>上的fetch和lazy
fetch:抓取策略,控制SQL语句格式
- select :默认值,发送普通的select语句,查询关联对象。
- join :发送一条迫切左外链接查询关联对象。
- subselect : 发送一条子查询查询关联对象。
lazy:延迟加载,控制查询关联对象是否采用延迟加载
- true :默认值,查询关联对象采用延迟加载
- false :查询关联对象不采用延迟加载
- extra :懒惰
@Test
public void demo12(){
//默认情况
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
Customer customer = session.get(Customer.class, 1l);//发送一条查询客户的SQL
System.out.println(customer.getCust_name());
for (LinkMan linkMan : customer.getLinkmans()) {//发送一条查询联系人的SQL
System.out.println(linkMan.getLkm_name());
}
transaction.commit();
}
<many-to-one>上的fetch和lazy
fetch:抓取策略,控制SQL语句格式
- select :默认值,发送普通的select语句,查询关联对象。
- join :发送一条迫切左外链接查询关联对象。
lazy:延迟加载,控制查询关联对象是否采用延迟加载
- proxy : 默认值,代理,现在是否采用延迟加载,由另一端(即一对多的一方)的
<class>
上的 lazy 确定如果一方的
<class>
上的lazy="true"
,proxy 的值就是 true (延迟加载)如果一方的
<class>
上的lazy="false"
,proxy的值就是 false (不采用延迟.)
- false :查询关联对象不采用延迟加载
- noproy (不会使用)
批量抓取
获取客户的时候,批量抓取联系人
修改 Customer.bhm.xml <set name="linkmans" batch-size="4">
@Test
public void demo13(){
//获取客户的时候 批量抓取联系人
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
List<Customer> list = session.createQuery("from Customer").list();
for (Customer customer : list) {
System.out.println(customer.getCust_name());
for (LinkMan linkMan : customer.getLinkmans()) {
System.out.println(linkMan.getLkm_name());
}
}
}
获取联系人的时候,批量抓取客户
修改 <class name="com.cn.Hibernate.Customer" batch-size="3" table="cst_customer" >
@Test
public void demo14(){
//获取客户的时候 批量抓取联系人
Session session = HibernateUtil.getCurrentSession();
Transaction transaction = session.beginTransaction();
List<LinkMan> list = session.createQuery("from LinkMan").list();
for (LinkMan linkMan : list) {
System.out.println(linkMan.getLkm_name());
System.out.println(linkMan.getCustomer().getCust_name());
}
}