环境搭建参考我的第一天:https://blog.csdn.net/GreatDistance/article/details/84594662
Hibernate学习第二天
1.1持久化类的概述
1.1.1什么是持久化类
持久化:将内存中的一个对象持久化到数据库中的过程,Hibernate框架就是用来持久化的框架。
持久化类:一个java对象与数据库中的表建立了映射关系,那么这个类在Hibernate中被称为持久化类。持久化类 = java类 + 映射文件
1.1.2持久化类的编写规则
- 必须提供一个无参数的public构造方法 Hibernate底层需要使用反射生成实例
- 所有属性要private ,对外提供public 的get/set方法 Hibernate获取,设置对象的值
- 在持久化类必须提供一个标识属性,让它与数据库中的主键对应,我们管这个属性叫OID Hibernate中通过持久化类的OID的属性来区分是否是同一个对象
- 持久化类中的属性尽量使用基本数据类型的包装类. 基本类型的默认值有时会产生歧义
- 持久化类它不能使用final修饰符 延迟加载时Hibernate本身的一种优化手段,返回的是一个代理对象(javassist)可以对没有实现接口的类产生代理---使用了非常底层字节增争强技术,继承这个类进行代理,如果不能继承就不能产生代理对象,延迟加载就失效。(可在第一天的代码中将Users对象用final修饰 调试运行get()和load()方法 debug作比较)
1.2主键生成策略
1.2.1主键的分类
Hibernate中定义的主键类型包括:自然主键和代理主键:
自然主键:具有业务含义字段作为主键,比如:学号、身份证号
代理主键:不具有业务含义字段作为主键(例如 自增id),比如:mysql自增主键,oracle序列生成的主键、uuid()方法生成的唯一序列串
建议:企业开发中使用代理主键!一旦主键参与到业务逻辑中,后期有可能需要修改源代码。
好的程序设计应满足OCP原则,对程序的扩展是开放的,对修改源代码是关闭的。
1.2.2Hibernate主键的生成策略
在实际开发中一般不允许用户手动设置主键,一般将主键叫给数据库,手动编写程序进行设置,在Hibernate中为了减少程序编写,提供了很多的主键生成策略。见下表
主键生成器 |
描述 |
increment |
代理主键:由hibernate维护一个变量,每次生成主键时自动以递增。 问题:如果有多个应用访问一个数据库,由于每个应用维护自己的主键,所以此时主键可能冲突。建议不采用。 优点:可以方便跨平台 缺点:不适合高并发访问 |
identity |
代理主键:由底层数据库生成表识符。条件是数据库支持自动增长数据类型。比如:mysql的自增主键,oracle不支持主键自动生成。 如果数据库支持自增建议采用。 优点:由底层数据库维护,和hibernate无关 缺点:只能对支持自动增长的数据库有效,例如mysql |
sequence |
代理主键:Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列。比如oracle的序列。 如果数据库支持序列建议采用。 优点:由底层数据库维护,和hibernate无关 缺点:数据库必须支持sequence方案例如oracle |
native |
代理主键:根据底层数据库对自动来选择identity、sequence、hilo 由于生成主键策略的控制权由hibernate控制,所以不建议采用。 优点:在项目中如果存在多个数据库时使用 缺点:效率比较低 |
uuid |
代理主键:Hibernate采用128bit位的UUID算法来生成标识符。该算法 能够在网络环境中生成唯一的字符串标识符。 此策略可以保证生成主键的唯一性,并且提供了最好的数据库插入性能和数据库平台的无关性。建议采用。 优点:与数据库无关,方便数据库移植,效率高,不访问数据库就可以直接生成主键值,并且它能保证唯一性。 缺点:uuid长度大(32位),占用空间比较大,对应数据库中类型 char varchar |
assigned |
自然主键:由java程序负责生成标识符。 不建议采用。 尽量在操作中避免手动对主键操作 |
可以更改 <property name="hibernate.hbm2ddl.auto">update</property>中的值来演示。
1.3 Hibernate持久化对象状态
1.3.1持久化类的三种状态
Hibernate是持久层框架,通过持久化类完成ORM操作。Hibernate为了更好地管理持久化类,将持久化类分为三种状态。
- 瞬时态:也叫做临时态或自由态,它一般指我们new出来的对象,它不存在OID,与hibernate session无关联,在数据库中也无记录。它使用完成后,会被jvm直接回收掉,它只是用于信息携带。
简单说:无OID 与数据库中的信息无关联,不在session管理范围内。
- 持久态:在hibernate session管理范围内,它具有持久化标识OID它的特点,在事务未提交前一直是持久态,当它发生改变时,hibernate是可以检测到的。
简单说:有OID 由session管理,在数据库中有可能有,也有可有没有。
- 托管态:也叫做游离态或离线态,它是指持久态对象失去了与session的关联,托管态对象它存在OID,在数据库中有可能存在,也有可能不存在。
对于托管态对象,它发生改变时hibernet不能检测到。
1.3.2区分三种状态
@Test
public void testStatus() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 使用get方法查询
Users users = new Users(); // 瞬时态(无OID 无session管理)
users.setName("lisa");
users.setAge(19);
users.setSex("女");
session.save(users); // 持久态(有唯一的OID 有session管理)
//6.提交事务
transaction.commit();
//7.资源释放
session.close();
System.out.println("姓名:"+users.getName()); // 托管态 有唯一的OID 无session管理
}
1.3.3持久化类的状态转换
判断持久化类对象三种状态:
- 是否有OID
- 判断是否与session关联
持久化类对象三种状态的状态转换图
1.瞬时态(new 出来的)
瞬时---->持久 save saveOrUpdate
瞬时---->脱管(游离) 手动设置OID
2.持久态 它是由session管理
持久------>瞬时 delete() 被删除后持久化对象不在建议使用
持久----->脱管 注意:session它的缓存就是所说的一级缓存
evict(清除一级缓存 中指定的一个对象)
clear(清空一级缓存)
close(关闭,清空一级缓存)
3.脱管态 (它是无法直接获取)
脱管----->瞬时 直接将OID删除
脱管---->持久 update saveOrUpdate
1.3.4持久化对象可以自动更新数据库
测试代码:
@Test
public void testAutoUpdate() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 使用get方法查询
Users users = session.get(Users.class, 1L);
users.setName("刘强");
// session.update(users); // 注销本句也能修改数据库
//6.提交事务
transaction.commit();
//7.资源释放
session.close();
}
1.4Hibernate一级缓存
1.4.1什么是缓存
缓存:是一种优化方式,将数据存入到内存中,使用上的时候直接从缓存中获取,无需通过储存源来获取。
1.4.2hibernate的一级缓存
Hibernate框架中提供了优化手段、缓存、抓取策略。Hibernate提供了两种缓存机制。一级缓存、和二级缓存。
Hibernate中的一级缓存成为是Session级别的缓存,一级缓存生命周期和Session生命周期一致(一级缓存由Session中的一些列集合构成)一级缓存是hibernate框架自带的不可卸载。(hibernate的二级缓存是SessionFactory级别的缓存,需要配置才能生效)。
Hibernate的一级缓存就是指session缓存。
actionQueue它是一个行列队列,它主要记录crud操作的相关信息
persistenceContext它是持久化上下文,它其实是真正缓存。
在session中定义了一系列的集合来存储数据,它们构成session缓存。
只要session没有关闭,它就会一直存在。
当我们通过hibernate中的session提供的一些API例如 save get update等进行操作时,就会将持久化对象保存到session中,当下一次在去查询缓存中具有的对象(OID值来判断),
就不会去从数据库查询,而是直接从缓存中获取。
Hibernate的一级缓存存在的目的就是为了减少对数据库访问。
1.4.3证明hibernate一级缓存存在
测试代码:
@Test
public void testCacheExist() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 使用get方法查询
Users users1 = session.get(Users.class, 1L); // 发送SQL语句
Users users2 = session.get(Users.class, 1L); // 不发送SQL语句
System.out.println(users1 == users2); // 返回true
//6.提交事务
transaction.commit();
//7.资源释放
session.close();
}
1.4.4一级缓存中特殊区域:快照区
Hibernate向一级缓存中存数据,它的底层使用一个map来存储数据的。Map的key存储的是一级缓存对象value存储的是快照。
当事务提交,session关闭向数据库发送请求时会判断一级缓存中的数据与快照区的数据是否一致如果不一致会发送update语句。
1.4.5一级缓存常用API
一级缓存特点:
- 当我们通过session的save,update saveOrupdate进行操作时,如果一级缓存中没有对象,会将这些对象从数据库中查询到,存储到一级缓存。
- 当我们通过session的load,get,Query的list等方法进行操作时,会先判断一级缓存中是否存在,如果没有才会从数据库获取,并且将查询的数据存储到一级缓存中。
- 当调用session的close方法时,session缓存清空。
clear 清空一级缓存.
evict 清空一级缓存中指定的一个对象。
refresh重新查询数据库,用数据库中信息来更新一级缓存与快照
1.5hibernate的事物管理
1.5.1什么是事务
所谓事务是用户定义的一个数据库操作序列,这些操作要么全做,要么全部做,是一个不可分割的工作单位。
1.5.2事务的特性
原子性:事务是数据库的逻辑工作单位,事务中包括的诸操作要么全做,要么全不做。
一致性:事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性:一个事务的执行不能被其他事务干扰。
持久性:一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
1.5.3如果不考虑隔离性,引发安全性问题
读问题
读脏数据
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
不可重复读
这是由于查询时系统中其他事务修改的提交而引起的。比如事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。
幻读
幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样.一般解决幻读的方法是增加范围锁RangeS,锁定检索范围为只读,这样就避免了幻读。
1.5.4读问题的解决
设置事务的隔离级别
Read uncommitted(读未提交): 最低级别,任何情况都会发生。
Read Committed(读已提交): 可避免脏读的发生。
Repeatable read(可重复读): 可避免脏读、不可重复读的发生。
Serializable(串行化): 避免脏读、不可重复读,幻读的发生。
1.5.5设置事务的隔离级别
在hibernate核心配置文件中配置事务隔离级别src/hibernate.cfg.xml
<!--
事务隔离级别
hibernate.connection.isolation=4
1---Read uncommitted isolation
2---Read Committed isolation
4---Repeatable read isolation
8---Serializable isolation
-->
<!-- 设置事务隔离级别 -->
<property name="hibernate.connection.isolation">4</property>
1.5.6hibernate配置事务管理
改写工具类(使用线程绑定的方法来确保事物中的Connection是同一个对象):HibernateUtils.java
package com.utils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/**
* Hibernate工具类
*/
public class HibernateUtils {
public static final Configuration CONFIGURATION;
public static final SessionFactory SESSION_FACTORY;
static {
CONFIGURATION = new Configuration().configure();
SESSION_FACTORY = CONFIGURATION.buildSessionFactory();
}
public static Session openSession(){
return SESSION_FACTORY.openSession();
}
public static Session getCurrentSession(){
return SESSION_FACTORY.getCurrentSession(); // 默认是不开启的必须在核心配置文件中配置了才能使用
}
}
核心配置文件中配置当前线程绑定的Session
<!-- 配置当前线程绑定的Session -->
<property name="hibernate.current_session_context_class">thread</property>
测试代码:
@Test
public void testGetCurrentSession() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 使用get方法查询
Users users = new Users();
users.setName("赵六");
users.setSex("男");
session.save(users);
//6.提交事务
transaction.commit();
//7.资源释放
// session.close(); // 不能使用该句 因为getCurrentSession是线程级别的 线程结束后会自动关闭
}
1.6hibernate其他API
1.6.1 Query
Query接口用于接收HQL(Hibernate Query Language),查询多个对象。
1.6.2Criteria
Criteria:QBC(Query By Criteria)条件查询
1.6.3 SQLQuery
SQLQuery用于接收SQL语句,特别复杂情况下使用,一般情况Query、Criteria就能解决。
事务的测试代码:
// 普通查询
@Test
public void testQuery() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 通过Session获取Query
String hql = "from Users";
Query query = session.createQuery(hql);
List<Users> list = query.list();
for (Users users : list)
System.out.println(users);
transaction.commit();
}
// 条件查询
@Test
public void testQuery1() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 通过Session获取Query
String hql = "from Users where name like ?";
Query query = session.createQuery(hql);
query.setParameter(0, "刘%");
List<Users> list = query.list();
for (Users users : list)
System.out.println(users);
transaction.commit();
}
// 分页询
@Test
public void testPageQuery() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 通过Session获取Query
String hql = "from Users";
Query query = session.createQuery(hql);
query.setFirstResult(0); // 设置开始limit的第一个参数
query.setMaxResults(3); // 每页显示多少个记录
List<Users> list = query.list();
for (Users users : list)
System.out.println(users);
transaction.commit();
}
@Test
public void testCriteria() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 通过Session获取Criteria
/*
// 1、普通查询
String hql = "from Users";
Criteria criteria = session.createCriteria(Users.class);*/
/* // 2、条件查询
Criteria criteria = session.createCriteria(Users.class);
criteria.add(Restrictions.like("name","刘%"));*/
// 3、分页查询
Criteria criteria = session.createCriteria(Users.class);
criteria.setFirstResult(0); // 设置开始limit的第一个参数
criteria.setMaxResults(3); // 每页显示多少个记录
List<Users> list = criteria.list();
for (Users users : list)
System.out.println(users);
transaction.commit();
}