一、 Hibernate中的实体
我们在私有化一个变量的时候,不能把它称为属性。如:private int id
,该段代码称为一个成员变量。当它生成getter/setter方法的时候,才能称为一个属性。实体类也被称为持久化类(将内存中的数据永久存储到关系型数据库中)
- 规则一:实体类也称持久化类,需要提供无参构造方法。因为Hibernate底层需要使用反射生成类的实例。
- 规则二:成员变量私有化,提供公有的getter/setter方法,转化为属性。因为在hibernate底层会将查询到的数据进行封装。
- 规则三 :持久化类中的属性,应当尽量使用包装类型。如我们的int 最好改为Integer,lon最好转换为Long类型,因为包装类和基本数据类型的默认值是不同的,包装类的类型语义描述更加清晰。如如果使用double类型,如果一个员工的工资忘记添加,那么系统会默认设置为0。等于员工没有工资,如果设置为Double,那么忘记录入工资,会存入null。并不表示0,没有工资。
- 规则四:持久化类需要提供主键与数据库中的主键对应。在java中是通过地址来区分是否是同一个对象的,而Hibernate是通过关系型数据库的主键来区分的。Hibernate不允许在内存中出现两个主键相同的持久化对象。
- 规则五:不要用final修饰class。hibernate使用cglib生成代理对象是继承被代理的对象,如果被final修饰,将无法生成代理。
二、 Hibernate主键生成策略
1. 主键的类型
数据库中的主键分两种类型,一种是自然主键
,另外一种是代理主键
- 自然主键:把具有业务字段含义的字段作为主键,称为自然主键。如我们把name作为主键。但是前提条件是必须每条数据的姓名不能为null。也就是除id以外的其他字段
- 代理主键:把不具备业务含义的字段作为主键。称为代理主键。一般都为ID,且为整数类型。因为整数类型比字符串类型更加节省数据库空间
2. hibernate的主键生成策略类型
我们在书写orm元数据的时候,会有如下配置
<id name="cust_id" >
<generator class="native"></generator>
</id>
<!--
generator:主键生成策略,就是每条记录录入时,主键的生成规则(7个)
identity:主键自增,有数据库来维护主键值,录入时不需要指定主键
increment:主键自增,由hibernate来维护,每次插入前会先查询表中id最大值+1作为新主键值(正是开发不需要使用)且由线程安全问题
sequence:Oracle中的主键生成策略
hilo:高低位算法(开发不使用)
native:自动选择选择 identity、sequence、hilo三种生成器中的一种
uuid:产生随机字符串作为主键,逐渐乐星必须用String类型的主键
assigned:自然主键生成策略,由用户手动设置id值
-->
在Hibernate提供了几个内置的主键生成策略,其常用主键生成策略如下:
名称 | 描述 |
---|---|
increment | 用于long、short、或 int类型,由Hibernate 自动以递增的方式生成唯一标识符,每次增量为1。只有当没有其它进程向同一张表中插入数据时才可以使用,不能在集群环境下使用。适用于代理主键。 |
identity | 采用底层数据库本身提供的主键生成标识符,条件是数据库支持自动增长数据类型。在DB2、MySQL、MS SQL Server、Sybase 和HypersonicSQL数据库中可以使用该生成器,该生成器要求在数据库中把主键定义成为自增长类型。适用于代理主键。 |
sequence | Hibernate 根据底层数据库序列生成标识符。条件是数据库支持序列。适用于代理主键。 |
native | 根据底层数据库对自动生成表示的能力来选择 identity、sequence、hilo三种生成器中的一种,适合跨数据库平台开发。适用于代理主键。 |
uuid | Hibernate 采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,其UUID被编码为一个长度为32位的十六进制字符串。这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。适用于代理主键。 |
assigned | 由java程序负责生成标识符,如果不指定id元素的generator属性,则默认使用该主键生成策略。适用于自然主键。 |
hilo | 高低位算法 |
三、 Hibernate中的实体类的对象的状态
实体类具有三种状态,分别为瞬时状态
,持久化状态
,游离或托管状态
。
- 瞬时状态:没有id,没有在sesson缓存中。
- 持久化状态:由id且在session缓存中
- 托管状态:有id没有在session缓存中
在代码中表现如下:(注意注释)
Session session = HibernateUtils.openSession();
//2.开启事务
Transaction tx = session.beginTransaction();
//3.执行操作
Customer customer =new Customer();
//没有id,且没有与Session关联——>瞬时状态
customer.setCust_name("明辉企业");
session.save(customer);
//有id,且与Session关系——>持久化状态
//4.事务提交
tx.commit();
session.close(); //游离状态
实际上,我们进行增删查改这一系列操作,就是将我们的实体对象的这三个状态进行改变,如:查询,是直接获取到对象的持久化状态,更新是从游离状态转换为持久状态。删除操作时将我们实体类对象从持久化状态转为瞬时状态。
-
当一个对象被执行new关键字创建后,该对象处于瞬时态;
-
当对瞬时态对象执行Session的save()或saveOrUpdate()方法后,该对象将被放入Session的一级缓存,对象进入持久态;
-
当对持久态对象执行evict()、close(或 clear()操作后,对象进入脱管态;
-
当直接执行Session的get()、loadO、find(或 iterate()等方法从数据库里查询对象时,查询到的对象也处于持久态;
-
当对数据库中的纪录进行update(O、saveOrUpdate()以及lock()等操作后,此时脱管态的对象就过渡到持久态;由于瞬时态和脱管态的对象不在session的管理范围,所以会在一段时间后被JVM回收。
1. 瞬时态转换为其他状态
- 瞬时态的对象有new关键字创建。其转换如下:
- 瞬时态转换为持久态:执行session的save()或saveOrUpdate()方法
- 瞬时态转换为托管态:为瞬时态对象设置持久化标识的主键托管态对象存在主键,但是没有和Session关联,也就是说托管态和瞬时态的区别就是主键有没有值。所以可以通过为瞬时态对象设置主键,使其变成托管态对象
Customer c = new Customer();//瞬时态
c.setCust_id(1) //托管态
2. 持久化对象转换到其他状态
持久化对象可以通过Session中的get(),load()或者Query查询从数据库中获得。
- 持久化状态转换为瞬时态:执行了Session的delete()方法,被删除的持久化对象,不建议再次使用
- 持久化状态转换为托管态:执行Session的evict(),close()或clear()方法。evict()方法用于清除一级缓存中某一个对象;close()方法用于关闭Session,清楚一级缓存,clear()方法用于清楚以及缓存中的所有对象。
3. 脱管态对象转换到其他状态
脱管态对象无法直接获得,是由其他状态对象转换而来的,脱管态对象转换到其他状态总结如下:
- 脱管态转换为持久态:执行Session的update O、saveOrJpdate(或lock(方法。
- 脱管态转换为瞬时态:将脱管态对象的持久化标识OID设置为null。
脱管态和瞬时态的区别就是主键有没有值,所以可以通过将脱管态对象的OID设置为null,使其变成瞬时态对象。例如在session.close()操作后,加入代码
customer.setCust_ id(null)
customer对象将由脱管态转换为瞬时态。
四、 Hibernate的一级缓存
Hibernate缓存分为一级缓存和二级缓存,Hibernate的这两级缓存均位于持久化层,存储的都是数据库数据的备份。其中一级缓存为Hibernate的内置缓存,不能被卸载。
1. 什么是一级缓存
Hibernate的一级缓存就是指 Session 缓存,Session 缓存是一块内存空间,用来存放相互管理的java对象。
在使用Hibernate查询对象的时候首先会使用对象属性的主键在Hibernate的一级缓存中进行查找,
如果找到匹配主键的对象,就直接将该对象从一级缓存中取出使用,不会再查询数据库
如果没有找到相同主键的对象,则会去数据库中查找相应数据。当从数据库中查询到所需数据时,该数据信息也会放置到一级缓存中。
Hibernate的一级缓存的作用就是减少对数据库的访问次数
2.Hibernate的一级缓存有如下特点:
当应用程序调用Session接口的save()
、update()
、saveOrUpdate()
时,如果Session缓存中没有相应的对象,Hibernate就会自动的把从数据库中查询到的相应对象信息加入到一级缓存中去。
当调用Session接口的load()、get()方法,以及Query接口的 list()、iterator()方法时,会判断缓存中是否存在该对象,有则返回,不会查询数据库,如果缓存中没有要查询对象,再去数据库中查询对应对象,并添加到一级缓存中。
当调用Session的 close(方法时,Session缓存会被清空。
public class Demo {
@Test
public void fun1() {
//1.获取session对象
Session session = HibernateUtils.openSession();
//2.开启事务
Transaction tx = session.beginTransaction();
//3.执行操作
Customer customer1 = session.get(Customer.class, 1l);
Customer customer2 = session.get(Customer.class, 1l);
Customer customer3 = session.get(Customer.class, 1l);
Customer customer4 = session.get(Customer.class, 1l);
System.out.println(customer1==customer2);//返回true
//4.事务提交
tx.commit();
session.close(); //游离状态
}
}
3. 快照区(减少不必要的修改)
Hibernate向一级缓存存入数据时,同时复制一份数据放到hibernate快照中
当使用commit()方法提交事务时,同时会清理Session的一级缓存,这时会使用主键判断一级缓存中的对象和快照中的对象是否一致
如果两个对象属性发生变化,且执行update语句,将缓存的内容同步到数据库中,并更新快照
如果一致,则不执行update语句。hibernate快照的作用就是确保一级缓存中的数据和数据库的数据一致
五、事务回顾
1. 回顾事务
事务是由一条或多条操作数据库的SQL语句组成一个不可分割的工作单元。当事务中的所有操作都正常完成时,整个事务才能被提交到数据库中,如果有一项没有完成,则整个事务都会被回滚。即要么一起成功,要么一起失败
2. 事务有四大特性
(ACID)即原子性,一致性,隔离性,持久性。
A(原子性)
:表示将事务中所做的操作绑定成一个不可分割的单元,即对事务所进行的数据修改等操作。要么全部执行,要么全部不执行。C(一致性)
:表示事务完成时,必须使所有的数据都保持一致状态I (隔离性)
:指一个事务的执行不能被其它事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰D(持久性)
:持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。提交后的其他操作或故障不会对其有任何影响
3. 事务并发产生的问题
数据库要被多个用户所共同访问的。在多个事务同时使用相同的数据时,可能会发生并发问题:
- 脏读:一个事务读取到另一个事务未提交的数据
- 不可重复读:一个事务读到了另一个事务已经提交的update的数据,导致在同一个事务中的多次查询结果不一致
- 幻读:一个事务读到了另一个事务已经提交的insert的数据,导致在同一个事务中的多次查询结果不一致
4. 事务的隔离级别
为了避免事务并发问题的发生,在数据库中定义了4个事务的隔离级别。不同的隔离级别对事务的处理不同
隔离级别 | 含义 |
---|---|
读未提交(1级) | 允许读取还未提交的改变了的数据。可能会导致脏、换、不可重复读 |
已提交读(2级) | 允许在并发事务已经提交后读取,可防止脏读,但幻读和不可重复读仍然发生 |
可重复读(4级) | 对相同字段的多次读取是一致的,除非数据被事务本身改变。可以防止脏、不可重复读,但幻读仍可能发生 |
串行化(8级) | 完全服从ACID的隔离级别,确保不可发生脏读、幻读、不可重复读。所有隔离级别中最慢的。它是典型的通过完全锁定在事务中涉及的数据表来完成的 |
六、 Hibernate中的事务控制
在Hibernate中,可以通过代码来管理事务。通过
Transaction tx = session.beginTransaction()
tx.commit();
除了在代码中对事务开启,提交和回滚操作外,还可以在Hibernate的主配置文件中对事务进行配置。在配置文件中设置事务的隔离级别。
<!--
指定hibernate操作数据库时的隔离级别
hibernate.connection.isolation 4
0001 1 读未提交
0010 2 读已提交
0100 4 可重复读
1000 8 串行化
-->
<property name="hibernate.connection.isolation">4</property>
问题
在我们的项目中,事务控制应该放在Service层,但我们在Dao层写了事务的开启和提交。我们需要将代码转移到Service层,所以我们应当确保我们的Service层和Dao层使用的是一个Session对象。类似我们javaweb层,我们需要将Service层的Connection和Dao层的connection 放到ThreadLocal中。确保在一个线程内,完成对事务的控制。
解决方案
- 可在在Service层获得Session并将Session作参数传递给Dao层
- 可以使用ThreadLoacl将业务层获取的Session绑定到当前线程中,然后再Dao中获取session的时候,都从当前线程中获取。
第一步:我们需要写配置文件。再hibernate.cfg.xml中引入下面这段配置文件(需强记)
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.connection.isolation">4</property>
第二步:调用getCurrent()
@Test
public void fun1() {
Session session1 = HibernateUtils.getCurrentSession();
Session session2 = HibernateUtils.getCurrentSession();
//返回绑定统一线程的Session
System.out.println(session1==session2);//true
}
@Test
public void fun2() {
Session session1 = HibernateUtils.openSession();
Session session2 = HibernateUtils.openSession();
//返回不同的Session
System.out.println(session1==session2);//true
}
注意:通过getCurrentSession方法获取的Session对象,当事务提交时,Session会自动关闭,不需要手动进行关闭
七、Hibernate的批量查询
1.HQL查询
适用于多表查询
2.Criteria查询
适用于单表查询
3.原生SQL查询
复杂的业务查询