接触Hibernate在学习了java数据库连接(JDBC)以后,相对于Hibernate ,JDBC无法直接面向对象,并且开发效率低,代码冗余,重复性工作多,而Hibernate是一个优秀的java持久化层解决方案,是当今主流的对象——关系映射(ORM)工具,它对JDBC进行了非常轻量级的对象封装,简化了JDBC繁琐的编码。ORM是持久化的一种解决方案,主要是把对象模型和关系型数据库关系模型映射起来,并且使用元数据对这些映射进行描述。
一 Hibernate入门
1.1 为项目增加Hibernate支持
在使用Hibernate前必须为项目增加Hibernate支持,有三个步骤,即添加jar包,编写配置文件和测试连接。Jar包主要根据自己的数据库环境和需求选择。创建Hibernate主配置文件是用于配置数据库连接,以及运行所需的各种属性,默认的文件名是“hibernate.cfg.xml”,文件中主要包括了数据库连接信息,Hibernate的相关配置,连接池配置和注册ORM实体类映射文件的路径四个方面。示例如下(需根据环境进行相关修改):
<?xmlversion='1.0'encoding='utf-8'?>
<!DOCTYPEhibernate-configuration PUBLIC
"-//Hibernate/HibernateConfiguration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 1.数据库连接信息 -->
<!--数据库JDBC驱动类名 -->
<propertyname="connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
<!--数据库连接URL -->
<propertyname="connection.url">jdbc:oracle:thin:@localhost:1521:XE</property>
<!--数据库用户名 -->
<propertyname="connection.username">dev2</property>
<!--数据库密码 -->
<propertyname="connection.password">dev123</property>
<!-- 2.hibernate相关配置 -->
<!--数据库方言 -->
<propertyname="dialect">org.hibernate.dialect.Oracle10gDialect</property>
<!--是否输出Hibernate生成的SQL语句,开发阶段一般需要开启 -->
<propertyname="show_sql">true</property>
<!--是否对输出SQL进行格式化 -->
<propertyname="format_sql">true</property>
<propertyname="hbm2ddl.auto">validate</property>
<!-- 3.连接池配置 -->
<!--注册ORM实体类映射文件的路径-->
<mappingresource="entity/Type.hbm.xml"/>
</session-factory>
</hibernate-configuration>
创建完Hibernate的主配置文件后对其进行测试,代码如下:
publicclass HibernateTest {
@Test
publicvoid cfgTest(){
//获取配置,默认读取classpath根目录下名为hibernate.cfg.xml的文件
Configuration cfg = new Configuration().configure();
//创建SessionFactory——Session工厂
SessionFactory factory = cfg.buildSessionFactory();
//获取session——Hibernate的持久化管理器,通过它可以做增删改查
Session s =factory.openSession();
assertNotNull(s);
}
}
测试成功后就可以使用Hibernate对数据进行相关的操作。
数据库操作步骤:
1.2 Hibernate的核心接口
Configuration接口:Configuration对象用来配置和引导Hibernate,一个Hibernate应该用一个Configuration实例来制定主配置文件的位置,Configuration cfg = new Configuration().configure(“/cfg/auction.cfg.xml”), configure()方法未带参数的时候,Hibernate会在classpath的根路径搜索名为hibernate.cfg.xml的文件,如果没有找到将会抛出异常。这里与Hibernate主配置文件命名匹配,一般将其放在src目录下(src目录映射到编译后的classpath根路径)。
SessionFactory接口:一个Hibernate应用从SessionFactory里获得会话实例,SessionFactory是重量级的,创建它的对象消耗资源较多,而且它只需要存在一个对象就够了,因此在整个应用中一般只需编写一个工具类HibernateUtil,将SessionFactory的实例设置为静态成员(只在内存中存在一份),并将其初始化放在静态初始化块中(只执行一次)。总结的来说就是:重量级,单实例。
Session(会话)接口:Session接口是Hibernate应用使用的主要接口,它拥有操作持久化对象的一系列API,可用于管理(例如加载和保存)对象,因此我们也称之为“持久化管理器”。相对来讲Session是轻量级的实例,因此我们会为每一个事务创建一个Session实例,并在使用后关闭。
Transaction(事务)接口:Transaction接口是对实际事务实现的一个抽象,这些实现包括JDBC事务或者JTA事务等。主要定义了提交事务的方法commit()和回滚事务的方法rollback()。
Query(查询)接口:Query接口允许在数据库上执行查询,并控制查询如何具体执行。查询使用HQL或SQL。Query实例可用来绑定查询参数,限定查询返回的结果数,并且最终执行查询。
1.3 Hibernate中实体对象的三种状态
实体对象的生命周期是Hibernate 应用中的一个关键概念。
Transient(瞬时状态):刚用new语句创建,还没有被持久化,且不处于Session的缓存中。
Persistent(持久状态):已经被持久化,且加入到Session的缓存中。
Detached(游离状态):已经被持久化,但不再处于Session的缓存中。
app操作对象状态,在某一时刻,Hibernate会将对象状态同步到数据库,瞬时对象和游离对象的状态的改变不会被同步,直到它们变成一个持久状态。
new产生的对象为瞬时态,通过save(),saveOrUpdate()转换为持久态,通过delete()可以变回瞬时态,session关闭之后,原有的持久态就变成了游离态,游离态通过系列方法可重新回到持久态,并在事务提交的时候和数据库同步。
session的get load()可直接获得一个持久态对象。持久态和游离态的共同点在于,都有数据表的一行与之对应,不同的是,持久态在session范围内,而游离态不在session范围内。
二 Hibernate 关联映射
实体间的关系主要是关联关系和泛化关系,而类与类之间最普遍的关系就是关联关系。
2.1many-to-one单向多对一关联
Many的一端持有one一端的对象(引用),映射文件体现多对一。
映射文件示例:
<hibernate-mapping>
<class name="org.ijob.bean.Resume"table="ijob_resume">
<!—省略主键映射及属性映射 -->
<!-- 多对一关联 -->
<many-to-onename="seeker" class="org.ijob.bean.Seeker"> 1
<columnname="seeker_id"></column> 2
</many-to-one>
</class>
</hibernate-mapping>
代码解析:
1、name为属性名,class为“one”端类名
2、外键列列名
2.2 one-to-many单向一对多关联
one一端持有many端的对象集合,映射文件体现一对多
映射文件示例:
<hibernate-mapping>
<classname="org.ijob.bean.Seeker" table="ijob_seeker">
<!—省略主键映射及属性映射 -->
<setname="resumes" > 1
<keycolumn="seeker_id"></key> 2
<one-to-manyclass="org.ijob.bean.Resume"/> 3
</set>
</class>
</hibernate-mapping>
代码解析:
1、set集合节点适用于配置一对多关联关系, name属性指定one的一端对应属性名。
2、外键列列名。
3、class属性指定many端的全限定类名。
2.3 其它关联
多向一对多关联:同时配置了单向一对多和单向多对一。
一对一关联:特殊的一对多关联,即many的一端是唯一的而不是多个。
多对多关联:将多对多转换成两个一对多,为中间表建立实体类及映射文件,两个端点和中间端分别建立双向一对多关联。
2.4 cascade属性和inverse属性的作用
cascade:cascade="save-update"意味着对某个对象执行save或update时,其关联对象会自动被持久化。可用于set标签和many-to-one标签。cascade=“delete”级联删除关联集合,用于set标签。none为默认,无级联。all对应save/update/delete操作。
需要注意的是:配置级联关系时,通常将cascade设置为save-update,删除的时候最好使用手工控制。
Inverse:用于one-to-many节点,默认为false。设置为true,可强制由many的一端来做实际的外键关系维护者,即便代码是从one的一端往集合中添加数据也是一样的。
三 Eclipse插件及反向工程
常见开发场景
自上而下:在自上而下的开发过程中,由业务分析和建立领域模型开始,用Java实现实体类。然后构建映射元数据(映射文件),通常是XML文件,也可以是注解,然后用Hibernate的hbm2ddl工具产生数据库模式(hbm指的是hibernate-mapping,2代表的是to,ddl是数据定义语言)。
自下而上:反之,自下而上开发由一个现成的数据库模式开始,可以利用反向工程提取数据库中的元数据。利用hbm2hbmxml可生成XML映射文件;利用hbm2java,由Hibernate映射元数据生成实体类,利用hbm2dao可生成数据访问对象(DAO)。但是,这种方式生成的代码并不一定能很好地适应于应用的具体要求,有时需要一些手工的修改。
自中间开始:直接编写Hibernate映射文件(XML文件),由它生成Java源码和数据库模式。用手工编写XML映射文件,实体类的信息、数据库模式的信息都需要在这里面描述,并且在开发过程中不断地更新,这种开发风格只建议经验丰富的Hibernate专家使用。
四 Hibernate查询语言(HQL)
4.1HQL简介
HQL查询是Hibernate 支持的两种主要查询方式之一,是一种面向对象的查询语言,其中没有表和字段的概念,只有类、对象和属性的概念,应用较为广泛。另一种是Criteria 查询,又称为“对象查询”,它用面向对象的方式将构造查询的过程做了封装。
使用HQL四步曲:得到Session;编写HQL语句;创建Query对象;执行查询,得到结果。
注:Query接口是HQL 查询接口,它提供了各种的查询功能。
4.2基本语法
HQL看上去很像SQL,但是HQL是非常有意识的被设计为完全面向对象的查询。HQL中没有表和字段的概念,只有类、对象和属性的概念。
选择:where 子句指定限定条件,通过与SQL 相同的比较操作符指定条件,如:
=、<>、<、>、>=、<=
between、not between
in、not in
is、like
通过and、or 等逻辑连接符组合各个逻辑表达式。
投影:选择出现在结果集中的字段,默认情况下结果为对象数组组成的集合,可使用结果转换器,代码示例:select b.name, b.count from Book b
参数绑定:参数绑定有两种方法,分别是利用定位参数和利用命名参数。需要注意的是通用查询参数绑定方法中有一种允许绑定任何hibernate类型实参的一般方法setParameter(),该方法可以自动找出正确的类型。Hibernate定位参数是从0开始计数的。
五 HibernateSession管理
Java程序的运行起点要么是主线程,要么是主线程中启动的新线程,主方法(线程run方法)既是起点,也是终点,其它方法被上一层调用,也调用下一层,等待下一层返回,最终也会返回给上一层。
频繁开闭Session不仅造成性能的浪费,上一层调用者获得下一层方法返回的实体对象(关联的session关闭后可能是游离态)仍然无法延迟加载
让延迟加载成了鸡肋。
Junit是一个线程依次执行多个方法,所以我们需要在所有方法调用前开启事务,在所有方法完成后再提交事务并关闭Session。代码示例:
@BeforeClass
publicstatic void beforeClass() throws Exception {
HibernateUtil.currentSession().beginTransaction();
}
@AfterClass
publicstatic void afterClass() throws Exception {
HibernateUtil.currentSession().getTransaction().commit();
HibernateUtil.closeCurrentSession();
}
六 HQL查询进阶
6.1Criteria查询
Criteria查询可以实现HQL查询的功能,但是HQL查询是基于字符串的,所以相对而言更加的灵活。
基本条件查询:如果想要查询某个实体类所对应的数据表中所有的内容,可以进行如下的查询:
Criteriacriteria = session.createCriteria(**.class);
List users= criteria.list();
使用DetchedCriteria查询:Criteria实例必须与Session绑定,其生命周期跟随着Session结束而结束。使用Criteria进行查询时,每次都要在运行时动态建立其实例,加入各种查询条件,并且随着Session的回收,Criteria实例也跟着被回收。为了能够重复使用Criteria实例,在Hibernate3中新增了org.hibernate.criterion.DetchedCriteria,代码示例:
// 先建立DetchedCriteria实例
DetachedCriteriadetchedCriteria = DetachedCriteria.forClass(**.class);
…// 省略加入查询条件的代码
Sessionsession = sessionFactory.openSession();
// 绑定Session并返回一个Criteria实例
Criteriacriteria = detchedCriteria.getExecutableCriteria(session);
Listusers = criteria.list();
设定限制条件:org.hibernate.Criteria实际上是个条件附加的容器,如果想要设定查询条件,则要使用org.hibernate.criterion.Restrictions的各种静态方法传回org.hibernate.criterion.Criterion实例,传回的每个org.hibernate.criterion.Criterion实例代表着一个条件,使用org.hibernate.Criteria的add()方法加入这些条件实例。例如查询“身高”在160和170之间的求职者,代码如下:
Criteriacriteria = getSession().createCriteria(Seeker.class);
criteria.add(Restrictions.gt("bodyHigh",new Integer(160)));//添加条件
criteria.add(Restrictions.lt("bodyHigh",new Integer(170)));//添加条件
List<Seeker>users = criteria.list();
代码中,Restrictions的gt()方法表示大于(greatthan)条件,而lt()方法表示小于(less than)条件。
排序:我们可以使用org.hibernate.criterion.Order对结果进行排序:
Criteriacriteria = session.createCriteria(Seeker.class);
criteria.addOrder(Order.asc("birth"));
Listusers = criteria.list();
注:在加入Order条件时,使用的是addOrder()方法,而不是add()方法,在产生SQL语句时,会使用orderby与asc(desc)来进行排序指定。
分页:Criteria接口的setMaxResults()方法可以限定查询回来的笔数,如果配合setFirstResult()设定查询的起始位置的话,就可以实现简单的分页。
例如需要查询从第51个求职者开始的40个求职者信息,代码如下:
Criteriacriteria = session.createCriteria(Seeker.class);
criteria.setFirstResult(50);
criteria.setMaxResults(40);
Listusers = criteria.list();
投影和分组查询:通过CriteriaAPI的setProjection()方法可以投影一个或多个属性,例如:
List l= getSession().createCriteria(Resume.class)
.setProjection(Projections.id()).list();
6.2 加载计划和策略
Hibernate提供了下列方法从数据库中获取对象:通过get()或load()方法按照id获取对象;从一个已经加载的对象开始,通过系列的get方法访问被关联的对象;HQL查询获取单个或系列对象;Criteria查询获取单个或系列对象;原生SQL查询获取单个或系列对象。
加载计划:为了加以区别,我们称第一层次的查询目标为“主对象”。现在我们要关注的是数据何时被加载以及数据被怎样加载的问题。关注点有以下几个:主对象的关联对象何时被加载;主对象的关联集合何时被加载;主对象本身何时被加载;主对象的属性何时被加载。而对此Hibernate有其默认的加载计划。如下:
1、关联对象和关联集合的默认加载计划是:延迟加载,即加载主对象时它们不会被立即加载,而是直到使用这些对象和集合时才发送SQL语句、获取数据、初始化对象和集合。
2、主对象本身是否延迟加载取决于使用的是load()方法还是其它方法,load()方法是延迟的,而get()方法或其它方法是立即的。
3、主对象的属性默认是被立即加载的。
改变默认加载计划:通过修改对应类的实体映射文件,改变默认的加载计划。但我们不建议这样做,因为这样配置之后,不管是否需要关联数据,Hibernate都会加载,这样会造成大量的内存浪费。最好是采用编程方式在特定情况下进行立即加载(也称热加载),比如我们之前提到的HQL fetch join或者在Criteria查询中设置FetchMode。
Load方法加载计划:如果要禁用代理对象的生成,可在映射文件中,做如下改动:
<classname="……" table="……" lazy="false">
...
</class>
get和load方式是根据id取得一个记录;
1.从返回结果上对比:
load方式检索不到的话会抛出org.hibernate.ObjectNotFoundException异常
get方法检索不到的话会返回null
2.从检索执行机制上对比:get方法和find方法都是直接从数据库中检索而load方法的执行则比较复杂首先查找session的persistentContext中是否有缓存,如果有则直接返回 如果没有则判断是否是lazy,如果不是直接访问数据库检索,查到记录返回,查不到抛出异常 如果是lazy则需要建立代理对象,对象的initialized属性为false,target属性为null 在访问获得的代理对象的属性时,检索数据库,如果找到记录则把该记录的对象复制到代理对象的target上,并将initialized=true,如果找不到就抛出异常。
6.3Hibernate高速缓存
Hibernate一级缓存:Hibernate一级缓存的生命周期跟Session的生命周期一样,所以也可以将Hibernate一级缓存称为Session缓存。Hibernate一级缓存是一个强制的高速缓存。
get()方法会去一级缓存中查找获取数据(又称命中数据),并且将查询结果放置在一级缓存中。load()也一样。
list()方法会将查询结果放置在一级缓存中,但是它不会去一级缓存中查找获取数据,原因是list()方法不是通过id加载的。
iterate方法
Iterator<Seeker>iter = session.createQuery(“from **").iterate();
该语句只把ID的值放到迭代器中,当遍历的时候,会根据ID的值再去数据库中查。并且该语句会产生N+1次查询。
注:iterate查询针对实体(不是针对属性查询)的时候会采用N+1的方式,加载实体对象前会做一级缓存命中,并且将实体缓存起来。默认情况都用list获取数据
清除缓存:session.clear()该方法会清除一级缓存。
session.evict(Objectobject);session.clear()方法是一次性清除一级缓存中的所有数据,而session.evict(Object object)方法是清楚一级缓存中指定对象。
注:在数据量比较大的情况下,管理一级缓存的做法一般都是在一定数量的更新或者保存等操作之后,使用flush()和clear()方法将数据同步到数据库并清除一级缓存,从而避免一次性大量实体数据的缓存导致内存溢出。
Hibernate二级缓存:二级缓存是一个可选的缓存插件,它由SessionFactory负责管理,所以也常将二级缓存称为SessionFactory缓存。
由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此二级缓存是进程范围的缓存,可以被线程共享,所以可能存在并发问题,因此需要采用适当的并发访问策略。
二级缓存对实体类是可选的,可以在每个类或每个集合的粒度上配置二级缓存。Hibernate的二级缓存策略,是针对于id查询的缓存策略,对于条件查询则毫无作用。
适用条件:很少被修改的数据;不是很重要的数据;不会被并发访问的数据。
不适用条件:经常被修改的数据;财务数据;与其他应用共享的数据。