Hibernate 进阶

Hibernate 持久化类的编写规则

持久化类

HIbernate 是持久层的 ORM 映射框架,专注于数据的持久化工作。所谓持久化,就是将内存中的数据永久存储到关系数据库中。那么知道了什么是持久化,什么又是持久化类呢?

其实所谓的持久化类指的是一个java类与数据库表建立了映射关系,那么这个类称为持久化类。其实你可以简单的理解为持久化类就是一个 java 类有了一个映射文件与数据库的表建立了关系。

那么编写持久化类又有什么要求呢?

持久化类的编写规则

1. 持久化类需要提供无参数的构造方法
    在HIbernate 的底层需要使用反射生成类的实例
2. 持久化类的属性需要私有
	底层会将查询到的数据进行封装
3.	持久化类的属性要尽量使用包装类的类型
	包装类和基本数据类型默认值不同,包装类的类型语义描述更清晰,基本类型不易描述
4. 持久化类要有一个唯一标识 OID 与表的主键对应
	Hibernate 中需要通过这个唯一标识 OID 分区在内存中是否是同一个持久化类
	HIbernate 是不允许在内存中出现两个 OID 相同的持久化类
5. 持久化类不要使用 final 关键字进行修饰
	延迟加载机制中需要用到该类来生成一个子类的代理对象。后面会讲到。

主键类型

自然主键(少见)

表的业务列中,有某业务列符合,并且不重复的特征时,该列可以作为主键使用。
代理主键(常见)

表的业务列中,没有某业务列符合,并且不重复的特征时,创建一个没有业务意义的列作为主键。

主键生产策略

代理主键
	1.increment 由Hibernate自动以递增的方式生成主键,每次增量为1 ,会执行两个sql语句,先从表中查找出最大的id,然后加一,插入当前数据
	2.identity  由底层数据库生成主键,依赖数据库的主键自增功能
	3.sequence  由底层数据库的序列来生成主键,前提是数据库支持序列。(mysql不支持,oracle支持)
	4.hilo      Hibernate根据hilo算法来自己生成主键。
	5.native    根据底层数据库对自动生成主键的支持能力选择 identity|sequence|hilo
	6.uuid     采用UUID算法生成主键
自然主键
	assigned:自然主键生成策略. hibernate不会管理主键值.由开发人员自己录入.
	例如指定身份证号为主键值

持久化对象的三种状态

Hibernate 为了更好的来管理持久化类,将持久化类分成了三种状态,分别为瞬时态、持久态和托管态。一个持久化类的实例可能处于三种不同状态的一种,三种状态的详细介绍如下

瞬时态(transient)

瞬时态的实例是由 new 创建、开辟内存空间的对象,不存在持久化标识 OID ,未与 Session 关联,在数据库中也没有记录,失去引用后将被 JVM 回收。瞬时状态的对象在内存中是孤立存在的,与数据库中的数据无任何关联,仅是一个信息携带的载体(没有 id,没有在 session 缓存中)

持久态(persistent)

持久化的对象存在持久化标识 OID ,加入到了 Session 缓存中,并且关联的 session 没有关闭,在数据库中有对应的记录,每条记录只对应唯一的持久化对象,需要注意的是,持久化对象是在事务还没提交前变成持久态的(有 id,在 session 缓存中)

脱管态(detached)

也叫游离态,当某个持久化状态的实例与 Session 的关联被关闭时就变成了脱管态。托管态对象存在持久化表示 OID,并且仍然与数据库中的数据存在关联,只是失去了与当前 Session 的关联,脱管状态对象发生改变 Hibernate 不能检测到(有 id,没有在 session 缓存中)

区分三种状态的一个简单例子

	@Test
	// 查看三种状态
	public void fun1() {
		// 1 获得session
		Session session = HibernateUtils.openSession();
		// 2 控制事务
		Transaction tx = session.beginTransaction();
		// 3执行操作
		Customer c = new Customer(); // 没有id, 没有与session关联 => 瞬时状态

		c.setCust_name("雷军"); // 瞬时状态

		session.save(c); // 持久化状态, 有id,有关联

		// 4提交事务.关闭资源
		tx.commit();
		session.close();// 游离|托管 状态, 有id , 没有关联

	}
save() 方法: 其实不能理解成保存.理解成将瞬时状态转换成持久状态的方法
当主键生成策略为identity,执行save方法时,为了将对象转换为持久化状态.必须生成id值.所以需要执行insert语句生成.
但如果把主键生成策略改为increment 时,执行save方法,为了生成id.会执行查询id最大值的sql语句.此时并没有执行insert语句,所以save()不能理解成保存

持久化状态特点: 持久化状态对象的任何变化都会自动同步到数据库中.

	@Test
	public void fun3() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		Customer c = session.get(Customer.class, 1l);// 持久化状态对象
		//不用手动调用update方法就可以更新
		c.setCust_name("微软公司");
		tx.commit();
		session.close();// 游离|托管 状态, 有id , 没有关联
	}

三种状态的转换演化图

在这里插入图片描述

Hibernate 的一级缓存

缓存,介于应用程序和永久数据存储源之间,作用是为了降低应用程序对物理数据源访问的频率,从而提高应用的运行性能。

例如我们 cpu 执行效率每秒处理的数据高达上千兆,而我们的硬盘读取速度却没那么高,读取几百兆,这时候我们使用缓存来存储数据,存储满后一次性交由 cpu 处理。

Hibernate 中也存在缓存,同样是为了提高效率。Hibernate 的缓存包括 Session 的缓存和 SessionFactory 的缓存。

Session 的缓存是内置的,不能被卸载,也被称为 Hibertnate 的一级缓存。

SessionFactory 有一个内置缓存和外置缓存。SessionFactory 的外置缓存是一个可配置的缓存插件。默认情况下,Hibernate 不会启用这个缓存插件。被称为 Hibernate 的二级缓存。这里只详解以及缓存,想了解二级缓存,自行学习。

Session 缓存

Session对象中具有一个缓存。Session 的缓存是一块内存空间,存放的是持久化对象。

当 Session 通过 save 方法持久化一个对象时,该对象被加入到 Session 缓存中。
当 Session 通过 get 方法获取一个持久化对象时,Session 会先判断 Session 缓存中是否存在这个对象,如果存在,就不需要再从数据库中查找。

        //开启事务
        Transaction ts=session.beginTransaction();
        //加上断点,当我们执行完这一步,会打印select语句,而后面的都不会打印,说明并没有从数据库中获取
        Customer c1 = session.get(Customer.class, 1l);   
        //这次get方法会先从session缓存中查找,由于已经存在,直接返回引用 
        Customer c2 = session.get(Customer.class, 1l);
        Customer c3 = session.get(Customer.class, 1l);
        System.out.println(c1==c2);//true
        System.out.println(c1==c3);//true
        session.close();

缓存原理如下:
在这里插入图片描述

脏检查及清理缓存的机制

我们先来看下面的例子

        Transaction ts=session.beginTransaction();
        Customer c1 = session.get(Customer .class, 1l);
        c1.setcCust_name("老马");
        ts.commit();

我们发现我们改变了 Name 属性,这时候 session 缓存中的对象的 name 属性和数据库表中的 NAME 字段不一致了。但是我们并没有进行更新操作,而是直接提交了事务

幸运的是,Session 中在清理缓存的时候,会自动进行脏检查。如果发现 Session 缓存中的持久化对象和数据库中的记录不一致,就会根据对象的最新属性去更新数据库。
所以在本例中,Session 会自动提交一个 update 语句对数据库进行更新。

Session 是怎样进行脏检查的呢?

当一个对象被加入到 Session 缓存中时,Session 会为该对象复制一份快照。当 Session 清理缓存时,会比较当前对象的属性和快照来判断是否发生变化,如果发生变化,就会根据最新属性来执行相关的更新操作。

原理如下图所示:
在这里插入图片描述

我们看下面一个例子加深对快照的理解

        //我们从数据库中取出 id为5,name为tom,password为123456的对象
        Transaction ts=session.beginTransaction();
        Customer c1=session.get(Customer .class, 5);
        session.update(c1);
        session.close(); 

        过程:获取了持久化对象,放入缓存中,并创建了快照,我们执行更新,
        Session缓存中的对象会和快照进行比较,没有任何变化,所以不会执行update语句。
        //我们自己设置一个和数据库中一模一样的对象,这时候会打印update语句
        Transaction ts=session.beginTransaction();
        Customer user=new Customer ();
        Customer .setCust_id(5l);
        Customer .setCust_name("老黄");
        session.update(c1);
        ts.commit();
        session.close();

        过程:因为此时我们执行update语句时会将对象直接放入缓存中,但是没有持久化对象的快照,
        所以进行对比结果就是不一致,所以尽管什么都没更改,还是会执行update语句,在控制台打印。

什么时候会清理缓存呢?

-默认情况下,在调用commit()方法时会先清理缓存。再向数据库提交事务。 

-当执行查询操作时,如果缓存中的持久化对象属性已经发生了改变,就会先清理缓存,同步数据,保证查询到的是正确的结果。 

-当应用程序显式调用Session的flush()方法时  

-Session清理缓存的例外情况,如果对象使用的是native生成策略生成OID,那么调用Session的save()方法来保存该对象时,会立刻执行向数据库的插入语句。

如果不希望 Session 在以上默认的时间点清理缓存,可以通过 Session 的 setFlushMode()方法来设定清理缓存的时间点。
FlushMode 类定义了三种清理模式。

                            各种查询方法          commit()方法        flush()方法 
-FlushMode.AUTO(默认模式)        清理                 清理                清理
-FlushMode.COMMIT              不清理                清理                清理 
-FlushMode.NEVER               不清理                不清理              清理   

例如Session.setFlushMode(FlushMode.AUTO) 

ps:一级缓存这一部分参考与 https://blog.csdn.net/c99463904/article/details/72812996

Hibernate 中的事务

事务的基本知识我在前面已经学习过,这里我放个链接,需要了解学习的点击这里 https://blog.csdn.net/PNGYUL/article/details/80271652

我们目前所写的例子中,事务操作都在 dao 层。然而在实际开发中,事务操作是在 service 层中。

在 dao 层操作数据库需要用到 session 对象。在 service 控制事务也是使用 session 对象完成.。我们要确保 dao 层和 service 层使用的使用同一个 session 对象

在 hibernate 中,确保使用同一个 session 的问题, hibernate 已经帮我们解决了. 我们开发人员只需要调用 sf.getCurrentSession() 方法即可获得与当前线程绑定的 session 对象

一个简单例子

//在hibernate中指定数据库的隔离级别
 <!-- 指定hibernate操作数据库时的隔离级别 
			#hibernate.connection.isolation 1|2|4|8		
			0001	1	读未提交
			0010	2	读已提交
			0100	4	可重复读
			1000	8	串行化
 -->
<property name="hibernate.connection.isolation">4</property>
//service层
public class CustomerServiceImpl implements CustomerService {
	private  CustomerDao customerDao = new CustomerDaoImpl();
	@Override
	public void save(Customer c) {
		
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		try {
			customerDao.save(c);
			
		} catch (Exception e) {
			tx.rollback();
		}
		tx.commit();
	}
	}
//dao层
public class CustomerDaoImpl implements CustomerDao {

	@Override
	public void save(Customer c) {

		Session session = HibernateUtils.getCurrentSession();

		session.save(c);
    }
	}

PS:最近有一个困惑,不知道你们某些写技术博客的大牛有没有这种感觉。就是在整理、学习某个知识点的时候会上网搜索几篇相关的文章来学习,然后觉得他们某些文章写的很好,很好理解,在自己写文章的时候就会带上他们比较好的思维,有点像洗稿。

其实在参考哪位大神哪个知识点的时候,我一般都会在对应的知识点下附上了参考文章来源!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值