一、Hibernate持久化类的编写规范
1.什么是持久化类
Hibernate是持久层的ORM影射框架,专注于数据的持久化工作。所谓持久化,就是将内存中的数据永久存储到关系型数据库中。那么知道了什么是持久化,什么又是持久化类呢?其实所谓的持久化类指的是一个Java类与数据库表建立了映射关系,那么这个类称为是持寺久化类。其实可以简单的理解为持久化类就是一个Java类有了一个映射文件与数据库的表建立了关系。那么我们在编写持久化类的时候有哪些要求呢?接下来我们来看一下:
2.持久化类的编写规则(应该遵循JavaBean的编写规范)
我们在编写持久化类的时候需要有以下几点需要注意:
举个例子:假设表中有一列员工工资,如果使用 double类型,如果这个员工工资忘记录入到系统中,系统会将默认值0存入到数据库,如果这个员工工资被扣完了,也会向系统中存入0。那么这个0就有了多重含义,而如果使用包装类类型就会避免以上情况,如果使用 Double类型,忘记录入工资就会存入null,而这个员工工资被扣完了,就会存入0,不会产生歧义。
持久化类我们已经可以正常编写了,但是在持久化类中需要有一个唯一标识OID与表的主键去建立映射关系。而且主键一般我们是不会让客户手动录入的,一般我们是由程序生成主键。那么Hibernate中也提供了相应的主键生成的方式,那么我们]来看下 Hibernate的主键生成策略。
Tips: Bean:在软件开发中指的是可重用的组件。 JavaBean:指的是用java语言编写的可重用组件,在实际项目中,domain、service、dao都可以看成是JavaBean。
Tips: Hibernate中的对象标识符OID(Object Identifier) Hibernate中把OID一直的对象,就认为是同一个对象,在同一个Session中不允许出现两个相同类型的对象的OID一致。 OID就是映射文件,对应数据库的 <id name="lkm_id"> <generator class="native"></generator> </id>
二、Hibernate主键生成策略
1.主键的类型
在讲解 Hibernate的主键生成策略之前,先来了解两个概念,即自然主键和代理主键,具体如下:
自然主键(业务主键):把具有业务含义的字段作为主键,称之为自然主键。例如在 customer表中,如果把name字段作为主键,其前提条件必须是:每一个客户的姓名不允许为null,不允许客户重名,并且不允许修改客户姓名。尽管这也是可行的,但是不能满足不断变化的业务需求,一旦出现了允许客户重名的业务需求,就必须修改数据模型,重新定义表的主键,这给数据库的维护增加了难度。
代理主键(逻辑主键):把不具备业务含义的字段作为主键,称之为代理主键。该字段一般取名为“ID”通常为整数类型,因为整数类型比字符串类型要节省更多的数据库空间。在上面例子中显然更合理的方式是使用代理主键。
2.主键生成策略
Hibernate中提供了几个内置的主键生成策略,其常用的主键生成策略的名称和描述如下:
名称 | 描述 |
---|---|
increment | (一般不用)用于long、short、int类型,由Hibernate自动以递增的方式生成唯一标识符,每次增量为1.只有当没有其它进程向同一张表中插入数据时才可以使用,不能在集群环境下使用。(先查询出当前最大值,在保存)适用于代理主键。 |
identity | 采用底层数据库本身提供的主键生成标识符,条件是数据库支持自动增长数据类型。在DB2、MySQL、MS SQL Server、Sybase和HypersonicSQL数据库中可以使用该生成器,该生成器要求在数据库中把主键定义为自增长类型。适用于代理主键。 |
sequence | Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列。适用于代理主键。(Oracle) |
native | 根据底层数据库对自动生成标识符的能力来选择identity、sequence、hilo(高低位算法:high low)三种生成器中的一种,适合跨数据库平台开发。适用于代理主键。 |
uuid | Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,其UUID被编码为一个长度为32位的十六进制字符串。这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。适用于代理主键。 |
assigned | 由java程序负责生成标识符,如果不指定id元素的 generator属性,则默认使用该主键生成策略。适用于自然主键。 |
三、Hibernate持久化对象的三种状态
1.持久化对象三种状态的描述(学习对象的状态是为了更好的去掌握hibernate操作数据库的方法)
了解了主键的生成策略之后,我们可以进一步来了解持久化类了。 Hibernate为了更好的来管理持久化类,将持久化类分成了三种状态。在 Hibernate中持久化的对象的三种状态分别是瞬时态、持久态和脱管态。一个持久化类的实例可能处于三种不同状态中的某一种,三种状态的详细介绍如下。
Tips:有时称为四种状态,还有删除态,它的状态特征为:有OID,和Session有关系,同时已经调用了删除方法,即将从数据库中把记录删除,但是事务还没有提交,此时的对象状态是删除态。
1.1瞬时态(临时态)
瞬时态也称为临时态或者自由态,瞬时态的实例是由new命令创建、开辟内存空间的对象,不存在持久化标识OID(相当于主键值),尚未与 Hibernate Session关联,在数据库中也没有记录,失去引用后将被JVM回收。瞬时状态的对象在内存中是孤立存在的,与数据库中的数据无任何关联,仅是一个信息携带的载体。
1.2持久态
持久态的对象存在持久化标识OID,加入到了 Session缓存中,并且相关联的 Session没有关闭,在数据库中有对应的记录,每条记录只对应唯一的持久化对象,需要注意的是,持久态对象是在事务还未提交前变成持久态的。
1.3托管态(游离态)
脱管态也称离线态或者游离态,当某个持久化状态的实例与 Session的关联被关闭时就变成了脱管态。脱管态对象存在持久化标识OID,并且仍然与数据库中的数据存在关联,只是失去了与当前Session的关联,脱管状态对象发生改变时 Hibernate不能检测到。
2.区分对象的三种状态
为了让大家更好地理解持久化对象的三种状态,下面通过一个实例为大家演示持久化对象的三种状态:
@Test
public void test1(){
Customer ccustomer=new Customer(); //瞬时状态对象:没有持久化标识OID,没有被session管理
custoemr.setCust_name("Kevin");
Session s=HibernateUtil.openSession();
Transaction tx=s.beginTransaction();
s.save(customer); //持久态对象:有持久化标识OID,被session管理
tx.commit();
s.close();
System.out.println(customer); //托管态对象:有持久化标识OID,没有被session管理
}
customer对象由new关键字创建,此时还未与 Session进行关联,它的状态称为瞬时态;在执行了 session. save( customer)操作后,customer对象纳入了 Session的管理范围,这时的 customer对象变成了持久态对象,此时 Session的事务还没有被提交;程序执行完 commit操作并关闭了 Session后,customer对象与 Session的关联被关闭,此时 customer对象就变成了脱管态。
3.Hibernate持久化对象三种状态转换
前面我们已经了解了持久化对象的三种状态,然后这三种状态是可以通过一系列方法互相进行转换的,下面来看持久化对象的状态演化图:
从图中可以看出,当一个对象被执行new关键字创建后,该对象处于瞬时态;当对瞬时态对象执行 Session的 save( ) 或 saveOrUpdate( ) 方法后,该对象将被放入 Session的一级缓存(在后面会有介绍),对象进入持久态;当对持久态对象执行 evict( )、 close( ) 或 clear( ) 操作后,对象进入脱管态;当直接执行 Session的get( )、 load( )、 find( )或 iterate( ) 等方法从数据库里查询对象时,查询到的对象也处于持久态;当对数据库中的纪录进行 save( ) 或 saveOrUpdate( ) 以及 lock( ) 等操作后,此时脱管态的对象就过渡到持久态;由于瞬时态和脱管态的对象不在 session 的管理范围,所以会在一段时间后被JVM回收。 持久化对象的三种状态可以通过调用 Session中的一系列方法实现状态间的转换,具体如下:
3.1瞬时态转换到其他状态 通过前面学习可知,瞬时态的对象由new关键字创建,瞬时态对象转换到其他状态总结如下:
由于持久化对象状态演化图中没有涉及到瞬时态转换到脱管态的情况,这里做下简要的说明,在前面学习中可知,脱管态对象存在OID,但是没有 Session的关联,也就是说脱管态和瞬时态的区别就是OID有没有值,所以可以通过为瞬时态对象设置OID,使其变成脱管态对象,示例如下:
Customer customer= new Customer(); //瞬时态
customer.setCust_id(1); //托管态
3.2持久态对象转换到其他状态 持久化对象可以直接通过 Hibernate中 Session的get( )、 load( )方法,或者 Query查询从数据库中获得,持久态对象转换到其他状态总结如下:
3.3脱管态对象转换到其他状态 脱管态对象无法直接获得,是由其他状态对象转换而来的,脱管态对象转换到其他状态总结如下:
由于持久化对象状态演化图中没有涉及到脱管态转换到瞬时态的情况,这里做下简要的说明,跟瞬时态转换到脱管态的情况相似,脱管态和瞬时态的区别就是OID有没有值,所以可以通过将脱管态对象的OID设置为null,使其变成瞬时态对象。例如在 session.close( )操作后,加入代码 customer.setCust_id( null ) ; 此时customer对象将由脱管态转换为瞬时态。
4.持久态对象能够自动更新数据库
持久态对象有一个非常重要的特性:持久态对象可以自动更新数据库,请看如下示例:
@Test
//测试持久态对象有自动更新数据库的能力
public void test()
{
Session session=HibernateUtil.getCurrSession();
Transaction transaction=session.beginTransaction();
//获得持久态对象
Customer customer=session.get(Customer.class, 1l); //持久态对象
customer.setCust_name("Kevin_Zhang");
//不用手动调用update就可以更新
transaction.commit();
//session.close(); 由于在HibernateUtil中,使用getCurrentSession函数将线程和session进行了绑定,所以无需在手动关闭
}
执行测试我们会发现,我们并没有手动调用 update( )方法,,Hibernate就可以将数据自动更新了。持久态对象就有这样的一个功能,持久态对象之所以有这样的功能其实都依赖了 Hibernate的一级缓
存。接下来我们就开始学习 Hibernate的一级缓存。
四、Hiberate的一级缓存
缓存是计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存。
Hibernate的缓存分为一级缓存和二级缓存,Hibernate的这两级缓存都位于持久化层,存储的都是数据库数据的备份。其中第一级缓存为 Hibernate的内置缓存,不能被卸载。
Tips:什么是缓存:内存中的临时数据 为什么使用缓存:减少和数据库交互的次数,从而提高效率 适用缓存的数据:经常查询的,并且不经常修改的。同时数据一旦出现问题,对最终结果影响不大的。 不适用于缓存的数据:不管是否经常查询,只要是经常修改的,都可以不用缓存。并且如果数据由于使用缓存,产生了异常数据,对最终结果影响很大,则不能使用。例如股市的牌价、银行的汇率、商品的库存等。
1.什么是Hibernate的一级缓存
Hibernate的一级缓存就是指 Session缓存,Session缓存是一块内存空间,用来存放相互管理的Java对象,在使用 Hibernate查询对象的时候,首先会使用对象属性的OID值在 Hibernate的一级缓存中进行查找,如果找到匹配OID值的对象,就直接将该对象从一级缓存中取出使用,不会再查询数据库;如果没有找到相同OID值的对象,则会去数据库中查找相应数据。当从数据库中查询到所需数据时,该数据信息也会放置到一级缓存中。 Hibernate的一级缓存的作用就是减少对数据库的访问次数。 在 Session接口的实现中包含一系列的Java集合,这些Java集合构成了 Session缓存。只要Session实例没有结束生命周期,存放在它缓存中的对象也不会结束生命周期。固一级缓存也被称为是 Session基本的缓存。Hibernate的一级缓存有如下特点:
2.测试Hibernate的一级缓存
我们可以通过以下示例来证明一级缓存是否存在:
@Test
//证明Hibernate的一级缓存确实存在
public void test1(){
Session session=HibernateUtil.openSession();
Transaction tx=session.beginTransaction();
//1.根据id查询客户
Customer customer1=session.get(Customer.class, 1l);//先在数据库查询,并把查询结果存入了一级缓存中,此时会产生查询的Sql语句
System.out.println(customer1);
//2.根据id再次查询客户
Customer customer2=session.get(Customer.class, 1l);//先去一级缓存中查看,如果有直接拿过来用,如果没有,再去查询,此时没有产生查询的Sql语句
System.out.println(customer2);
System.out.println(customer1==customer2);//true
tx.commit();
session.close();//session关闭,一级缓存消失
}
在以上代码中,第一次执行 Session的get( )方法获取 customer 1 对象时,由于一级缓存中没有数据,所以 Hibernate会向数据库发送一条SQL语句,查询 id 等于1的对象;当再次调用了 Session的get( )方法获取 customer 2 对象时,将不会再发送SQL语句,这是因为 customer 2 对象是从一级缓存中获取的。接下来,验证一下代码的执行结果是否和描述的相一致。
输出结果:
Hibernate:
select
customer0_.cust_id as cust_id1_0_0_,
customer0_.cust_name as cust_nam2_0_0_,
customer0_.cust_source as cust_sou3_0_0_,
customer0_.cust_industry as cust_ind4_0_0_,
customer0_.cust_level as cust_lev5_0_0_,
customer0_.cust_linkman as cust_lin6_0_0_,
customer0_.cust_phone as cust_pho7_0_0_,
customer0_.cust_mobile as cust_mob8_0_0_
from
cst_customer customer0_
where
customer0_.cust_id=?
Customer [cust_id=1, cust_name=Kevin_Zhang, cust_source=America, cust_industry=2, cust_level=2, cust_linkman=null, cust_phone=null, cust_mobile=null]
Customer [cust_id=1, cust_name=Kevin_Zhang, cust_source=America, cust_industry=2, cust_level=2, cust_linkman=null, cust_phone=null, cust_mobile=null]
true
从上图的输出结果中可以看出,执行第一次 session.get( )方法后, Hibernate向数据库发送了一条select语句,这说明此时 customer 1 对象是从数据库中查找的。执行输出语句输出 customer 1 对象中的数据后,继续执行程序,当执行到输出 customer 2 对象的代码处时,从上图的输出结果可以看出,customer 2 对象的查询结果被直接打印了,说明第二次调用 Session对象的 get( )方法没有向数据库发送 select语句,而是直接从一级缓存中获取 customer 2 对象 。
之前我们介绍过 Hibernate的持久态对象能够自动更新数据库,其实就是依赖了一级缓存,那么级缓存为什么就可以去更新数据库了呢,其实是因为一级缓存的一块特殊的区域就是快照区。
3.一级缓存的内部结构:(快照区)
Hibernate向一级缓存放入数据时,同时复制一份数据放入到 Hibernate快照中,当使用 commit 方法提交事务时,同时会清理 Session的一级缓存,这时会使用OID判断一级缓存中的对象和快照中的对象是否一致,如果两个对象中的属性发生变化,则执行 update 语句,将缓存的内容同步到数据库,并更新快照;如果一致,则不执行 update 语句。 Hibernate 快照的作用就是确保一级缓存中的数据和数据库中的数据一致,并且程序员是无法修改快照区的数据。
五、Hibernate的事务控制
Hibernate是对JDBC的轻量级封装,其主要功能是操作数据库。在操作数据库过程中,经常会遇到事务处理的问题,那么我们接下来就来介绍 Hibernate中的事务管理。
在学习 Hibernate中的事务处理之前,先来回顾一下什么是事务。
1.什么是事务
在数据库操作中,一项事务( Transaction ) 是由一条或多条操作数据库的SQL语句组成的一个不可分割的工作单元。当事务中的所有操作都正常完成时,整个事务才能被提交到数据库中,如果有一项操作没有完成,则整个事务会被回滚。
其实事务总结起来理解为:逻辑上的一组操作,组成这组操作的各个单元,要么一起成功,要么一起失败。
2.事务的四个特性
事务有很严格的定义,需要同时满足四个特性,即原子性、一致性、隔离性、持久性。这四个特性通常称之为ACID特性,具体如下:
3.事务的并发问题
在实际应用过程中,数据库是要被多个用户所共同访问的。在多个事务同时使用相同的数据时,可能会发生并发的问题,具体如下:
(1) 脏读:一个事务读取到另一个事务未提交的数据;
(2) 不可重复读:一个事务读到了另一个事务已经提交的 update 的数据,导致在同一个事务中的多次查询结果不一致; (3) 虚读 / 幻读:一个事务读到了另一个事务已经提交的 insert 的数据,导致在同一个事务中的多次查询结果不一致。
4.事务的隔离级别
为了避免事务并发问题的发生,在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同。
事务的隔离级别,是由数据库提供的,并不是所有数据库都支持四种隔离级别,常用数据库支持情况隔离级别情况如下:
● MySQL:READ_UNCOMMITTED、 READ_COMMITTED、 REPEATABLE_READ、SERIALIZABLE(默认 REPEATABLE_READ)。 ● Oracle:READ_UNCOMMITTED、 READ_COMMITTED、 SERIALIZABLE(默认 READ_COMMITTED)。
在使用数据库时候,隔离级别越高,安全性越高,性能越低。 实际开发中,不会选择最高或者最低隔离级别,选择 READ_COMMITTED( oracle默认)、REPEATABLE_READ( mysql默认)。
六、Hibernate的其它API
1.Query
Query代表面向对象的一个 Hibernate查询操作。在 Hibernate中,通常使用 session.createQuery( ) 方法接受一个HQL语句,然后调用 Query 的 list ( ) 或 uniqueResult ( ) 方法执行查询。所谓的HQL是Hibernate Query Language缩写,其语法很像SQL语法,但它是完全面向对象的。
在 Hibernate中使用 Query对象的步骤,具体所示:
了解了使用 Query对象的步骤后,接下来通过具体示例来演示 Query对象的查询操作:
//Query执行HQL语句
//1.查询所有记录:
//获取Query对象
Query query=session.createQuery("from Customer");
//2.获取结果集
List list=query.list();
for(Object o:list)
System.out.println(o);
//2.1条件查询:
//获取Query对象
Query query=session.createQuery("from Customer where cust_level=? and cust_name like ?");
//给参数占位符赋值
query.setString(0, "4"); //hibernate的参数占位符从0开始
query.setString(1, "%K%");
//2.获取结果集
List list=query.list();
for(Object o:list)
System.out.println(o);
//2.2条件查询:
//1.获取Query对象
//给参数起名需要使用:参数名称
Query query=session.createQuery("from Customer where cust_level=:level and cust_name like :name");
//给参数占位符赋值
//query.setString("level", "4"); //hibernate的参数占位符从0开始
//query.setString("name", "%K%");
query.setParameter("level", "3");
query.setParameter("name", "%k%");
//2.获取结果集
List list=query.list();
for(Object o:list)
System.out.println(o);
//3.分页查询:
Query q=s.createQuery("from Customer ");
q.setFirstResult(1);
q.setMaxResults(2);
//获取结果集
List list=q.list();
for(Object o:list)
System.out.println(o);
//4.排序查询
Query q=session.createQuery("from Customer order by cust_level desc");
List list=q.list();
for(Object o:list)
System.out.println(o);
//5.统计查询:
Query q=s.createQuery("select count(*) from Customer");
//List list=q.list();
//for(Object o:list)
//System.out.println(o);
Long count=(Long)q.uniqueResult();//当返回的结果唯一时,可以使用此方法。如果返回结果不唯一,使用了此方法会抛异常
System.out.println(count);
//6.投影查询
Query q=s.createQuery("select new com.Kevin.domain.Customer(cust_id,cust_name) from Customer");
List list=q.list();
for(Object o:list){
System.out.println("---------Element-----------");
System.out.println(o);
}
程序通过使用 Query接口,将 customer 表中的三条数据全部输出。此处只需了解 Hibernate中是如何使用 Query 接口进行数据查询的即可。
Query中除了使用 list( ) 方法查询全部数据外,还有其它一些常用方法,具体如下:
● setter方法:Query接口中提供了一系列的 setter 方法用于设置查询语句中的参数,针对不同的数据类型,需要用到不同的 setter方法。 ● iterator( ) 方法:该方法用于查询语句,返回的结果是一个 Iterator对象,在读取时只能按照顺序方式读取,它仅把使用到的数据转换成Java实体对象。 ● uniqueResult( ) 方法:该方法用于返回唯一的结果,在确保只有一条记录的查询时可以使用该方法。 ● executeUpdate( ) 方法:该方法是 Hibernate3的新特寺性,它支持HQL语句的更新和删除操作。 ● setFirstResult( ) 方法:该方法可以设置获取第一个记录的位置,也就是它表示从第几条记录开始查询,默认从0开始计算。 ● setMaxResult( ) 方法:该方法用于设置结果集的最大记录数,通常与 setFirstResult( ) 方法结合使用,用于限制结果集的范围,以实现分页功能。
2.Criteria
Criteria是一个完全面向对象,可扩展的条件查询API,通过它完全不需要考虑数据库底层如何实现,以及SQL语句如何编写,它是 Hibernate框架的核心查询对象。 Criteria查询,又称为QBC查询( Query By Criteria),它是 Hibernate的另一种对象检索方式。 org.hibernate.criterion.Criterion 是 Hibernate提供的一个面向对象查询条件接口,一个单独的查询就是 Criterion接口的一个实例,用于限制 Criteria对象的查询,在 Hibernate中 Criterion对象的创建通常是通过 Restrictions工厂类完成的,它提供了条件查询方法。 通常,使用 Criteria对象查询数据的主要步骤,具体如下:
了解了 Criteria对象的使用步骤后,接下来,通过具体示例来演示 Criteria对象的查询操作。
//Hibernate中的QBC查询示例
//基本查询
//获取Criteria对象
Criteria c=session.createCriteria(Customer.class);
//获取结果集
List list=c.list();
for(Object o:list)
System.out.println(o);
//条件查询
Criteria c=session.createCriteria(Customer.class);
//使用Criteria对象的add方法来添加条件
c.add(Restrictions.eq("cust_level","4"));
c.add(Restrictions.ilike("cust_name", "%k%"));
//c.add(Restrictions.or)
//获取结果集
List list=c.list();
for(Object o:list)
System.out.println(o);
//排序查询
Criteria c=session.createCriteria(Customer.class);
//添加排序
c.addOrder(Order.desc("cust_level"));
//获取结果集
List list=c.list();
for(Object o:list)
System.out.println(o);
//分页查询
//QBC的分页查询方法和HQL的分页查询方法的方法含义是一样的
Criteria c=session.createCriteria(Customer.class);
//设置分页条件
c.setFirstResult(0);
c.setMaxResults(3);
//获取结果集
List list=c.list();
for(Object o:list)
System.out.println(o);
//统计(投影)查询
Criteria c=session.createCriteria(Customer.class);
//设置使用聚合函数
//c.setProjection(Projections.rowCount());
c.setProjection(Projections.count("cust_id"));
//获取结果集
//List list=c.list();
//for(Object o:list)
//System.out.println(o);
Long count = (Long)c.uniqueResult();
System.out.println(count);
//离线查询
// 使用DetachedCriteria对象,该对象不需要获取session。可以直接得到
//使用DetachedCriteria对象实现的查询,称之为离线查询
@Test
public void test6(){
//获取离线对象不需要session
DetachedCriteria dc=DetachedCriteria.forClass(Customer.class);
//封装查询条件
dc.add(Restrictions.eq("cust_level","4"));
dc.add(Restrictions.ilike("cust_name", "%K%"));
List list=testService(dc);
for(Object o:list)
System.out.println(o);
}
private List testService(DetachedCriteria dc){
Session s=null;
Transaction tx=null;
try{
s=HibernateUtil.getCurrSession();
tx=s.beginTransaction();
List list=testDao(dc);
tx.commit();
return list;
}catch(Exception e){
tx.rollback();
}
return null;
}
private List testDao(DetachedCriteria dc){
Session s=HibernateUtil.getCurrSession();
//把离线对象转换成在线对象
Criteria c=dc.getExecutableCriteria(s);
return c.list();
}
在 Criteria 对象中,除了使用 criteria.list( ) 方法查询全部数据外,还有其它一些常用方法:如果只返回一个值时,可以使用 criteria 的 uniqueResult( ) 方法;如果需要分页时可以使用 setFirstResult( ) 和setMaxResult( ) 两个方法,setFirstResult( ) 方法表示从第几条记录开始查询,setMaxResult( ) 方法表示查询几条记录。