初识Hibernate之理解持久化类

     上一篇文章我们简单介绍了Hibernate相关的一些最基本的文件及其作用,并在最后完整的搭建了Hibernate的运行环境,成功的完成了与数据库的映射。但是至于其中的一些更加细节的地方并没有很详尽的解释,本篇则主要介绍Hibernate中的一个关键元素,持久化类。主要涉及以下一些内容:

  • 定义用作持久化类的基本要求
  • 持久化对象的几种不同状态及其相互之间的转换
  • 使用Hibernate完成对数据库的crud操作

一、定义用作持久化类的基本要求      所谓的持久化类其实本质上也就是一个普通的Java类,只不过我们通过配置文件让它与数据库中的某张表形成对应关系。虽然Hibernate号称低侵入式设计,对持久化类基本不做要求,但是实际上为了一些优化效率而言,遵守一定的规则则可以提高我们框架的运行效率。      首先,在该类中需要提供一个无参的构造器。因为我们的持久化类和数据库中具体的数据表形成了映射,那么我们从数据库中取出的数据都会被转换成持久化类的对象返回,这里的无参构造器就是用于框架在反射时构建持久化类对象时候使用的。当然,这一点一般不用我们关心,在Java类中如果没有显式指定构造器都会有一个默认的无参构造器。      其次,在该类中定义的属性,也就是用于与数据表的字段相对应的元素,它们需要满足Javabean规范,提供相对应的getter和setter方法。这一点毋庸置疑,和我们平常对类属性方法策略是相同的,但是如果有其他需要,也可以自定义访问策略,此处只是Hibernate建议。      最后,该类不能不定义为final类。在Hibernate中通过生成代理对象来优化框架性能是很常见的操作,而大部分生成代理的方式是通过javassist生成持久化的子类进行代理,如果持久化被定义为final,显然是无法进行代理的。

     还有一些其他的规则需要遵守,但是由于并不是强制要求且只有在某些场景下才具有相应的应用价值,此处暂时不做介绍,等到相应的场景再进行补充。

二、持久化对象的三种不同的状态      一个持久化对象对应于数据表中的一条记录,那么无论是对数据表的增加删除还是修改都是基于该持久化对象的。比如我想要插入一条记录到数据表中,我就可以new一个持久化对象并为其各个属性(对应于数据表的字段)赋值,然后映射到数据表中。这就是典型的以操作对象的方式操作数据库,但是这个持久化对象是有几种状态的,某个状态下对对象的修改是可以映射到数据表的,而某个状态下则不能作为持久化对象与数据表进行映射操作。而持久化对象主要有以下三个不同状态:

  • 瞬态:对象刚刚被new创建出来,只是一个普通的类对象。
  • 持久化:持久化对象与一个Hibernate Session相关联,在这个状态下,对象的所有属性值的改动,都是可以在事务结束时提交到数据库中的
  • 脱管:原本处于持久状态的对象因为其对应的Session被关闭,而失去持久化能力。此时的对象就处于脱管状态。一旦有Session愿意关联脱管对象,那么该对象就可以立马变为持久状态。

至于这三种不同状态下的相互转换可以用下面这张图很明显的表示出来:

其中,Transient表示瞬态,Persisitent表示持久化,Detached表示脱管状态。这张图同时也囊括了持久化对象的整个生命周期,至于其中各个方法的详细介绍,本篇的下一小节将陆续介绍,通过这些方法的调用来感受持久化对象的状态变化。

三、使用Hibernate完成对数据库的crud操作      上述主要介绍了有关Hibernate持久化对象的一些基本状态等内容,但是对于上图中具体方法调用后,持久化对象状态改变情况并不是很直观。本小节就将从具体代码执行的结果看这些状态之间的切换,至于一些配置文件的内容此处不再编写(详见上篇文章,此处节约篇幅突出重点),首先我们看insert操作。

1、持久化实体对象      持久化实体对象也可以理解为插入一条记录到数据表中,反正最终都是让我们new出来的持久化对象和数据表中的某一行相关联。例如:

Transaction transaction = session.beginTransaction();

UserInfo user = new UserInfo();
user.setName("single");
user.setAge(21);

session.save(user);
//提交事务
transaction.commit();
session.close();

此处只列出了最核心的一部分代码。我们首先创建了一个user的持久化对象,此时该对象只是一个普通Java对象并不具备持久化能力,这个状态就是瞬态。接着我们调用save方法,这个方法就会将user对象当前各个属性的值映射到数据库中,并且在save方法调用后,user这个对象此时的状态就变成了持久化状态。所以说,我们的插入操作也是持久化实体对象的一个过程。从Navicat中可以显然的看出来,新数据已经插入:

此时的user,只要session不关闭就可以不断的通过修改user属性的值来映射数据表。例如下面一段程序:

UserInfo user = new UserInfo();
user.setName("single");
user.setAge(21);

session.save(user);

user.setName("cyy");
//提交事务
transaction.commit();
session.close();

我们在save方法之后再次对user对象的属性进行修改,然后我们看保存到数据库中的是什么。

再看看控制台的输出:

显然,调用save方法,Hibernate会为我们生成一条insert语句,我们重新修改user对象的属性值,Hibernate又为我们生成了一条update语句。 但是在没有提交事务之前,所有的Sql语句对于数据库的操作都是预操作,并不会实际改变数据库。直到事务提交的时候,所有的操作才变为实际数据表的变化。还有几个和save相关的方法在这里简单介绍下:

  • Serializable save(Object var1):这是我们上述一直在使用的save方法,var1就是我们的持久化对象,通过调用该方法,Hibernate会为我们生成一条insert语句并立即对数据库进行一次预插入操作。最后返回该对象所对应的数据表中一行的主键值。
  • void persist(Object var1):这个方法所做的事情和save方法是一样的,都是将持久化对象的各个属性值去映射到数据表中的一行数据,只是不返回对应的主键的值。

除此之外,persisit方法和save方法还有一个重要区别。save方法在调用后会立马向数据库发送一条Sql,做一次预插入操作。而perisist方法采用懒加载机制,persist如果在事务之外调用,它不会立即向数据库发送Sql语句进行预插入,而是暂时被缓存直到清除缓存的时候才向数据进行插入。这样做有一个好处就是减少了对数据库的访问次数,但是缺点就在于数据库中的数据始终没有得到更新,容易产生脏数据读取。通过个例子看看:

UserInfo user = new UserInfo();
user.setName("single");
user.setAge(21);

session.save(user);

我们将这段代码从事务中抽离出来单独执行,通过打断点可以看到在save方法调用结束之时控制台输出的信息:

显然,save方法调用结束就会立马对数据库进行预插入操作。下面我们看persisit方法:

UserInfo user = new UserInfo();
user.setName("single");
user.setAge(21);
        
session.persist(user);
session.flush();

通过断点调试,可以看到perisist方法调用结束后,控制台并没有输出任何信息,反而在flush方法调用完毕之后,控制台输出insert语句。这就是persisit的懒加载思想,平常的一般操作首选save,在一些长会话流程的时候可以选择persist方法降低数据库压力。

2、根据主键加载持久化实体      以上我们可以通过save方法向数据库中插入一条记录,同样我们也可以使用get方法根据主键的值从数据库中加载出来一个持久化对象。下面我们看个例子,首先展示下userinfo表中内容:

接着我们使用Hibernate取出其中某条记录:

/*这里依然只是展示部分代码,说明问题即可*/
UserInfo user = (UserInfo)session.get(UserInfo.class,2);
System.out.println("name: "+user.getName()+",age: "+ user.getAge());

//提交事务
transaction.commit();

这里的get方法主要有两个参数,第一个参数指定要加载的数据表,第二个参数指定主键值。Hibernate将根据该主键的值进行加载,最后会返回一个Object对象。运行结果如下:

从运行结果来看,显然我们成功的根据主键值加载出来一个userInfo对象。除此之外,get方法调用结束后也会立即向数据库进行访问操作,这点和save方法是类似的。当然,如果主键的值不存在,那么将返回null,否则则会返回相对应的持久化对象。这里需要注意一点的是,我们的get方法是用于加载一个持久化对象的,而对于数据库的各种查询操作将在后文介绍。

3、更新持久化实体      除了insert和get,我们还可以通过操作持久化对象的属性值来修改数据表中的数据内容。例如:

UserInfo user = (UserInfo)session.get(UserInfo.class, 2);
user.setName("aaaa");
user.setAge(111);

session.update(user);

transaction.commit();
session.close();

程序比较简单,从数据库的变化来看,上述程序将主键值为2的记录name和age字段的信息做了修改。程序在控制台一共输出了两条Sql语句,一条是get方法调用结束生成的,一条是commit时候生成的update更新语句。也就是说update方法调用结束后并没有立即访问数据库,而是暂时存放在缓存中,等事务提交的时候在要求数据库执行。

3、删除持久化实体      Hibernate中提供delete方法通过持久化对象来删除数据表中的一行记录。例如:

//删除数据库中id为3的记录
UserInfo user = (UserInfo)session.get(UserInfo.class,3);

session.delete(user);

//提交事务
transaction.commit();
session.close();

首先我们获得数据表中id为3的一条记录的引用,然后直接调用delete方法删除该记录。同样的,Hibernate为我们生成两条Sql语句,一条是get生成的,一条是delete方法产生的,但是delete方法结束后并没有立即向数据库发送Sql语句,而是等到事务提交之时。

最后还要提到两个方法,这两个方法用于清除session中的持久化对象。

  • clear:调用该方法将清除与session绑定的所有持久化对象,这些对象统统变为脱管状态,或者说游离状态
  • evict:该方法有一个参数,调用该方法将显式指定清除session中的某个具体的持久化对象,调用完毕之后,该对象将处于游离状态

至此,有关持久化类及其生成的对象的相关知识,已经简单的介绍了,下篇我们将学习映射。总结不到之处,望指出。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏窗户

深入设计电子计算器(一)——CPU框架及指令集设计

前几天写了一篇《如何设计一个电子计算器》,一个朋友看了之后说实在太low,好吧,依照他的意思,那我就采用文中FPGA设计的方式,然后自己从指令集设计、cpu设...

1736
来自专栏三丰SanFeng

Linux Kernel CMPXCHG函数分析

最近看到Linux Kernel cmpxchg的代码,对实现很不理解。上网查了内嵌汇编以及Intel开发文档,才慢慢理解了,记录下来以享和我一样困惑的开发者。...

28010
来自专栏前端儿

JavaScript中常见的十五种设计模式

在JavaScript中并没有类这种概念,JS中的函数属于一等对象,在JS中定义一个对象非常简单(var obj = {}),而基于JS中闭包与弱类型等特性,在...

881
来自专栏王亚昌的专栏

让代码更有效率的方法

        老大总结的代码级提高代码执行效率需要注意的点,很值得和大家分享一下,在这儿也由衷地感谢下老大的总结和工作中的指导。大多数的点都在项目中验证过,比...

581
来自专栏Golang语言社区

一些Golang小技巧

今天给大家介绍3个我觉得比较有启发的Golang小技巧,分别是以下几个代码片段 nsq里的select写文件和socket io模块里的sendfile fas...

3479
来自专栏iKcamp

翻译 | 带你秒懂内存管理 - 第一部(共三部)

原文地址:A crash course in memory management 原文作者:Lin Clark 译者:黑黑 校对者:Bob 要理解为什么将 Ar...

1887
来自专栏Golang语言社区

Golang通用连接池

连接池在编程中并不少见,链接数据库,redis等操作都需要连接池,否则就会出现并发问题,如果每次操作都建立一条新的链接将会大大消耗资源,笔者也是在使用thrif...

1044
来自专栏Java技术

如何通过软引用和弱引用提升JVM内存使用性能!

初学者或初级程序员在面试时如果能证明自己具有分析内存用量和内存调优的能力,这相当有利,因为这是针对5年左右相关经验的高级程序员的要求。而对于高级程序员来说,如果...

622
来自专栏小樱的经验随笔

堆和栈的区别

一、预备知识—程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量...

3289
来自专栏蓝天

Oops错误

在at91rm9200下写了一个spi的驱动,加载后,运行测试程序时,蹦出这么个吓人的东西: Unable to handle kernel paging r...

571

扫码关注云+社区