主要内容:
* * Hibernate持久化类的编写规则
* * Hibernate主键生成策略
* * Hibernate持久化类的三种状态
* * Hibernate一级缓存(核心)
* * Hibernate事务管理(核心)
* * Hibernate其他的API:Query,Criteria, SQLQuery
hibernate持久化类及编写规则:
持久化类的定义
- 持久化:将内存中的数据永久存储到关系型数据库中。把容易丢失的数据,长期保存的过程,持久化。
- 持久化类:指的是一个java类与数据库表建立了映射关系,那么这个类就成为持久化类;参与这个持久化操作的类,就是持久化类。
- 持久化类的特点:一个类与数据库的表建立了映射关系。
- 持久化类= java类(更类似javaBean类) + 映射文件
持久化类的编写规则
a. 有无参的构造方法。
* hibernate底层使用反射创建对象。 比如get和load方法b. 属性私有化,对属性提供get/set方法。
* hibernate底层会将查询到的数据进行封装c. 属性尽量使用包装类。
* int和Integer的区别:初始化的值:int–》0 ,Integer –>null。
因为,在数据库里面,0值和null值,代表不同的意义。d. 持久化类要有一个唯一标识OID与表的主键对应。
* java判断两个对象相同,是通过地址进行比较。而hibernate判断两个持久化对象是否相同,是通过OID的值。(缓存的时候,体现)e. 持久化类,不要使用final修饰符。
* final修饰类,这个类不能被继承。导致load方法的延迟加载特性失效。
* 因为load的延迟加载的原理:通过继承持久化类,生成一个代理对象,从而实现延迟加载。如果用final修饰符,不能被继承,导致不能生成代理对象,hibernate就会使用直接加载。细节
a. 数据库中0值和null值的不同
* 工资表中,有人的工资是0, 有人的工资是null.
* 领导指示,工资低于1000的,加200块钱。
* update 工资表 set 工资=工资+200 where 工资<1000;
* 领导指示,工资低于1000的或者没有工资的人,加200块钱。
* update 工资表 set 工资=工资+200 where 工资<1000 or 工资 is null;b. final修饰符的影响
* load机制的延迟加载,是通过继承持久化对象,产生一个代理对象实现的。final修饰类之后,无法继承,从而无法产生代理对象,导致延迟加载失效。load方法,会使用立即加载。c. 唯一标识符OID的理解
* 在持久化类中,对表示主键属性的统称。
hibernate主键及主键生成策略:
1)数据库的主键的类型
1. 1数据库主键分类
* 自然主键:对象本身的属性,作为表的主键(把具有业务含义的字段作为主键)
* 代理主键:不使用对象本身的属性,而使用没有任何意义的一个字段,作为主键。(推荐)
使用自然主键,就会失去主动的控制权。一般情况下就都不要使用自然主键,推荐使用代理主键
1.2. 主键的生成方式
* 自然主键:需用户手动输入
* 代理主键:由系统自动生成。mysql: auto_increment, oracle:序列
2)hibernate的主键生成策略:
hibernate的跨数据库平台特性:
核心配置文件中的方言dialect、映射配置文件中的主键生成策略,都体现了hibernate的跨数据库平台开发的特性。主键生成策略,使用native是正确的,因为可以支持跨数据库平台的特性。
hibernate持久化对象的状态
1)持久化对象的三种状态:
持久化对象,就是持久化类的生成的实例,hibernate为了更好的管理持久化对象。给持久化对象分为三种状态。
* * 瞬时状态(Transient): 没有oid,没有关联session对象
*
* 也成为临时态或者自由态,瞬时态的实例是由new命令创建的,开辟内存空间的对象,不存在持久化表示OID,尚未与Hibernate session关联。在数据库中也没有记录,失去引用后将被JVM回收。与数据库中的数据没有任何关联,仅仅是一个信息携带的载体。
* * 持久状态(Persistent):有oid, 关联session对象
*
* 持久态的对象存在持久化表示OID,加入到了Session缓存中,并且相关联的Session没有关闭,在数据库中有对应的记录。每条记录值对应唯一的持久化对象。需要注意的是,持久态对象是在事务还未提交前变成持久态的。
* * 脱管状态(Detached): 有oid, 没有关联session对象
*
* 脱管态也成为离线态或者游离态,当某个持久化状态的实例与session的关联被关闭的时候就变成了托管态,托管态对象存在持久化标志OID,并且仍然与数据库中的数据存在关联,只是失去了当前Session的关联。托管状态对象发生改变的时候Hibernate不能够检测到。
测试:
@Test
public void test03() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 瞬时态:没有oid,和session没有关联
User user = new User();
user.setUsername("刘德华3号");
user.setPassword("123");
System.out.println("瞬时:" + user.toString());
// 持久态:有oid,和session有关联
session.save(user);
System.out.println("持久:" + user.toString());
transaction.commit();
session.close();
// 托管态:有oid,和session没有关联
System.out.println("托管:" + user.toString());
}
2) 持久化对象三种状态的切换
a. 保存: 瞬时–》持久态–》脱管态
b. 查询:持久态–》脱管态
3)持久化对象处于持久态的特性(重点)
* 自动更新数据库。
如果调用了update方法也没有错,但是内部控制了,只会执行一次。
Hibernate的一级缓存
1)缓存的理解:
缓存理解成一种策略。利用高速存储区域的空间,提高对低速存储区域数据的读取速度。即空间换时间。内存是缓存的一种体现,缓存不仅仅只是内存
缓存的原理
* 利用高速存储区域,保存低速存储区域的数据,从而实现缓存功能。空间换时间。作用是降低应用程序直接读取永久性数据存储源的频率,从而提高应用的运行性能。缓存的物理介质通常是内存;
* 案例:cpu–内存–硬盘
2)缓存的替换算法
a. 原因:高速存储区空间,必定要小于低速存储空间。随着程序的运行,缓存的数据越多,高速存储区空间会全部使用,即存储空间满。这个时候,需要缓存替换算法。
b. 最基本两个策略:
(1) 先入先出(队列):最先保存的数据,最先被替换。
(2) 最少使用:使用最少的数据,最先被替换。
还有多种策略和算法,比如综合上述两种策略,采取权重算法。
3)hibernate缓存:
3.1. 缓存分类:一级缓存和二级缓存。
a. 一级缓存: session级别的缓存,默认可使用。一级缓存的生命周期与session一致。
只要Session实例没有结束生命周期,存放在它缓存中的对象也不会结束,
b. 二级缓存:SessionFactory级别的缓存,需要手动配置才能使用的。二级缓存的生命周期与SessionFactory一致。不过,实际中,用redis代替二级缓存,故不使用。(集群环境下,二级缓存效果不佳)
3.2. hibernate缓存的目的
* 减少对数据库访问的次数。
3.3. hibernate缓存的原理
* 通过OID来区分持久化对象。OID相同,认为是同一个持久化对象。
3.4. hibernate缓存的使用
a. hibernate调用get/load进行查询
b. 先查询缓存。根据OID区分,如该持久化对象存在,直接使用
c. 没有查到,发送sql语句,从数据库查询。
d. 查询到的数据,session缓存保存一份(自动),并返回给调用程序。
【备注】hibernate调用save(),update()等操作,也使用缓存。
3.5. 代码演示缓存的存在
@Test
public void test03() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
User user = session.get(User.class, 2);
System.out.println("第一次查询:" + user.toString());
User user2 = session.get(User.class, 2);
System.out.println("第二次查询:" + user2.toString());
transaction.commit();
session.close();
}
打印语句为:
可以看到,查询数据库的sql语句值出现了一次,说明只查询了一次数据库,第二次查询相同id的user的时候,使用session的一级缓存。
3.6. 清除缓存的方法:session的API
* clear():清除一级缓存中全部对象
* evict(Object obj): 清除一级缓存中指定对象
3.7一级缓存的内部结构:快照区
举例说明:
id为2的user的username的值原本就为“测试”,先取出来之后,设置为了“修改了修改了”,然后再改回了原来的值“测试”,然后再进行事务的提交commit;
@Test
public void test03() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
User user = session.get(User.class, 2);
System.out.println("第一次查询:" + user.toString());
user.setUsername("修改了修改了");
user.setUsername("测试");
transaction.commit();
session.close();
}
结果:
只有第一次查询的sql语句,并没有数据库的更新语句!
这就是快照的体现,当从数据库中取出user的时候会同时复制一份对象到Hibernate快照中去,当使用commit方法提交事务的时候,就会清理Session的一级缓存,这时候会使用OID判断一级缓存中的对象和快照中的对象是否一致,如果两个对象的属性发生了变化,就会执行了update语句,将缓存的内容同步到数据库并更新快照,如果一致,那么update语句就不会执行。
!!!!!!
引申出来:hibernate持久化对象处于持久态时自动更新数据库的原理:
原理:
a. session内部,有两个存储区:一个是一级缓存区,一个是快照区
b. get/load查询到的数据,一级缓存区和快照区,各自保存一份(注意:保存对象本身,而不是引用)。
c. 持久状态下,进行修改。只修改缓存区的数据,快照区的数据不变(即一直保持和数据库的原始数据一致)。
d. 事务提交时,对比缓存区和快照区。如果相同,说明数据与数据库的原始数据一致,无需update。如果不同,执行update。
hibernate的事务控制:
事务的特性:
* 事务包含4种重要的特性,统称为ACID(原子性、一致性、隔离性和持久性)
a. 原子性(Atomic):事务是一个整体的工作单元,事务对数据库所做的操作要么都执行,要么都取消。如果某条语句执行失败,则所有语句全部回滚。
b. 一致性(ConDemoltent):事务在完成时,必须使所有的数据都保持一致状态。如果事务成功,则所有数据将变为一个新的状态;如果事务失败,则所有数据将处于开始之前的状态。
c. 隔离性(Isolated):指一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
d. 持久性(Durability):当事务提交后,对数据库所做的修改就会永久保存下来。
事务的并发问题
* 在实际的使用中,数据库被多个用户共同访问,即出现多个事务并发执行的情况。
* 在多个事务同时使用相同的数据时,可能出现并发问题。 查询,无需事务的操作。因为,查询,不会修改数据
* 并发问题分类:
a. 脏读: 一个事务读到了另一个事务未提交的数据.
b. 不可重复读: 一个事务读到了另一个事务已经提交的update数据,导致多次查询结果不一致.
c. 虚读(幻读): 一个事务读到了另一个事务已经提交的insert数据,导致多次查询结果不一致.
事务的隔离级别
1. 解决事务并发问题并考虑数据库的效率,设计了多种隔离级别:
数据库事务的隔离级别有4个
(1). 由低到高依次为Read uncommitted(读未提交)、Read committed(读提交)、Repeatable read(重复读)、Serializable(序列化)(2). 这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
- 体会–“平衡”2字
a. 事务的隔离级别,没有固定的完美的解决方案。可序列化,可以解决一切并发问题,但是效率太低,很少使用。
b. 在实际工作,根据数据库存储数据的特点,来确定隔离级别。隔离级别最高和最低,都很少使用。一般,偏重性能,设置为:读提交;偏重安全,设置为:重复读
c. mysql默认:重复读 oracle默认:读提交
- 体会–“平衡”2字
hibernate设置隔离级别
在hibernate.cfg.xml中设置隔离级别:
<!-- 事务隔离级别 -->
<property name="hibernate.connection.isolation">2</property>
* 其中数字的含义:
1—Read uncommitted isolation:读未提交
2—Read committed isolation:读提交
4—Repeatable read isolation:重复读
8—Serializable isolation:序列化
hibernate管理session的三种方式
Hibernate提供了三种管理Session对象的方法
(1) Session对象的生命周期与本地线程绑定
(2) Session对象的生命周期与JTA事务绑定
(3) Session对象的生命周期由Hibernate委托程序管理
hibernate事务控制api:
sessionFactory.getCurrentSession()
获取和当前线程相关联的Session,底层使用ThradLocal实现的
注意:
1)使用前,需要在核心配置文件hibernate.cfg.xml进行配置:
<!-- 配置session绑定本地线程 -->
<property name="hibernate.current_session_context_class">thread</property>
2)这是Hibernate官方提供的工具类,是不能够关闭这个session的。在线程销毁的时候,hibernate自动关闭这个session
3)害人的小细节
(1) 没有“thread”配置,使用getCurrentSession()方法,会报错。
(2) getCurrentSession()获取的session对象。在线程销毁时,自动关闭,千万不要手动调用session.close()方法,会报错。
例如:抽取工具类:
HibernateUtils:
public class HibernateUtils {
private static final Configuration configuration;
private static final SessionFactory sessionFactory;
private static ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
static {
configuration = new Configuration();
configuration.configure();
sessionFactory = configuration.buildSessionFactory();
}
/**
* @Title: HibernateUtils.java
* @Description: TODO(获得session对象)
* @return
* @author jjizh
* @date 2017年7月26日 下午8:07:59
* @version V1.0
*/
public static Session openSession(){
return sessionFactory.openSession();
}
/**
* @Title: HibernateUtils.java
* @Description: TODO(利用ThradLocal,获得本地线程的Session对象,应用于事务中)
* @return
* @author jjizh
* @date 2017年7月27日 下午5:09:11
* @version V1.0
*/
public static Session getThreadLocalSession(){
Session session = threadLocal.get();
if(session == null){
session = openSession();
threadLocal.set(session);
}
return session;
}
/**
* @Title: HibernateUtils.java
* @Description: TODO(关闭资源)
* @author jjizh
* @date 2017年7月27日 下午5:11:54
* @version V1.0
*/
public static void closeSession(){
Session session = threadLocal.get();
if(session != null){
session.close();
session = null;
}
threadLocal.remove();
}
/**
* @Title: HibernateUtils.java
* @Description: TODO(hibernate官方提供的从本地线程获取的session对象的方法,不需要关闭)
* @return
* @author jjizh
* @date 2017年7月27日 下午5:50:06
* @version V1.0
*/
public static Session getCurrentSession(){
return sessionFactory.getCurrentSession();
}
}
hibernate的其他API
1)Query
Query的介绍
a. Query是一个面向对象的查询。其核心是HQL语句(区分大小写)。(又称为HQL查询)
b. HQL:hibernate query language,hibernate查询语言,语法类似SQL(案例中感受)。
查询步骤:
相关API:
a. session.createQuery(String hql): –创建Query对象
b. list(): –查询结果是多个
c. uniqueResult(): 查询结果是一个
d. setXxx(int position,Xxx val):–设置查询参数,Xxx标识类型,postion标识位置,从0开始
e. setFirstResult(int firstResult): –分页查询1,设置第一个结果的起始位置,从0开始
f. setMaxResults(int maxResults):–分页查询2,设置结果的总数
案例:
public class QueryDemo {
@Test //查询表中所有数据,多个数据
public void test01(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
//User就是一个持久化类的类名
Query query = session.createQuery("from User");
List<User> list = query.list();
for (User user : list) {
System.out.println(user);
}
transaction.commit();
HibernateUtils.closeSession();
}
@Test //查询表中指定条件数据,多个数据
public void test02(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Query query = session.createQuery("from User where money = ?");
query.setInteger(0, 2000); //和JDBC中不同,下标从0开始
List list = query.list();
for (Object object : list) {
System.out.println((User)object);
}
transaction.commit();
HibernateUtils.closeSession();
}
@Test //查询表中的单个数据
public void test03(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Query query = session.createQuery("from User where username = ?");
query.setString(0, "zs");
User user = (User) query.uniqueResult();
System.out.println(user);
transaction.commit();
HibernateUtils.closeSession();
}
@Test //分页查询
public void test04(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Query query = session.createQuery("from User");
query.setFirstResult(2); //下标从0开始
query.setMaxResults(2); //每页显示多少条数据
List<User> list = query.list();
for (User user : list) {
System.out.println(user);
}
transaction.commit();
HibernateUtils.closeSession();
}
@Test //模糊查询
public void test05(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Query query = session.createQuery("from User where username like ?");
query.setString(0, "%z%");
List<User> list = query.list();
for (User user : list) {
System.out.println(user);
}
transaction.commit();
HibernateUtils.closeSession();
}
}
hibernate的其他API
2)Criteria(重要)
1. Criteria的介绍
a. Criteria:完全面向对象的查询。又称为QBC查询,query by criteria.
b. 不用管底层数据库实现和SQL语句的编写。
API介绍
a. session.createCriteria(Class persistentClass): –创建Criteria对象。persistentClass:需要查询对象的字节码对象名称。需要传入字节码文件,因为将查询的结果都直接封装成为一个对象了
b. list(): –查询结果是多个
c. uniqueResult(): –查询结果是一个
e. setFirstResult(int firstResult):–分页查询1,设置第一个结果的起始位置,从0开始
f. setMaxResults(int maxResults): –分页查询2,设置结果的总数
g. add(Criterion criterion): –添加查询条件对象(支持添加多个,互相是and关系)烦人的完全面向对象
- Criteria查询,把查询条件封装成了“查询条件对象”
查询条件对象,使用Criterion和Restrictions两个类来实现
(1) Criterion: 称为查询条件对象,每个对象实例,代表一个查询条件。
(2) Restrictions: 工具类,通过其静态(static)方法,创建Criterion对象* 其常见的Restrictions的静态方法 方法 说明 Restrictions.eq = Restrictions.allEq 利用Map来进行多个等于的限制 Restrictions.gt >(greater than) Restrictions.ge >=(greater than or equal) Restrictions.lt < (less than) Restrictions.le <=(less than or equal) Restrictions.between BETWEEN Restrictions.like LIKE Restrictions.in in Restrictions.and and Restrictions.or or Restrictions.sqlRestriction 用SQL限定查询
Criteria的使用
a. 通过Session对象,获取Criteria对象。session.createCriteria(Class persistentClass)
b. 通过Restrictions的静态方法创建Criterion对象。每个Criterion对象实例代表一个查询条件
c. 向Criteria对象添加Criterion查询条件。add()方法
d. 执行list()或者uniqueResult()获得结果。
案例:
@Test //查询持久类在数据库中的所有对象列表
public void test06(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Criteria criteria = session.createCriteria(User.class);
List<User> list = criteria.list();
for (User user : list) {
System.out.println(user);
}
transaction.commit();
HibernateUtils.closeSession();
}
@Test //添加查询的限制条件
public void test07(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Criteria criteria = session.createCriteria(User.class);
SimpleExpression gt = Restrictions.gt("id", 2);
//上述这一句等同于:
//Criterion gt = Restrictions.gt("id", 2);
criteria.add(gt);
List<User> list = criteria.list();
for (User user : list) {
System.out.println(user);
}
transaction.commit();
HibernateUtils.closeSession();
}
@Test //添加查询的限制条件
public void test08(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Criteria criteria = session.createCriteria(User.class);
Criterion gt = Restrictions.eq("username", "zs");
criteria.add(gt);
User user = (User) criteria.uniqueResult();
System.out.println(user);
transaction.commit();
HibernateUtils.closeSession();
}
@Test //分页查询
public void test09(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Criteria criteria = session.createCriteria(User.class);
criteria.setFirstResult(2);
criteria.setMaxResults(2);
List<User> list = criteria.list();
for (User user : list) {
System.out.println(user);
}
transaction.commit();
HibernateUtils.closeSession();
}
@Test //合并多个查询条件的时候
public void test10(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Criteria criteria = session.createCriteria(User.class);
Criterion lhs = Restrictions.gt("id", 4);
Criterion rhs = Restrictions.lt("id", 2);
Criterion or = Restrictions.or(lhs, rhs);
criteria.add(or);
List<User> list = criteria.list();
for (User user : list) {
System.out.println(user);
}
transaction.commit();
HibernateUtils.closeSession();
}
hibernate的其他API
3)SQLQuery
SQLQuery介绍
a. 使用原始的SQL语句进行查询。
b. SQLQuery不会直接封装到实体对象。API介绍
a. session.createSQLQuery(String sql):–通过sql语句,创建对象
b. list(): –查询结果是多个
c. uniqueResult(): –查询结果是一个
d. addEntity(Class entityType): –把查询结果封装成entityType类的对象。SQLQuery介绍
a. 编写SQL语句。
b. Session对象通过SQL语句,创建SQLQuery对象。session.createSQLQuery(String sql)
c. 执行SQLQuery的list()或uniqueResult()方法获取结果。结果形式是:Object[]。
d. 不会直接封装到实体对象,需要手动调用addEntity(Class entityType)方法。
案例:
@Test
public void test11(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Query query = session.createSQLQuery("select * from transferCustomer");
List<Object[]> list = query.list(); //查询出来是以数组的形式存在的
for (Object[] object : list) {
System.out.println(Arrays.toString(object));
}
transaction.commit();
HibernateUtils.closeSession();
}
@Test
public void test12(){
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
SQLQuery query = session.createSQLQuery("select * from transferCustomer");
query.addEntity(User.class); //将查询出来的结果封装成为对象
List<User> list = query.list();
for (User user : list) {
System.out.println(user);
}
transaction.commit();
HibernateUtils.closeSession();
}