Hibernate框架学习
入门:
什么是Hibernate框架 能干什么 ?
Hibernate是一个ormapping的轻量级框架 什么又是ormapping?
ormapping(Object Relational Mapping)简称ORM 对象关系映射
是一种为了解决面向对象与关系型数据库存在的互不匹配的现象的技术
简单的来说ORMapping就是建立Java程序中的对象与数据库表的关系 我们在程序设计中通常会根据对象和对象的属性对应的在数据库中创建一张表出来,然后将java程序中的对象持久化到关系型数据库中,相反在数据库中设计的表则Java程序中也往往会定义一个实体类来进行体现,而Ormapping的作用就是来描述对象和数据库表中的映射关系的框架
Hibernate就是Oramapping框架的一种 是一个轻量级框架
Hibernate位于三层架构中的数据持久层,最底层的用于数据库的操作,以往我们对会使用JDBC来作为我们操作数据库的DAO层
了解JDBC的优缺点 和 Hibernate的优缺点 面试题
JDBC的优缺点
优点:
1、对数据库的操作更直白,方便调试。
2、速度比较快,因为JDBC是使用SQL语句传送的元数据来操作数据库
3、可控性高,通过JDBC来操作数据库时定义的sql语句都是我们都是手动编写的,对于程序的效率自己可以把控
缺点:
1、代码繁琐,每一次对数据库的操作都需要JDBC资源的获取和关闭
2、不能缓存数据 (数据的缓存)
3、sql语句的移植性不好,对于JDBC操作的某一个数据库平台,只对于指定的数据库平台可用
4、重复的代码过多
Hibernate的优缺点
优点:
1、支持数据缓存 一级缓存 二级缓存 查询缓存
2、移植性好,使用hibernate框架,我们只需要关心数据即可,而操作数据库的sql语句都是框架内部定义的,不可更改
3、操作简单
缺点 :
1、sql语句不可控。使用hibernate框架,我们不可以控制sql语句,因为sql语句是框架内部自动生成的,所以就涉及到sql语句的优化问题
2、如果数据库过大,那么不推荐使用hibernate,因为使用hibernate操作的数据都需要最后封装成对象,如果数据库中的表的记录过多,过大,则对应的创建的对象也很多,及其占用资源。
详细的hibernate的优缺点
优点:
1、hibernate可以让开发人员以面向对象的思想来操作数据库。JDBC只能通过操作SQL语句将元数据传送给数据库,进行封装操作。而hibernate可以在底层对元数据和对象进行转化,使得开发者只用面向对象的方式来存取数据即可
2、hibernate使用xml或JPA的配置以及数据库方言等等的机制,使得hibernate具有更好的移植性,对于不同的数据库,开发者只需要使用相同的数据操作即可,无需关系数据库之间的差异。而直接使用JDBC就不得不考虑数据库的差异问题
3、hibernate提供了大量的封装(这也是它最大的缺点),很多数据操作以及关联关系等都被封装的很好,开发者不需要写大量的sql语句,这就极大的提高了开发者的开发效率
4、hibernate提供了缓存机制(session缓存,二级缓存,查询缓存),对于那些改动不大且经常使用的数据,可以将他们放到缓存中,不必在每次使用时都去查询数据库,缓存机制对提升性能大有帮助
缺点
1、有时优点也就是缺点 对hibernate而言
它对JDBC的封装过于厉害,所以就失去了对SQL语句的控制(虽然也有HQL,但是这样一来也影响了hibernate的移植性)使用hibernate在很多地方不够灵活,难于优化
2、hibernate没有提供专门的批处理机制,如果要批量处理更新和插入数据时,还需要显示的flush、clear之类的操作,性能不如JDBC
3、相对于JDBC,hibernate更消耗内存,因为它每次的数据库操作做数据和对象的转换和封装,查询出一条数据就要创建一个或多个对象,这样太消耗内存了 (数据库的数据量一大,hibernate的效率就底下了)
学习目标
- Hibernate的加载流程
- Hibernate的操作(CRUD)
- 建立对象与表的映射关系(基于Hibernate的映射文件)
- Hibernate的优化
- HQL语句的学习
入门 搭建Hibernate
使用hibernate框架持久化对象
入门 搭建Hibernate
导入Hibernate所需的jar包 14个jar包
Hibernate框架是一个对象关系映射的框架,我们首先创建一个POJO对象出来
POJO:持久化对象,就相当于普通的JavaBean 表示该对象会被持久化存储到数据库中
小知识点:为什么要将JavaBean或者POJO实现Serializable接口
对象的序列化Serializable,该接口并没有定义任何的抽象方法或者字段,只是作为一个标识,给JVM虚拟机使用的,对象实现了该接口,就会被虚拟机所识别,虚拟机就会将该对象转换为二进制流的方式在网络中存储Serializable接口就是为了JavaBean对象POJO对象可以在网络环境中存在。
搭建步骤
1、hibernate 对象映射关系 首先我们应该明确两方的任意一方 对象 或者表
2、创建一个持久化对象出来,然后创建一个配置文件,用配置文件来描述对象与表的映射关系
配置文件的名称 POJO类名称.hbm.xml hbm: hibernate mapping
配置文件需要在POJO类的类路径下创建
映射文件的功能:
- 描述持久化对象与表的映射关系
- 对象与表的映射关系
- 对象中的字段与表中的字段的映射关系
- 对象中的字段数据类型与表中的字段数据类型的映射关系
- 一对多和多对多的关系
hibernate.hbm.xml文件的配置详情
根节点:<hibernate-mapping>
子节点:<class name="cn.itcast.domain.Person">
使用class节点来描述持久化对象与表的关系
一个class就对应着一个持久化对象
class节点的属性
name属性:持久化类的类全名
catalog属性:对应的数据库名称 ,可以不写,因为在hibernate.cfg.xml文件中还需要进行设置
table属性:对象映射的表的名称 默认值是name属性的类名称
在class节点下定义id节点来描述标识字段 定义property字段描述一般属性
id节点 id节点是为了用来描述持久化类的主键字段和表中的主键的映射关系
name属性:指定持久化类中的主键字段
column属性:指定表中的主键字段
type属性:指定持久化类中的字段数据类型
length属性:指定数据库表中的数据大小 必须指定
id节点还有一个子节点必须设置 generator节点 用于设置数据库生成主键的方式
<generator class="increment">
</generator>
property节点的属性和id节点的属性一致,只是property节点用来描述一般属性
<!--
hibernate的映射配置文件 最重要的配置文件。
该配置文件的功能也就是hibernate框架的核心 对象映射关系。
该配置文件就是为了描述对象与表的关系的描述对象与表的关系。
-->
<!-- 根节点 hibernate-mapping -->
<hibernate-mapping>
<!-- 首先声明建立映射的对象 -->
<!--
name属性 名称持久化类的类全名
catalog属性指定数据库的名称 一般不设置,因为在hibernate的配置文件中还需要进行声明
table属性 对象映射数据库的表名称 可以不写 不写默认就取name属性的类名称
-->
<class name="cn.itcast.domain.Person">
<!-- 标识属性 标识映射 映射着表中的主键 可以看出在持久化类中必须声明一个id 也就是主键字段 -->
<!--
name属性指定持久化类中的主键字段
column属性指定表中的主键名称 可以不写 不写默认就取name属性的值
type属性 主键字段的数据类型
length属性 指定的是数据库的字段的长度 必须写, 如果不写的话默认的就是最大值
-->
<id name="pid" column="pid" type="java.lang.Long" length="10">
<!--主键生成器 声明表中的主键的生成方式
可以手动设置主键,也可以让主键自动加1
generator节点为了告诉hibernate容器主键生成器是如何产生的
-->
<generator class="increment"></generator>
class的取值范围及其含义
assigned:由程序自己维护产生主键 测试的时候使用
uuid:由hibernate内部产生一个uuid作为主键
increment:由hibernate内部进行主键加1的操作,这种参数主键的类型必须是整数类型
identity:由数据库来实现主键加1的操作
</id>
<!-- 描述一般属性,只有主键的字段使用id节点来进行描述 剩下的其他字段都统一使用property节点进行描述
-->
<property name="pname" column="pname" type="java.lang.String" length="20"></property>
<property name="psex" column="psex" type="java.lang.String" length="10"></property>
</class>
</hibernate-mapping>
3、创建hibernate的配置文件 hibernate.cfg.xml
配置连接数据库的环境信息 存放到src类路径下
hibernate的配置文件需要设置连接数据库的用户名 密码 连接数据库的url 和数据库(该数据库预先创建)
设置hibernate根据对象创建数据库的方式,并且在hibernate的配置文件中还需要关联映射文件
<hibernate-configuration>
<!--
一个session-factory只能连接一个数据库
-->
<session-factory>
<!--
数据库的用户名
-->
<property name="connection.username">root</property>
<!--
密码
-->
<property name="connection.password">19961001</property>
<!--
url
-->
<property name="connection.url">
jdbc:mysql://localhost:3306/itcast_day01_hibernate
</property>
<!--
hibernate可以根据持久化对象和映射文件动态的创建表
也可以根据表来动态的生成持久化类和映射文件
如果想要该功能就必须使用该节点
property name="hbm2ddl.auto:
作用:根据持久化类和映射文件生成表
validate 只验证 并不创建表 在hibernate启动的时候,验证持久化对象与表中的结构是否一致
create-drop 当hibernate启动的时候创建表 销毁时 删除表
create 当hibernate启动的时候就创建表
update 当hibernate启动的时候判断对象所映射的表是否被创建了,如果没有则会被创建,如果创建了则不创建
-->
<property name="hbm2ddl.auto">update</property>
<!-- 导入映射文件 -->
<mapping resource="cn/itcast/domain/Person.hbm.xml"/>
</session-factory>
</hibernate-configuration>
4、使用hibernate程序加载配置文件,根据持久化类和映射文件创建表
加载hibernate的配置文件 如果要使用Configuration的configure()方法加载配置文件,那么该配置文件必须存放在classpath类路径下,且配置文件的名称为hibernate.cfg.xml
*Configuration对象 加载配置文件创建SessionFactory使用
*SessionFactory对象 创建Session对象使用
*Session对象,用于操作数据库使用 开启一个事务
*Transaction对象 用于设置事务提交
//创建Configuration对象
Configuration configuration = new Configuration();
//加载hibernate的配置文件
configuration.configure();
//根据配置文件中配置的数据库信息和关联的映射文件创建表
SessionFactory sessionFactory = configuration.buildSessionFactory();
//获取session的实例
Session session = sessionFactory.openSession();
//开启事务 返回的一个事务实例
Transaction transaction = session.beginTransaction();
//创建持久化对象的实例,封装数据
Person person = new Person();
person.setPname("毛可星");
person.setPsex("男");
//调用Session中的save()方法保存对象数据到表中
session.save(person);
//提交事务
transaction.commit();
//关闭事务
session.close();
加载hibernate配置文件的两种方式
都需要使用Configuration对象来进行加载
第一种 调用configuration对象的configure()方法
该方法加载配置文件,有两个要求
1.hibernate配置文件必须存放在classpath的类路径下
2.配置文件名称必须为hibernate.cfg.xml
代码:
//创建Configuration对象
Configuration configuration = new Configuration();
//加载hibernate的配置文件
configuration.configure();
源码:
public Configuration configure() throws HibernateException {
configure( "/hibernate.cfg.xml" );
return this;
}
第二种:
使用Configuration.configure(“”);的方式 configure()方法传入一个String类型的参数,该参数指定配置文件的路径和名称 名称可以自定义
加载hibernate.cfg.xml配置文件 使用第二种方式加载hibernate的配置文件
configuration.configure(“cn/itcast/utils/2.xml”);
源码:
public Configuration configure(String resource) throws HibernateException {
log.info( "configuring from resource: " + resource );
InputStream stream = getConfigurationInputStream( resource );
return doConfigure( stream, resource );
}
hibernate的加载流程以错误信息
加载流程
注意:
1、Hibernate如何识别一个类为持久化类
根据hibernate的配置文件中关联的映射文件的class节点的name属性指定的类 映射文件中指定的类就是持久化类
2、Session对象操作的类必须是一个持久化类,也就是必须与映射文件中的class节点的name属性声明的类要一致
错误信息
1、Session操作的类要与映射文件中声明的持久化类一致
2、配置文件加载映射文件的路径要正确
3、持久化类的字段要符合JavaBean的规范
增删改查
使用Hibernate完成持久化对象对应的数据库中的表的增删改查的操作
这些操作都依赖以Session对象,都必须使用Session对象中的方法才可以完成
查
查询所有
//查询 调用session对象的createQuery方法
//调用createQuery()方法,参数是from 持久化类的类名 首字母大写
Query query = session.createQuery("from Person where pname='毕向东'");
List<Person> persons = query.list();
根据主键查询
//调用get()方法来根据主键查询person表中的记录
这里的get()方法的参数 第一个参数必须指定查询的持久化类的Class字节码对象, 而第二个参数指定的是主键的值,该值的类型必须与持久化类中定义的标示字段的数据类型一致
Person person = (Person)session.get(Person.class,1L);
修改
//第一种 先根据主键id在数据库中查出来
Person person = (Person) session.get(Person.class,1L);
//修改person对象的值
person.setPname("翘臀");
person.setPsex("女");
//调用update()方法修改
session.update(person);
//第二种方法需要创建一个持久化类的对象,根据给主键字段进行赋值
//在设置新的要被修改的值
Person person2 = new Person();
person2.setPid(6L);
person2.setPname("东方不败");
person2.setPsex("不详");
//调用update()方法
session.update(person2);
删除
//删除 持久化对象对应的表中的记录 两种方式
//先根据主键id查询出要被删除的Person对象
Person person = (Person)session.get(Person.class,7L);
//调用delete()方法删除
session.delete(person);
//删除第二种
Person p = new Person();
p.setPid(8L);
session.delete(p);
总结
使用Hibernate框架完成持久化对象在对应的数据库中的表中的增删改查的操作
增
创建持久化对象,该对象必须是映射文件中声明的持久化对象,并且符合JavaBean的规范 封装数据到持久化对象中
调用Session对象的save()方法,将持久化对象封装的数据保存到数据库对应的表中
注意Session对象使用后要进行资源的释放
Session.close();
查
查询表中的记录 根据主键id查询记录 并创建一个持久化类,将查询的数据封装到持久化类中session.get()方法 该方法需要两个参数
1、持久化类.Class
2、查询的标志字段值 也就是表中的主键id 注意:这里的值要和持久化类中标示字段的数据类型要一致
查询所有的记录
使用session.createQuery()方法,在参数中接from 持久化类名 where 查询条件
session.createQuery(“from Person where pname=‘猪头’”);
删
session.delete();
delete方法中传入持久化对象的实例 delete()方法删除表中的记录是根据对象中的主键id然后在构建sql语句进行删除的
有两种方式完成删除操作
1.先根据session对象的get()方法查询出指定主键对应的持久化对象
然后在delete()方法中传入查询出来的持久化对象即可
2.创建一个持久化对象,然后指定要被删除的记录的主键值,设置到持久化类中对应的标志字段中
然后在使用delete()方法删除
修改
session.update()
同样有两种方式实现修改操作
1.首先使用session.get()方法查询出要修改的对象
然后在调用持久化对象的setter()方法修改指定的值
最后在调用session.update()方法传入持久化对象,进行修改
2.创建一个持久化类的实例,设置持久化类中的标示字段的值,该值就对应着表中的要被修改的记录的 主键id 设置各个字段的新值
调用update()方法修改 不推荐使用,因为如果持久化类中的字段过多则很麻烦
观察一个问题
同一个主键能否被两个持久化类使用
表中有一条主键为1的记录 ,我们使用hibernate查询出这条记录并返回持久化类的实例,这时我们又创建了一个持久化类的对象,将该持久化类的标示字段也就是主键,同样设置为1 ,这时再去使用hibernate操作新建的持久化对象的数据。最后发现 这种操作在hibernate中是不被允许的,因为在表中一条主键唯一的对应着一条记录
在持久化类中同样也是这样,一个主键只能对应着一个持久化对象
org.hibernate.NonUniqueObjectException: a different object with the
same identifier value was already associated with the session:
[cn.domain.Person#1]
Java程序中的数据类型和数据库中的数据类型转换问题
在hibernate中,定义了内部的数据类型,这些数据类型可以默认的进行Java程序中的数据类型与数据库中的
数据类型的转换
hibernate中定义的数据类型 string long integer short …
在映射文件设置持久化对象与表中的字段映射时,可以使用hibernate定义的数据类型,但是使用Java的类型效率高
查询hibernate操作数据库时的sql语句
在hibernate配置文件中设置一个property节点name属性的属性值为show_sql 节点值为true
<property name="show_sql">true</property>
学习hibernate中的映射文件规定的主键产生器
<generator class=""></genderator>
学习class属性的取值
increment
自增 由hibernate自动完成主键加1的操作
hibernate操作表的时候,会先查询出当前表中的主键的最大值,然后在将最大值加1作为当前操作的主键
当映射文件的主键产生器的值为increament时 ,hibernate会在操作持久化对象的数据前执行 select max(pid) from Person 该语句 获取当前表中最大的主键值然后主键值+1设置到新的持久化对象上
Hibernate:select max(pid) from Person
Hibernate: insert into Person (pname, psex, pid) values (?, ?, ?);
identity
由数据库设置主键 数据库要设置主键的自动加1 效率相当于increment要高一些使用identity时生成的sql语句,可以看出,主键不是hibernate设置的,而是通过数据库内部设置的
Hibernate: insert into Person (pname, psex) values (?, ?)
uuid 代理主键
由hibernate内部采用UUID的方式生成一个唯一的字符串来设置主键
使用uuid作为主键产生器就要满足POJO类中的标示符为String类型
映射文件中的表中的字段也要设置为对应的String类型
assigned
由程序自己设置主键 一般在测试的时候使用
主键的值由我们通过代码进行设置维护
了解hibernate操作POJO对象的时候 对象处的状态
三种状态
临时状态、 持久化状态 也叫做托管、游离状态也叫做脱管
在使用hibernate操作持久化对象的时候 持久化对象的状态
三个状态
- 临时状态
- 托管状态 持久化状态
- 脱管状态 游离状态
临时状态
一个持久化对象 被实例初始化的时候,就是临时状态,此时该对象没有与数据库有任何的关联也就是说该对象是新的,在表中也没有该持久化对象对应的信息
//创建持久化对象的实例,这时该对象处于临时状态
//位于内存中,和hibernate并没有任何关联
Person person = new Person();
托管状态、持久化状态
临时状态的对象可以变成持久化状态的对象 持久化状态 表示当前的对象已经被hibernate操作具有了数据库标示的实例,被存储到了session的缓存中,但是持久化状态的对象虽然有了数据库的标示但是此时并没有真正的写入到数据库中,只有当Transaction提交事务时,才真正的对数据库进行更新,从而完成持久化对象和数据库的同步 ,在同步之前的持久化对象称为脏对象
session.save()方法 通过断点调试可知,save()方法并没有将真正的持久化到数据库的表中,而是只执行了一句查询当前数据库最大主键的语句,save()方法执行时,只是将持久化对象从临时状态变为持久化状态
同save()方法一样的方法:get()、load()、find()…
游离(脱管)状态
当session关闭后,持久化对象就变成了离线对象,离线表示这个对象不能在于数据库保持同步,与临时状态的区别为:游离状态的对象是由持久化状态的对象转变而来的,数据库中可能存在着游离状态的记录,而临时状态则没有session中的close()方法和evict()方法,clear(),会将持久化状态的对象变成游离状态的对象
而使用update()方法又可以将游离状态的对象变为持久化状态的对象前提是游离对象在表中有记录对应
hibernate中的方法操作对持久化对象的状态影响
当session.get()方法得到一个对象的时候,是不需要在执行update语句,因为已经是持久化状态,当一个对象是一个持久化对象的时候,当进行提交的时候(事务提交),hibernate内部会让该对象和快照进行对比,如果一样,则不发出update语句,如果不一样则发出update语句
evict()方法:将一个持久化状态的对象从session缓存中删除
clear()方法:将session缓存中所有的对象删除
@Test
public void state()
{
//创建Configuration对象,加载hibernate的配置文件
Configuration configuration = new Configuration();
//加载配置文件
configuration.configure();
//创建SessionFactory对象,将数据库的连接信息和映射文件、持久化类信息存储
//到SessionFactory对象中,该对象是一个单例对象,并且是一个
//线程安全对象
SessionFactory sessionFactory = configuration.buildSessionFactory();
//创建Session对象,连接数据库
Session session = sessionFactory.openSession();
//获取事务的实例
Transaction transaction = session.beginTransaction();
//创建持久化对象的实例,这时该对象处于临时状态
//位于内存中,和hibernate并没有任何关联
Person person = new Person();
//设置值
person.setPname("哈哈哈");
person.setPsex("男女");
//保存操作
session.save(person);
//提交事务
transaction.commit();
//关闭session资源
session.close();
}
@Test
public void loadTest()
{
//创建Configuration对象,加载hibernate的配置文件
Configuration configuration = new Configuration();
//加载配置文件
configuration.configure();
//创建SessionFactory对象,将数据库的连接信息和映射文件、持久化类信息存储
//到SessionFactory对象中,该对象是一个单例对象,并且是一个
//线程安全对象
SessionFactory sessionFactory = configuration.buildSessionFactory();
//创建Session对象,连接数据库
Session session = sessionFactory.openSession();
//获取事务的实例
Transaction transaction = session.beginTransaction();
//load()方法和get()方法的功能一致
//都是指定持久类的class对象和主键的id来查询记录
Person person = (Person) session.load(Person.class,1L);
System.out.println(person.getPname());
//提交事务
transaction.commit();
//关闭session资源
session.close();
}
@Test
public void getTest()
{
//获取session对象
Session session = sf.openSession();
//获取事务的实例
Transaction transaction = session.beginTransaction();
//使用get()方法根据主键查询一条记录
Person person = (Person) session.get(Person.class,1L);
person.setPname("张全蛋");
//从session缓存中删除指定的持久化对象 当前删除的对象的状态一定是持久化状态
session.evict(person);
//evict驱逐
//使用session对象的evict()方法将指定的持久化状态的对象从session缓存中
//删除,这时持久化对象的状态就会变为脱管状态,这时在指定commit操作就不会有任何的sql操作
//commit()操作只能操作session缓存中的对象,也就是持久化状态的对象
//修改方法,update()方法可以将游离状态的对象变为持久化状态的对象
session.update(person);
//clear()方法又会将当前session缓存中的所有持久化对象删除,全部变为游离状态
//的对象
session.clear();
/*
* 当get()方法查询出一条记录,封装到持久化对象时,该对象就处于持久化状态
* 当提交事务时 commit()方法执行 hibernate会判断当前的持久化对象中的值是否发生了
* 变化(快照)如果发生了变化则会执行update操作 如果没有发生变化则不会执行sql操作
*/
//提交事务
transaction.commit();
//关闭session
session.close(); //关闭session也就意味着删除了session中存储的持久化状态的对象
}
//当不同Session状态下的持久化对象
@Test
public void test2()
{
//获取session对象
Session session = sf.openSession();
//创建事务的实例
Transaction transaction = session.beginTransaction();
//创建Person对象
Person person = new Person(); //临时状态
person.setPname("曹操");
person.setPsex("宁教我负天下人");
session.save(person); //持久化状态 这时持久化对象存储到了session缓存中
//但是并没有同步到数据库中,是一个脏对象
//只有提交的时候,对象才真正的同步到了数据库中
//提交事务
//在save()方法后又改变了持久化类的值,当提交的时候是否会改变
person.setPname("不能说脏话"); //此时还处于session的缓存中
//这时就和get()方法获取的持久化对象一样,在session缓存中处于持久化状态的对象
//当改变值的时候,如果数据库中不存在该持久化对象对应的数据,会先执行insert操作
//然后在执行update操作,如果存在该对象则只执行update操作
transaction.commit();
//关闭session对象
session.close();
}
@Test
public void test3()
{
//了解不同session下的对象的状态
//创建Configuration对象,加载配置文件
Configuration configuration = new Configuration();
//调用configure()方法,加载hibernate的配置文件,在加载配置文件的时候就会连同加载映射文件
configuration.configure();
//创建SessionFactory对象,该对象在创建的时候,会将hibernate配置文件中的数据库连接信息映射文件
//的信息都封装到SessionFactory对象中
SessionFactory sessionFactory = configuration.buildSessionFactory();
//创建session对象,连接数据库
Session session = sessionFactory.openSession();
//获取事务的实例
Transaction transaction = session.beginTransaction();
//调用session的get()方法查询指定主键的记录
Person person1 = (Person)session.get(Person.class,1L);
//使用get()方法查询出来的对象的当前状态为持久化状态,当前的对象在数据库中的表中有着对应的记录
//并且当前查询出来的持久化状态的对象在hibernate内部生成了一个快照的复制版,当事务提交的时候
//hibernate会根据持久化状态的对象和内部的快照对象进行对比判断内部是否发生了改变,如果发生了
//改变则会对应的执行update操作,如果没有发生改变则不会执行任何操作
person1.setPsex("我是一张弓");//修改了持久化状态的对象的值
//开启一个事务
transaction.commit();
//关闭session
session.close(); //关闭后session中的person1变成了游离状态
//又创建了一个session对象 新的session对象
session = sessionFactory.openSession();
//新的事物对象
transaction = session.beginTransaction();
//这时我们将上面的person1使用save()方法又变为持久化状态的对象,然后改变内部的值进行判断
session.update(person1); //将游离状态的person1,又变为了持久化状态,因为数据库的表中有记录
person1.setPname("火云邪神");
//提交事务
transaction.commit();
//关闭session对象
session.close();
}
Hibernate的多表操作
首先学习一对多的单项操作
单项的一对多
一种关系 我们需要使用类和表在描述这种关系
一对多
在类的设计上无非就是根据一对多的对应关系 在类中定义list集合或者set集合来描述这种关系
比如:班级 和 学生 在班级就可以设置list集合和set集合保存多个学生
在hibernate的映射文件中的描述上
如何使用hibernate来描述这种一对多的关系 只要我们能够清楚的使用映射文件描述这种关系,那么表的建立也就非常容易
我们需要创建持久化类的映射文件来描述持久化对象与表之间的映射关系 我们把一叫做主类 | 多叫做从类
在主类的映射配置文件中,hibernate同样设置了与持久化类对应的建立关系的节点 list节点和set节点
使用set节点和list节点来描述主类和从类之间的映射关系
list集合对应的就是hibernate映射文件中的list节点
set集合对应的就是hibernate映射文件中的set节点
在映射文件中通过list节点和set节点来描述持久化类之间的关系
<set name=""> name属性指定set集合的名称
key节点指定外键 column属性指定外键名称
<key column=""></key>
建立一对多关系 当前的映射文件所映射的类 是one 而many就是需要设置外键的类了
<one-to-many class=""/>
</set>
级联
在映射文件中的set节点上 ,有一个属性,cascade属性 级联属性,可以设置该属性的属性值为save-update ,这样我们可以根据多表的类和类的关系,将many类设置到one类的内部,当保存或者修改操作时,只需要save one类即可, 设想如果要保存多个学生,则会频繁的调用session对象的save()方法,麻烦
在set节点的属性中有一个cascade属性,该属性可以设置多表操作中的级联关系
save-update 设置级联,方便保存和修改持久化对象的数据
<set name="students" cascade="save-update">
当设置了级联后,在进行保存或者修改的操作时,可以将many的对象设置到one中 如:classes.setStudents(students);
只使用session对象的save()方法保存classes对象,但是classes对象的内部的set集合中保存了多个Student对象,当保存的时候,hibernate 内部会判断Classes对象,会发现Classes
内部的多个student对象,并判断Student对象的当前状态,如果当前的Student状态是临时状态那么hibernate就会执行insert操作,将Classes对象和student对象一起持久化到表中
如果当前的student对象的状态为持久化状态,则会执行update操作
前提是映射文件中的set节点设置的cascade属性,并且属性值为save-update
通过级联设置的对象会自动的设置外键的值,因为已经级联确立的两个对象的关系??? 其实使用的inverse属性
因为级联的原因,临时状态的对象添加到持久化状态对象的内部后,状态就变为持久化状态的对象了
hibernate的隐式操作和显示操作
save()方法和update()方法是显示操作
而级联是隐式操作
级联操作 当一个持久化状态的对象添加到一个临时状态对象的内部 这时临时状态的对象状态并没有变,只有调用save()方法时,临时状态对象才变为持久化状态对象
cascade的三个可选值 | 意义 |
---|---|
save-update | 支持级联关系 支持保存操作和修改操作的级联关系 |
delete | 支持级联关系 支持删除操作的级联关系,级联删除 删除存在关系的一对多对象,需要将cascade属性的属性值设置 |
all | 既支持保存操作和修改操作的级联关系 又支持删除操作的级联关系 |
关系操作
关系操作 :当我们操作一对多关系的持久化对象时,我们通过级联来建立了一对多对象的关系当使用hibernate提交这种关系时,可以发现数据库的表中的数据在外键上已经建立了对应的联系
这是因为在对应的建立联系的映射文件中的set节点的inverse属性的属性值为false
<set name="students" cascade="save-update" inverse="false">
<key column="cid"></key><!-- key指定外键 -->
<!-- 指定外键的关联关系 -->
<one-to-many class="cn.itcast.domain.Student"/>
</set>
该属性的设置可以指定hibernate是否允许多表之间的关系操作 默认值是false 支持 当我们不声明该属性的时候,该属性的默认值就支持关系操作,也就是通过级联操作的数据都会在表中建立对应的联系。当我们将值改为true时,那么hibernate将不支持关系操作,就算是通过级联添加的数据数据库中也不会有关系产生
依赖级联关系虽然可以将一对多对象的关系的持久化表中,但是持久化的只是数据,而非关系,如果想要建立对应的关系,就需要在映射文件中用来描述一对多关系的set节点中声明一个inverse属性,设置该属性的属性值为false 支持关系操作 ,如果为true时,则不会建立关系
inverse属性有两个可选值 false 支持关系操作 | true 不支持
当映射文件中的inverse属性不支持关系操作时,如果一对多的操作中,涉及到了一对多的关系操作,就不会操作成功,或者建立关系
如果是删除则会因为外键约束而删除失败
建立双向的一对多关系
一对多的双向
双向的体现就是在操作的时候,既能从一对多出发,又能从多对一出发
在类上的体现 one to many many to one
如果要建立双向的关系 那么不仅要在one类的内部创建集合保存many,还需要在many内部创建一个one的实例来访问所属的one
在映射文件上,在many持久化对应的映射文件中,我们需要设置一个节点来描述多对一的映射关系
从节点就能看出描述的功能所在
使用该节点来描述映射关系
<!--
描述一对多的双向的关系
在many类的映射文件中,使用many-to-one节点描述一对多的双向关系的映射
name属性指定所属的关系的名称
class属性指定建立关系的类全名
column属性指定外键,这个属性指定的其实对应的表中的属性,cid在表中存在的外键
cascade属性:级联操作
-->
<many-to-one name="Classes" class="cn.itcast.domain.Classes" column="cid" cascade="delete">
</many-to-one>
hibernate操作双向的一对多关系
从many对象出发,从多的一方出发,效率会高一些,明确一点,多代码,高效率 和 少代码 低效率 在开发中 还是会选择多代码 高效率的
从多出发需要注意的事项
以班级和学生为例
从学生出发,可以发现hibernate内部并没有使用update语句来建立因为级联而产生的对象的关系
而在数据库的表中却发现,student的外键中已经关联了级联的Classes的id
这是因为关系操作是从Student对象出发的,hibernate默认将student的外键作为一个字段进行添加
而外键的值就来自级联的Classes的id,如果Student的映射文件中不支持级联呢???????
如果student映射文件中不支持级联,那么建立关系就会失败,在hibernate提交操作的时候
因为判断客户端的代码得知Student和classes建立了级联关系 所以hibernate会将student中的外键cid的取值从Classes的id去取,但是在根据映射文件执行操作的时候,却发现student和Classes并没有建立级联关系,所以抛出了错误
**明确关系的建立是谁来维护的
hibernate的原理就是通过客户端程序的操作和对应的映射文件使用JDBC的方式产生代码,操作数据库
而我们的持久化对象的关系是由我们的代码控制的,而不是hibernate中的save()/update()方法 ,这些方法只对表负责,而不对表之间的关系负责。
**明确最后到底是使用谁建立的关系,在hibernate最后执行的时候又是参照的谁的配置文件。
使用hibernate建立多对多的关系
1、在持久化类中,通过对象与对象的联系,在类中使用set集合建立起类与类的多对多关系
2、在持久化类对应的映射文件中通过映射文件描述所对应的关系,hibernate会通过映射文件最后建立对应的多对多关系的表结构
在映射文件中
首先声明set节点
set节点用于指定当前持久化映射文件对应的持久化类中建立关系的set集合
set节点的name属性就指定持久化中建立关系的set集合的名称
set节点还具有另一个属性 table属性,多对多的关系建立需要第三张表
table属性就用于指定第三张用于建立多对多关系的表
声明完set节点后,在set节点中声明key节点,指定建立多对多关系的主键
key节点有一个column属性,该属性指定的就是持久化类中的主键标示字段,充当第三张表的外键
最后使用many-to-many节点 与关联对象建立多对多的关系
many-to-many节点的class属性指定多对多关联对象的类全名
column属性指定关联对象的主键标示字段
<set name="courses" table="student_course">
<key column="sid"></key>
<many-to-many class="cn.itcast.domain.onetomany.Course" column="cid">
</many-to-many>
</set>
hibernate的优化optimizing
hibernate的优化主要体现在
执行SQL语句的时机
执行SQL语句的多少
重用数据的缓存
。。。
一、懒加载
简单的理解:需要的时候在加载需要的东西(执行SQL语句)
懒加载的体现:
- 类的懒加载
- 集合的拦截器
- 单端关联的懒加载(多对一的懒加载)
类的懒加载
首先学习类的拦截器 load()方法 ,类的拦截器依赖与session的load()方法
类的懒加载做的事情,控制类的一般属性获取的时间
load()方法查询的持久化类,只有当访问持久化类的一般属性的时候,hibernate才会发出SQL语句将表中的记录封装到持久化对象中,早晚都会发出SQL语句,但是时机却很重要,如果使用get()方法,过早的创建了持久化的实例,但是又没有使用到,持久化对象就会一直驻留内存,如果持久化对象中封装了过大的数据,那么就会长时间在不被使用的情况下占用着内存的空间。
我们使用hibernate的load()方法从表中根据主键查询出一个持久化对象出来,但是load()方法并不立即执行sql语句查询持久化对象,只有当我们访问到持久化对象的一般属性的时候hibernate才会发送sql语句查询持久化对象
get()方法和load()方法的区别
get()方法是执行到方法后,hibernate立即查询数据库表中的记录,不管接下来我们使用到查询的对象与否
load()方法是当我们使用到对象的属性时,hibernate才会发出查询语句进行查询,这样就减少了对象占用内存的时间,什么时候使用什么时候查询
在类的懒加载上,对应的类的映射文件中,用于描述类与表的映射关系的class节点具有一个lazy属性,该属性用于设置是否支持懒加载
当lazy属性的属性值为false时,load()方法会立即执行sql语句,并不能起到懒加载的作用
当lazy属性的属性值为true时,load()方法执行sql语句的时机为获取持久化类的一般属性时执行
***class节点的lazy属性用于设置是否支持懒加载
true支持 默认值是true
false不支持
可以看出当load()方法完成懒加载的时候,获取持久化的标示字段 主键并不会发出sql语句,这是因为我们查询持久化对象的时候,使用的就是标示字段主键。也就是说,类的懒加载,只有访问持久化对象的一般属性时,才会发出sql语句
get方法查询的持久化对象访问标示字段也不会发出sql语句
集合的懒加载
依赖于建立一对多或者多对多的set节点中的lazy属性
集合的懒加载
什么时候考虑懒加载 当表与表的关系为一对多和多对多的关系时,我们需要操作集合
集合的懒加载依赖与对应的映射文件中建立多对多联系或者一对多联系的set集合的lazy属性
当lazy属性的属性值为 true时,hibernate会对集合进行懒加载。
集合的懒加载体现在,当遍历集合时,才执行sql语句查询出集合的语句。
lazy属性的属性值为false时,说明不支持懒加载,当获取集合的时候就会执行sql语句查询出记录封装到集合中
lazy属性的属性值为true时,支持懒加载的操作 hibernate会在遍历集合的时候执行sql语句操作
lazy属性的属性值还有一个extra,当lazy属性的属性值为extra时,只有遍历集合的时候才会发生获取集合的sql语句,而一般访问集合属性的操作,hibernate会创建对应的sql语句来查询对应的集合属性
比如:我们要查询集合的个数 也就是集合的size()方法,当lazy属性的属性值为extra时,会编写查询表中个数的sql语句。
select count(sid) from Student where cid =?
单端关联的懒加载(多对一的懒加载)
单端关联指的是多对一的操作 ,获取一的哪一方,因为本来就获取一个对象,占用的资源不多,所以一般不需要在进行懒加载
总结:
懒加载
1、类的懒加载
-
a、利用session.load方法可以产生代理对象
-
b、在session.load方法执行的时候并不发出sql语句
-
c、在得到其一般属性的时候发出sql语句
-
d、只针对一般属性有效,针对标示符属性是无效的
-
e、默认情况就是懒加载
2、集合的懒加载
-
false 当session.get时,集合就被加载出来了
-
true 在遍历集合的时候才加载
-
extra 针对集合做count,min,max,sum等操作
3、单端关联的懒加载(多对一)
no-porxy 默认值 true
根据多的一端加载一的一端,就一个数据,所以无所谓
hibernate的优化二
抓取策略
懒加载控制的是执行sql语句的时间,而抓取策略控制的就是sql语句,减少sql语句做到要求的事
抓取策略:其实就是根据一个对象获取关联对象的sql语句的不同体现
在hibernate中如何由一个对象查询关联对象而发生SQL语句
由一个对象,根据多表建立的关系而查询关联对象的信息,如何控制sql语句的查询方式
这就是抓取策略
抓取策略一般与懒加载一同使用
类的懒加载和集合的懒加载 与 抓取策略相结合
类的懒加载和集合的懒加载控制着获取对象的时间,抓取策略控制着如何使用更少的sql语句查询出我们需要的关联对象
抓取策略 fetch属性
抓取策略应用在一个对象获取关联对象的情况下,获取的关联对象都以set集合的形式返回,所以使用抓取策略 需要在对应的映射文件中描述关系的set集合中设置fetch属性
fetch属性的取值范围
join select subselect
代表了三种不同的sql语句查询方式
join
join 代表了使用左外连接的方式查询对象和对象的关联信息
左外连接 查询左表的信息,并且会将右表中不符合信息也查询出来
使用抓取策略的join方式查询对象的时候,查询组件的sql语句
会将当前对象放置在左表 关联对象放置在右表,当查询对象的时候,将会根据左外查询查询出当前查询对象的信息和关联对象的信息封装到当前对象的内部。
join的左外连接有一个条件,只有当查询的关系中不存在子查询时,才可以使用左外连接
在fetch属性为join的时候,集合的懒加载lazy属性并不能对抓取策略产生影响,但是前提是当前不存在子查询
subselect
当查询当前对象的关联对象 满足子查询时,使用抓取策略的subselect属性,查询多个对象的关联对象时,使用子查询来进行查询可以减少sql语句
subselect:
子查询 根据对象查询关联对象时,如果对象只有一个那么就会使用平常的sql语句,因为对象只有一个的话查询关联对象根据标示字段查询只会产生一条sql语句,不用优化
当查询对象的关联对象时,存在子查询时,那么就使用subselect,使用subselect会生产两条sql语句
第一条会查询出当前对象,第二条sql语句会根据当前对象的id进行子查询,查询出关联对象
如果对象为多个,那么就会使用子查询
当对象为多个时,抓取策略的值建议为subselect,使用子查询 来构建sql语句进行查询
hibernate根据对象查询关联对象的时候,会根据对象的标示字段id作为查询条件,查询关联对象的表中对应的记录,标示字段id作为建立关联关系的外键存在,正常情况下我们需要对每一个对象的标示id建立一条查询语句,查询关联对象表中的数据,而当使用了抓取策略后,我们可以使用子查询
来 构建sql语句,只是用一条sql语句就查询出关联对象
select * from Student where cid in(select cid from Classes);
select 属性
n+1条sql语句
我们查询对象,然后根据映射关系查询关联对象,默认情况下,我们在映射文件中设置了一对多的关系或者多对多的关系,在查询的对象关联对象的时候,就会使用当前对象的标示属性来作为查询关联对象的条件
这就是经典的n+1条语句的由来
比如:我们查询的对象为2条,根据sql语句查询出指定2条对象封装到Set集合中,然后遍历set集合,获取这两个对象的关联对象,这时候就会根据这两个的标示字段去表中进行查询,所以最后会产生3条sql语句
如果sql语句非常多,则效率就非常的低。
Session详解
我们使用hibernate对数据库的操作都是基于Session对象的方法上的,包括事务的处理也都是基于Session开启和提交的事务。
学习获取Session的两种不同的方式
应用场景:
多个DAO中的某个方法都需要开启事务操作数据库表中的数据,在service层中,我们同时调用多个DAO中需要开启事务的dao方法完成业务需求,这时多个DAO中的方法的事务却不同,也就是说,在多个dao中的方法中,使用了多个session,在session不同的情况下开启了多个事务,这是不允许的,同时也不能保证事务的完整性
解决方案 了解Session的产生机制和两种不同的Session创建方式
Session session1 = sessionFactory.openSession();
Transaction transaction1 = session1.beginTransaction();
Session session2 = sessionFactory.openSession();
Transaction transaction2 = session2.beginTransaction();
transaction1.commit();
transaction2.commit();
session1.close();
session2.close();
这种方法创建的Session对象都属于一个新的Connection连接数据库的Session,所以开启的事务都专属于独立的Session
openSession()方法的源码:
private SessionImpl openSession(
Connection connection,
boolean autoClose,
long timestamp,
Interceptor sessionLocalInterceptor
) {
return new SessionImpl(
connection,
this,
autoClose,
timestamp,
sessionLocalInterceptor == null ? interceptor : sessionLocalInterceptor,
settings.getDefaultEntityMode(),
settings.isFlushBeforeCompletionEnabled(),
settings.isAutoCloseSessionEnabled(),
settings.getConnectionReleaseMode()
);
}
getCurrentSession()方法都干了些什么事?
当第一次调用getCurrentsession()方法时,hibernate会将创建一个Session对象,并且创建一个map集合,用map集合存放session对象,map集合中的键就是创建Session对象的SessionFactory,而map集合的键就是session对象,然后在将map集合存储到当前线程的资源共享类中 ThreadLocal,为什么要存放一个map集合而不是直接存放Session对象本身呢,首先根据map集合的特性,键唯一,如果键相同,值会覆盖,而SessionFactory是一个单例类,它是唯一的,用SessionFactory当做map集合的键可以保证存储的Session的唯一性
当在其他地方在调用getCurrentSession()方法时,会先判断当前线程资源共享类中是否存储了map集合,在从map集合中获取即可
public final Session currentSession() throws HibernateException {
Session current = existingSession( factory );
if (current == null) {
current = buildOrObtainSession();
// register a cleanup synch
current.getTransaction().registerSynchronization( buildCleanupSynch() );
// wrap the session in the transaction-protection proxy
if ( needsWrapping( current ) ) {
current = wrap( current );
}
// then bind it
doBind( current, factory );
}
return current;
}
getCurrentSession()方法的细节
使用getCurrentSession()方法的细节
org.hibernate.HibernateException: No CurrentSessionContext configured!
使用getCurrentSession()方法使用当前线程的资源共享类中获取一个Session对象,我们需要在hibernate的配置文件中,声明session的获取方式为线程中获取
<property name="hibernate.current_session_context_class">thread</property>
hibernate.current_session_context_class 节点的name值
节点的主体值指定为thread 表示从当前线程中获取Session对象
org.hibernate.SessionException: Session was already closed
抛出这个异常 session已经被关闭
使用getCurrentSession()方法创建的Session对象默认会被hibernate进行关闭,不需要我们在使用代码session.close()方法在进行关闭
org.hibernate.HibernateException: get is not valid without active
transaction
使用getCurrentSession()方法获取的Session对象必须与事务绑定。
使用session对象必须 创建Transaction
缓存的学习
session缓存和了解AJAX的缺点和页面缓存
缓存:将数据库中的数据存放到内存中,供客户端使用,缓存也就是
相当于在内存中建立了一个map集合或者list集合来存储数据
缓存需要关注的问题 也是缓存的技术所在
1、缓存的生命周期
2、如何将数据库的数据存放到缓存中
3、客户端如何访问内存中缓存的数据
4、客户端对缓存中的数据进行更新操作,如何将更新后缓存的内容写到数据库中
5、数据库更新数据,缓存如何实现更新
6、如何从缓存中获取对象
7、清空缓存
页面缓存
AJAX的缺点:异步请求是一种页面无刷新与服务端交互的技术,当页面多次与服务端进行请求交互而又不刷新的情况下,服务端的数据就会一直存储在页面中,会造成页面特别卡,
Session缓存
hibernate的缓存机制
hibernate提供了三种缓存机制
1、Session缓存 一级缓存
2、二级缓存
3、查询缓存
学习hibernate中的一级缓存
一级缓存又称session缓存,session缓存是将hibernate操作的数据存储到session内部封装的缓存中,这样再次访问数据的时候,就会从session缓存中获取数据而不是发送sql语句继续查询
1、session缓存的生命周期 session缓存也就是session对象内部来维护的,session缓存的生命周期也就是session对象的生命周期
2、session缓存存放的数据的安全性
session中存放的数据都是私有数据,使用getCurrentSession()方法获取的Session对象都是从当前线程共享类中获取的,对于同一个线程来说,线程中存放的数据是安全的,不存在数据丢失或者泄露的问题
3、 如何将使用hibernate操作的数据存放到缓存中
本质:session中的什么方法可以将获取的对象存放到session缓存中
- session.get()方法会将查询出来的数据存储到session缓存中
- session.load()方法会将查询出来的数据存储到session缓存中
- session.save()方法会将持久化到表中的对象存储到session缓存中
- session.update()方法会将此次更改的对象存储到session缓存中
4、怎么样从一级缓存中取数据
使用session对象的get load 方法可以从session缓存中取出数据
5、怎么移除session缓存中的一个对象
使用evict()方法,将指定的移除对象作为参数,移除session缓存中存储的对象
6、如何清空session缓存
使用session对象的clear()方法清空session缓存中缓存的所有对象
7、如何将数据库中的数据刷新到session缓存中的对象中session是一次数据库连接,当创建了session对象后,在更改了数据库的值,但是这次session对象中的数据库连接是上次默认的数据库状态 reflush()方法
8、flush()方法 刷新
将save()方法和update()方法或者delete()方法操作的结果刷新到表中
也就是对session缓存中的对象进行了变动,只要是save()、update()、delete()变动,都可以使用flush()方法刷新到表中
flush()方法做的事
flush()方法只对持久化状态的对象有效
- 1、 当执行flush()方法时,会判断当前session缓存中对象是否具有标示字段,在数据库中是否有指定的记录对象,如果没有则会执行insert方法。
- 2、session缓存中的持久化对象如果具有标示字段,那么就会根据hibernate的快照机制判断
当前对象中的内容是否被改变,如果被改变则会执行update操作,如果没有改变则不会执行任何操作 - 3、判断session缓存中的所有持久化对象的级联操作 是否有关联对象
- 4、判断session缓存中的所有持久化对象的关系操作,是否要对关联对象建立对应的关系
批处理
因为hibernate会将对象先变为持久化状态的对象,然后在持久化到类中进行存储如果批处理的数据量过大的话,就会导致session存储的持久化对象过多,session缓存就会因为持久化对象数量的增加导致内存溢出
java.lang.OutOfMemoryError: Java heap space
解决方案 批处理了一定的数量就flush()
假如要批处理10000条记录,那么就50条执行一次flush()方法,将数据持久化表中
注意 flush()方法执行完毕后,并不会将当前刷新的对象从session中去除,我们需要使用clear()方法情况
hibernate中的二级缓存和查询缓存
二级缓存:session中的一级缓存是用来存放私有数据的,如果我们的程序中需要使用到大量的共有数据,该将共有数据存储到哪里
为此hibernate提供了二级缓存,二级缓存就是用于存储共同数据缓存
hibernate本没有实现二级缓存,而是使用了外部的供应商,二级缓存依赖外部的供应商的API
常用的二级缓存的供应商的API
1、ehcache****最常用
2、oscache
3、jbosscache
如何使用二级缓存
默认情况下,hibernate是不支持二级缓存的,我们需要开启hibernate的二级缓存
1、开启二级缓存 在hibernate.cfg.xml配置文件中进行设置,开启二级缓存
使用二级缓存,在hibernate.cfg.xml配置文件中进行配置,开启二级缓存
<property name="hibernate.cache.use_second_level_cache">true</propert>
2、明确使用的二级缓存供应处的API、
在hibernate.cfg.xml配置文件中进行配置
还需要明确二级缓存的实现供应商是哪个,因为hibernate并没有去实现二级缓存而是使用了其他的供应商的API来实现了二级缓存
<property name="hibernate.cache.provider_class">
org.hibernate.cache.EhCacheProvider
</property>
3、明确会被二级缓存所缓存的对象 明确哪个类可以被放置到二级缓存中
有两种方式
1、在hibernate.cfg.xml配置文件中
<!-- 设置可以被放入到二级缓存的对象 -->
<class-cache usage="read-write" class="cn.itcast.domain.onetomany.Classes"/>
使用class-cache节点设置可以被存储到二级缓存中的对象,声明了该节点后,该节点指定的对象就会被存储到二级缓存中
2、在对应的持久化对象的映射文件中,如果我们想要哪个对象可以被放置到二级缓存中,就在对应的映射文件中进行配置
在映射配置文件中有一个class节点,在class节点下定义cache节点进行声明
usage属性:缓存策略
相当于数据库中事务的隔离操作 用来设置可以对存储到二级缓存中的对象的操作范围
属性值
ready-only :只可以对二级缓存中的对象进行读操作
read-write:可以对二级缓存中的对象进行读写操作
nonstrict-read-write:不严格的读写
transactional:最严格的读写
二级缓存中存储的数据的要求
要求该数据是公共的数据并且不能经常的被修改改动
因为二级缓存中存储的数据不需要经常的被改动,所以我们只需要关注如何向二级缓存中存储数据和取出数据即可
明确什么方法可以将对象放置到二级缓存中
session对象的get()方法可以将在配置文件或者映射文件中配置的指定的对象存储到二级缓存中
session对象的load()方法可以将在配置文件中获取映射文件中指定的对象存储到二级缓存中
session对象的update()方法并没有将对象存放到二级缓存中
查询缓存
查询缓存:用于缓存查询结果的缓存
查询缓存依赖于二级缓存,如果要使用查询缓存必须先开启二级缓存
查询缓存也需要在hibernate.cfg.xml配置文件中进行开启
true
查询缓存负责缓存session.createQuery()方法返回的数据 ,也就是对Query对象进行缓存
调用query对象的setCacheable()方法设置缓存和指定从查询缓存中取出数据
/*
* 查询缓存
* 依赖与hibernate的二级缓存
* 如果要使用查询缓存,必须首先开启二级缓存
* 在hibernate.cfg.xml配置文件中开启查询缓存
* <!-- 开启查询缓存 -->
<property name="hibernate.cache.use_query_cache">true</property>
*
*
* 使用查询缓存 hibernate中的查询缓存
* 查询缓存依赖与hibernate的二级缓存,如果要使用查询缓存,必须先开启二级缓存
* 二级缓存存储一个对象,而查询缓存可以存储我们查询出来的任何对象
*
*
*
*/
public class QueryCacheTest extends HibernateUtils
{
@Test
public void queryTest()
{
//查询缓存
Session session = sessionFactory.openSession();
//查询缓存只能应用在createQuery()方法指定sql语句查询的结果
Query query = session.createQuery("from Classes");
//查询缓存依赖与Query对象 ,使用query对象中的方法将查询出来的结果存储到
//查询缓存中
query.setCacheable(true);
///当使用query对象的setCacheable()方法,传入一个true值,就会将
//Query对象中封装的查询的结果存储到查询缓存中
//获取query的结果存储到二级缓存中
query.list();
session.close();
session = sessionFactory.openSession();
/*
* 另外还需要保持,存放在查询缓存中数据的sql语句要与取出数据的sql语句一致
* 如果不一致则还是会通过数据库获取
*/
query = session.createQuery("from Classes where cid = 1");
//从查询缓存中获取数据
query.setCacheable(true);
List<Classes> classes = query.list();
//System.out.println(classes.size());
}
}
二级缓存大量数据的解决方案
我们可以将二级缓存中缓存的数据存储到磁盘中,当获取缓存中的数据的时候从磁盘中获取即可
使用一个配置文件来完成将二级缓存缓存的数据转移到磁盘中进行缓存
1、在classpath下创建一个指定名称的配置文件ehcache.xml
2、通过该配置文件进行设置保存到磁盘的数据的信息
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--
解决二级缓存大数据
在classpath路径下创建一个ehcache.xml配置文件
该配置文件设置了二级缓存存储到磁盘的文件配置信息
diskStore节点:设置文件存储的路径
defaultCache节点:默认的存储策略
该节点是一个单节点,用于设置存储数据的信息 通过defaultCache节点的
属性来设置存储数据的信息
maxElementsInMemory:用于设置内存中最大对象数
erernal:用于设置对象的保留期限 是否是永久的 如果是true则会一直驻留在磁盘中
timeToIdleSeconds:钝化 ,如果超过指定时间不访问存储在磁盘上的数据则会发生钝化
timeToLiveSenconds:存活时间
maxElementsOnDisk:在磁盘上存储的最大文件值
-->
<diskStore path="C:\\TEMP"/>
<defaultCache
maxElementsInMemory="12"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
<!-- 指定某个特定的对象制定缓存策略
Cache节点
name属性指定被放置到磁盘的对象类全名
-->
<Cache
name="cn.itcast.domain.onetomany.Classes"
maxElementsInMemory="5"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>
HQL语句学习
hibernate提供了三种查询方式来查询表中的数据
1、原生SQL语句查询 native query
2、hibernate自定义的HQL语句查询 hibernate query language
3、Criteria query 条件查询 面向对象的查询
HQL学习
HQL是hibernate自定义的sql语句,它类似于SQL语句,对于表的大多数操作中,hql语句和sql语句的实现方式相同,所以对于一般的数据库hql也具有较好的支持性
hql单表查询
目标:
掌握使用hql查询的不同方式返回的结果集
掌握如何如果处理结果集
掌握在hql中使用参数占位符 两种方式
我们只有在session对象的createQuery()方法中才可以使用hql语句
查询单表中的所有数据
from 持久化类名
//使用createQuery()方法 指定hql语句 查询单表中的所有数据
Query query = session.createQuery(“from Classes”);
当使用createQuery()方法指定hql语句,查询单表中的所有数据时
通过debug调试可以看出通过from 持久化类名 查询的封装在list中的结果 封装的表中对应的持久化的对象 当使用createQuery查询表的所有数据时,直接使用from 持久化类名的方式进行查询
查询的结果使用Query对象的list()方法封装到list集合中,可以发现查询的结果封装到list结构的数据就是持久化类的JavaBean
注意这种查询方式返回的结果集list集合中封装的是持久化对象 持久化对象中就封装了表中对应的记录
查询单表中指定字段的数据
两种方式
第一种方式 可以使用hql语句进行查询
如:select cid,cname from Classes
//使用createQuery()方法指定查询表中的字段来查询
List lclasses = session.createQuery("select cid,cname from Classes").list();
这种查询方式返回的结果也同样封装到list集合中,但是list集合中存储的并不是封装查询字段的JavaBean, 而是一个Object数组,将查询的字段的值封装到了Object数组中,也就是list集合中包含了多个Object数组,使用Object数组封装了每一个字段的数据
第二种方式
hibernate支持使用构造方法 进行查询
使用持久化对象的构造方法进行查询
我们想要查询表中指定字段的值,可以使用两种方式进行查询
第一种:指定指定hql语句进行查询 select id,name from Classes
第二种:可以指定数据库表对应的持久化类的构造方法进行查询
在数据库中表都对应着一个持久化类,可以在持久化类中定义我们想要查询的字段的构造方法然后使用hql语句组合构造方法查询
1、在POJO类中,定义一个有参构造方法,参数就是我们想要查询的字段名称
2、在createQuery()方法中引用构造方法 创建定义的有参构造方法的对象
引用构造方法一定要使用类全名引用,在构造方法中填写字段的参数名称
可以看出这种方式封装到list集合中的结果是持久化对象,持久化对象中封装了我们在构造方法中指定的字段信息
List lclasses = session.createQuery
("select new cn.itcast.domain.onetomany.Classes(cid,cdescription) from Classes")
.list();
因为数据库的表都对应着一个持久化类
可以在持久化类中定义一个有参的构造方法,构造方法的参数就是查询的字段名称
在hql语句中声明该构造方法声明对象,构造方法中指定字段名称,这样在查询的时候,就会将指定的字段的值封装到持久化对象中,并且将所有的持久化对象存储到list集合中,并返回list集合
hql还支持where关键字进行筛选查询结果的操作
当where筛选的结果集唯一时,需要使用Query对象的uniqueResult()方法获取查询结果中的唯一结果集
//查询 当指定where条件,查询的结果为1时 使用query对象的uniqueResult()方法
//来获取查询的唯一结果集
Query query = session.createQuery("from Classes where cid=1");
Classes classes = (Classes) query.uniqueResult();
hql还支持参数占位符的应用
在where条件中应用参数占位符
有两种方式
***第一种直接使用?号 作为参数占位符
比如:select cname from Classes where cid = ?
注意该参数占位符的起始位置为0,当为声明的参数占位符赋值的时候使用Query对象中对应的类型方法即可,比如参数是Long类型的,那么在Query对象中就对应着有setLong()方法,执行参数占位符的位置,起始位置是0,指定值即可
Query query = session.createQuery("from Classes where cid=?");
query.setLong(0,3L);
***第二种 还可以使用:参数名称的形式
比如:select cname from Classes where cid=:cid
:号紧跟参数名称。在赋值的使用直接声明参数值即可
Query query = session.createQuery("from Classes where cid=:cid");
query.setLong("cid",3L);
*hql语句同样还支持 sql语句中的各种关键字来对查询结果进行筛选
sum max min count order by group by avg
hql语句的一对多操作
一对多操作的体现
总的体现在两个方面
1、查询对象与关联对象所有的数据信息
2、查询对象与关联对象中的某个信息
hql支持一对多的查询方式 等值连接 内连接 左外连接 迫切内连接 迫切左外连接
了解各种查询方式的查询结果 明确真正在实际开发中如何选择查询方式,以及优缺点
1、等值连接
通过左表与右边两边的关系来建立连接进行查询
要明白在hql中引用的是持久化对象,而不是表名称,明确表结构和持久化类的结构并不是全部相同的
使用等值连接来查询对象与关联对象中的所有数据
//根据等值连接查询一对多的数据,查询对象与关联对象的所有数据
List lclasses = session.createQuery("from Classes c,Student s where c.cid = s.Classes.cid").list();
这种等值连接返回的结果值为封装到list集合中的Object数组,Object数组中封装了查询的记录数据,每一个记录对应着一个索引
这种方式遍历起来非常麻烦
等值连接查询对象与关联对象的所有信息的结果结构
使用等值连接结合构造方法 查询对象与关联对象中的指定字段的值
因为涉及了两个对象,查询的也是两个对象中的数据,我们需要在重新定义一个JavaBean,在JavaBean中定义我们需要的字段名称,并且提供有参构造方法和对应的getter和setter方法
最后在createQuery方法中使用hql声明即可,使用构造方法,并在构造方法中传入指定的参数字段,将查询的结果封装到构造方法指定的JavaBean中List
lclasses = session.createQuery(
"select new cn.itcast.domain.onetomany.ClassesView(c.cid,c.cname,s.sid,s.sname)
from Classes c,Student s where c.cid = s.Classes.cid").list();
使用等值连接结合构造方法,这种结合构造方法的查询方法返回的list集合中存储的是封装了查询指定字段结果的JavaBean对象
2、内连接 inner join
内连接查询时,也是需要建立起对象与关联对象之间的关系,而关系的建立取决于持久化类中维护的关系而不是表中维护的关系
使用内连接查询出对象与关联对象所有的信息
一对多的内连接建立
使用hql语句建立一对多的内连接时,左表是建立关系的持久化类 而右表是关联关系的持久化类
hql的内连接右表直接通过左表中的关联关系直接指定即可
/*
* 一对多的内连接建立
* 使用hql语句建立一对多的内连接时,左表是建立关系的持久化类 而右表是关联关系的持久化类
* hql的内连接右表直接通过左表中的关联关系直接指定即可
*/
List lcalsses = session.createQuery("from Classes as c inner join c.students").list();
这种查询方式返回的结果结构也是将查询的所有的字段信息统统封装到了Object数组中,最后再将Object数组存储到list集合中
使用构造方法结合内连接 查询出对象与关联对象的特定字段信息
List lclasses = session.createQuery("
select new cn.itcast.domain.onetomany.ClassesView(c.cid,c.cname,s.sid,s.sname) from Classes c inner join c.students s where s.Classes.cid = c.cid").list();
这种方式的查询结构会将查询结果封装到构造方法指定的JavaBean中,并且将JavaBean存储到list集合中
迫切内连接 fetch
迫切内连接
查询对象与关联对象所有的字段信息的方式
在查询方式inner join后加 fetch
inner join fetch
List<Classes> lclasses = session.createQuery("from Classes c inner join fetch c.students s").list();
迫切连接的实质就是将原本查询结果封装到Object数组的形式转变为封装到建立联系的JavaBean当中将数据封装到JavaBean中,并且将JavaBean存储到list集合中这样返回的list集合中就是封装了对应记录的JavaBean对象
fetch
迫切内连接 使用这种内连接查询的结果具有更好的数据封装结构,便于我们在页面中遍历操作
可以通过debug查看 使用迫切内连接后,查询结果返回的list集合中封装的是封装了数据的持久化对象的JavaBean对象,而不是封装了字段数据的Object数组,这样我们操作JavaBean的数据就会变很方便
迫切内连接结合构造方法查询对象与关联对象的指定字段的信息
使用迫切内连接来查询对象与关联对象指定字段的值
通过新建一个JavaBean的构造方法来查询
迫切查询是根据对象与关联对象的关系,将关联对象封装到对象中,最后再将对象封装到query对象查询的结果集中 list集合中
而当我们指定了迫切查询时,我们又需要使用构造方法指定一个新的JavaBean来查询指定的对象与关联对象的字段信息,这时就会造成list封装的结果矛盾的异常fetch迫切查询希望返回的结果封装到对象中,而构造方法希望我们可以将查询的结果封装到指定的构造方法创建的JavaBean中,所以会造成异常 我们只能使用其中一种方式
也就是说 使用了迫切查询 fetch 就只能查询对象与关联对象的所有字段信息,如果要想指定构造方法查询特定的字段的话,是会产生异常的
3、外连接
左外连接 left outer join
外连接的连接条件和内连接类似
使用外连接查询对象与关联对象的所有信息
List lclasses = session.createQuery(“from Classes c left outer join c.students s”).list();
同样这种查询方式,会将查询的字段信息全都封装到Object数组中
使用外连接 指定对象与关联对象的某些字段进行查询 依赖构造方法进行查询
当使用外连接 结果构造方法查询对象与关联对象的某些字段时
返回的list结果集中封装的是封装了查询结果的构造方法对应的JavaBean对象
List cvs = session.createQuery(“select new cn.itcast.domain.onetomany.ClassesView(c.cid,c.cname,s.sid,s.sname) from Classes c left outer join c.students s”).list();
迫切外连接
left outer join fetch
将对象与关联对象,作为结果封装到list集合中
在查询结果集的封装结构的时候,可以看到 from后面跟随的哪个持久化类对象,在结构中就是以谁建立的关系
List<Classes> classes = session.createQuery("from Student s left outer join fetch s.Classes c where s.Classes.cid = c.cid").list();
多对多的hql语句应用
多对多的hql应用和一对多的应用相同
hql语句的操作方式同样是 等值连接 内连接 和外连接
同样对查询所有的多对多表中的数据,可以提供迫切内连接和迫切外连接进行查询
当查询多对多表的关系的数据时,可能会造成数据的重复问题,可以将查询返回的list集合转换set集合,依据set集合元素唯一的特性进行去除重复
总结:
1、如果页面上要显示的数据和数据库中的数据相差甚远,利用带select的构造器进行查询是比较好的方案
2、如果页面上要显示的数据和数据库中的数据相差不是甚远,这个时候用迫切连接
3、如果采用迫切连接,from后面跟的那个对象就是结构主体
4、如果多张表进行查询,找核心表
实际开发中的问题:
当我们使用构造方法查询对象与关联对象的某些特定数据的时候,将会产生一个新的对象,往往在页面中使用新的对象的数据进行显示,而当对数据在进行操作的时候,新的对象就会被操作,而新产生的对象并不是原来的对象与关联对象,在操作的过程中,不会被DAO所识别,这里就牵扯了一个对象的转换问题