•说出orm的作用
ORM的全称是ObjectRelationalMapping,即对象关系映射。它的实现思想就是将关系数据库中表的数据映射成为对象,以对象的形式展现,这样开发人员就可以把对数据库的操作转化为对这些对象的操作。因此它的目的是为了方便开发人员以面向对象的思想来实现对数据库的操作。
•简述使用Hibernate的基本流程。
Hibernate的工作流程:
读取并解析配置文件
读取并解析映射信息,创建SessionFactory
打开Sesssion
创建事务Transation
持久化操作
提交事务
关闭Session
关闭SesstionFactory
•叙述Hibernate应用的主要接口和类
Configuration类是Hibernate的入口,它负责配置并启动Hibernate。Hibernate通过Configuration的实例加载配置文件信息,然后读取指定对象关系映射文件的内容并创建SessionFactory实例。
SessionFactory接口负责初始化Hibernate。一个SessionFactory实例对应一个数据库。应用程序从SessionFactory中获得Session实例。
Session接口Session被称为持久化管理器,负责管理与持久化相关的操作:存储、更新、删除和加载对象。
Transaction接口是Hibernate框架的事务接口。它对底层的事务接口做了封装。包括:JDBCAPI和JTA。
•解释Hibernate全局配置文件中常用标记的使用(配置文件是什么及其作用)
•解释对象关系映射文件中常用标记的使用
•阐述常用对象标识符生成器的作用
•解释Hibernate数据类型在Java数据类型与SQL数据类型之间的桥接作用
•叙述Hibernate持久化生命周期及状态转换(Hibernate对象的三种状态是什么?)
瞬时(transient):数据库中没有数据与之对应,超过作用域会被JVM垃圾回收器回收,一般是new出来且与session没有关联的对象。
持久(persistent):数据库中可能有数据与之对应,当前与session有关联,并且相关联的session没有关闭,事务没有提交;持久对象状态发生改变,在事务提交时会影响到数据库(hibernate能检测到)。
脱管(detached):数据库中可能有数据与之对应,但当前没有session与之关联;托管对象状态发生改变,hibernate不能检测到。
•编写一个CRUD示例来运用Session中的常用方法
•说说Hibernate中的update()和saveOrUpdate()的区别。
publicvoidupdate(Objectobject):重附脱管对象,并把它的状态更新到数据库表中。
publicvoidsaveOrUpdate(Objectobject):同时具有save()和update()的功能。
•分类叙述出Set、Bag、List、Map的映射方式
<set>元素:可以映射java.util.Set接口的属性,元素没有顺序且不允许重复。
<list>元素:可以映射java.util.List接口的属性,有顺序,需要在集合属性对应的表中用一个额外的索引保存每个元素的位置。
<bag><idbag>元素:可以映射java.util.Collection接口的属性,元素可重复,但不保存顺序。
<map>元素:可以映射java.util.Map接口的属性,元素以键/值对的形式保存,也是无序的。
<primitive-array><array>:可以映射数组元素。
•编写订单和客户的单向多对一映射示例
<many-to-onename="cus"class="Customer"column="cus_id"></many-to-one>
•编写客户与订单的单向一对多映射示例
<one-to-manyclass=”Orders”/>
•编写客户与订单的双向一对多映射示例
<setname="orders"inverse="true"cascade="save-update">
<keycolumn="customer_id"/>
<one-to-manyclass="Order"/>
</set>
<many-to-onename="customer"column="customer_id"cascade="save-update"/>
•叙述inverse属性的作用
hibernate配置文件中有这么一个属性inverse,它是用来指定关联的控制方的。
inverse属性默认是false,若为false,则关联由自己控制,若为true,则关联
由对方控制
•编写客人与房间的单向一对一基于主键的映射示例
Privateintid;
PrivateStringname;
Privateroomroom;
<hibernate-mappingpackage="com.hbsi.one2one_pk">
<classname="Customer"table="customer_pk">
<idname="id"column="id">
<generatorclass="foreign">
<paramname="property">idcard</param>
</generator>
</id>
<propertyname="name"column="name"type="string"/>
<one-to-onename="room"constrained="true"cascade="all"/>
</class>
</hibernate-mapping>
房间:
Privateintid;
PrivateStringno;
<hibernate-mappingpackage="com.hbsi.one2one_pk">
<classname="IdCard"table="idcard_pk">
<idname="id"column="id">
<generatorclass="native"/>
</id>
<propertyname="no"column="no"type="string"/>
</class>
</hibernate-mapping>
•编写客人与房间的单向一对一基于外键的映射示例
客人:
Privateintid;
PrivateStringname;
Privateroomroom;
<hibernate-mappingpackage="com.hbsi.one2one">
<classname="Customer"table="customer">
<idname="id"column="id">
<generatorclass="native"/>
</id>
<propertyname="name"column="name"type="string"/>
<many-to-onename="room"column="room_card"unique="true"cascade="save-update"/>
</class>
</hibernate-mapping>
房间:
privateintid;
privateStringno;
<hibernate-mappingpackage="com.hbsi.one2one">
<classname="IdCard"table="idcard">
<idname="id"column="id">
<generatorclass="native"/>
</id>
<propertyname="no"column="no"type="string"/>
</class>
</hibernate-mapping>
•编写客人与房间的双向一对一映射示例
用户:
Privateintid;
PrivateStringname;
PrivateSet<Role>roles;
<hibernate-mappingpackage="com.hbsi.many2many">
<classname="Customer"table="customer">
<idname="id"column="id">
<generatorclass="native"/>
</id>
<propertyname="name"column="name"type="string"/>
<setname="roles"table="customer_roles">
<keycolumn="c_id"/>
<many-to-manyclass="Rolescolumn="r_id"/>
</set>
</class>
</hibernate-mapping>
角色:
Privateintid;
PrivateStringname;
<hibernate-mappingpackage="com.hbsi.many2many">
<classname="Roles"table="roles">
<idname="id"column="id">
<generatorclass="native"/>
</id>
<propertyname="name"column="name"type="string"/>
</class>
</hibernate-mapping>
•编写用户和角色的单向多对多映射示例
•编写用户和角色的双向多对多映射示例
•
•叙述三种常用继承映射的优缺点
方式一:整个的继承体系就用一张表、
建立关系模型原则:描述一个继承关系只用一张表,也就是说子类所使用的表与父类相同
优缺点:首先表中引入的区分子类的字段,也就是包括了描述其他字段的字段。其次,如果某个子类的某个属性不能为空,那么在数据库一级不能设置该字段notnull(非空),维护起来方便,只需要修改一个表,灵活性差,表中冗余字段会随着子类的增多而越来越多,在任何情况下,都只需处理一个表,对于单个对象的持久话操作只需要处理一个表
方式二:每个子类一张表,存放子类所特有的属性
建立关系模型原则:每个子类使用一张表,但这些子类所对应的表都关联到基类所对应的表中
优缺点:这种设计方式完全符合关系模型的设计原则,且不存在冗余,
维护起来比较方便,对每个类的修改只需要修改其所对应的表,灵活性很好,完全是参照对象继承的方式进行配置,对于父类的查询需要使用左外链接,对于子类查询需要使用内链接,对于子类的持久话至少要处理两个表
方式三:每个具体类一张表(union-subclass),保存是子类完整信息
建立关系模型原则:每个具体类对应一张表,有多少具体类就需要建立多少个独立的表
优缺点:这种设计方式符合关系模型的设计原则,但有表中存在重复字段的问题。如果需要对基类进行修改,则需要对基类以及该类的子类所对应的所有表都进行修改,映射的灵活性很大,子类可以包括基类属性在内的每一个属性进行单独配置,对于子类的查询只需要访问单独的表,对父类查询怎需要检索所有的表,对于单个对象持久话操作只需要处理一个表
•Hibernate有哪几种查询数据的方式
HQL查询、支持动态绑定参数、支持投影查询、分页查询、连接查询、分组查询,子查询
内置了一些聚集函数
Criteria查询、标准化条件查询,是比HQL更面向对象的查询语句。称为QBC
示例查询(QBE)根据一个给定的实例类实例来构建一个条件查询的方式。先创建一个对象样板,然后检索出所有和这个样板相同的对象。在查询表单中填写的项,可以封装成一个对象,这就是对象样板。
NativeSQLQueries原生SQL查询,就是指直接使用标准SQL语句或特定数据库的SQL进行查询。对原生SQL查询执行的控制是通过SQLQuery接口进行的,通过Session上调用createSQLQuery()来获取这个接口。
•阐述左连接查询和左连接抓取查询的异同
•叙述Criteria的使用步骤
创建Criteria容器
设定查询条件Restrictions
使用add()方法加入条件
执行查询
•阐述事务管理的步骤
数据库事务管理
Hibernate应用程序中的事务管理:Hibernate本事在设计时并不具备事务处理功能,平时所用的Hibernate的事务,只是将底层的JDBCTransaction或者JTATransaction进行一下封装,在外面套上Transaction和Session的外壳,其实底层都是通过委托底层的JDBC或JTA来实现事务的调度功能。
•解释并发引起的各种问题
丢失更新脏读不可重复读幻读
•编写程序来演示乐观锁的作用
乐观锁是假定当前事务操作数据库资源时,不会有其他事务同时访问,因此不做数据库层次上的锁定。为了维护正确数据,hibernate用version和timestamp来实现。
使用版本号进行版本控制
在持久化类中定义一个version属性。类型只能是整型的。
在映射文件中添加<version>标签,注意一定要放在<id>元素后面
publicvoidtestTx1(){
Sessionsession=HibernateSessionFactory.getSession();
Transactiontx=session.beginTransaction();
Studentstu=null;
try{
stu=(Student)session.get(Student.class,1);
System.out.println(stu.getName());
System.out.println("version="+stu.getVersion());
stu.setName("在第一个事务中修改");
session.getTransaction().commit();
}
catch(HibernateExceptionhe){
tx.rollback();
he.printStackTrace();
}
session.close();
//操作完毕
System.out.println("tx1操作完成后------");
System.out.println(stu.getName());
System.out.println("version="+stu.getVersion());
}
•叙述悲观锁的实现原理及使用步骤
悲观锁是假定当前事务操作数据资源时,一定有其他事务同时访问该数据资源,所以先锁定资源。
一般实现方式是由数据库来实现,采用独占锁来锁定资源。使用get(),load(),是可以显示指定锁定模式:LockMode.UPGRADE
Session.get(Student.class,1,LockMode.UPGRADE);
•
•分析各种延迟加载策略的异同点
延迟加载
延迟加载(load)是Hibernate为提高程序执行效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。
对象的延迟加载
Hibernate延迟加载之实体对象的延迟加载:
如果想对实体对象使用延迟加载,必须要在实体的映射配置文件中进行相应的配置,如下所示:
1.<Hibernate-mapping><classname=”com.neusoft.entity.User”
2.table=”user”lazy=”true”>
3.……</class></Hibernate-mapping>
通过将class的lazy属性设置为true,来开启实体的延迟加载特性。如果我们运行下面的代码:
1.Useruser=(User)session.load(User.class,”1”);(1)
2.System.out.println(user.getName());(2)
实体的延迟加载是用通过中间代理类完成的,所以只有session.load()方法才会利用实体延迟加载,因为只有session.load()方法才会返回实体类的代理类对象。
对象里的属性延迟加载
1.<Hibernate-mapping>
2.<classname=”com.neusoft.entity.User”table=”user”>……
3.<propertyname=”resume”type=”java.sql.Clob”
4.column=”resume”lazy=”true”/>
5.</class></Hibernate-mapping>
通过对<property>元素的lazy属性设置true来开启属性的延迟加载,在Hibernate3中为了实现属性的延迟加载,使用了类增强器来对实体类的Class文件进行强化处理,通过增强器的增强,
1.Stringsql=”fromUseruserwhereuser.name=’zx’”;
2.Queryquery=session.createQuery(sql);(1)
3.Listlist=query.list();
4.for(inti=0;i<list.size();i++)
5.{Useruser=(User)list.get(i);
6.System.out.println(user.getName());
7.System.out.println(user.getResume());(2)
8.}
当执行到(1)处时,会生成类似如下的SQL语句:
1.Selectid,age,namefromuserwherename=’zx’;
这时Hibernate会检索User实体中所有非延迟加载属性对应的字段数据,当执行到(2)处时,会生成类似如下的SQL语句:
1.Selectresumefromuserwhereid=’1’;
这时会发起对resume字段数据真正的读取操作。
集合延迟加载
Hibernate延迟加载之集合类型的延迟加载:
1.<Hibernate-mapping>
2.<classname=”com.neusoft.entity.User”table=”user”>…..
3.<setname=”addresses”table=”address”lazy=”true”
4.inverse=”true”>cacheusage=”read-only”/><<keycolumn=”user_id”/>
5.<one-to-manyclass=”com.neusoft.entity.Arrderss”/></set>
6.</class></Hibernate-mapping>
通过将<set>元素的lazy属性设置为true来开启集合类型的延迟加载特性。我们看下面的代码:
1.Useruser=(User)session.load(User.class,”1”);
2.Collectionaddset=user.getAddresses();(1)
3.Iteratorit=addset.iterator();(2)
4.while(it.hasNext()){Addressaddress=(Address)it.next();
5.System.out.println(address.getAddress());}
这里我们应用了<cacheusage=”read-only”/>配置,如果采用这种策略来配置集合类型,Hibernate将只会对数据索引进行缓存,而不会对集合中的实体对象进行缓存。
•鉴别各种抓取策略的异同点
通过配置“抓取策略”来直接影响session的get()和load()方法的查询效果。
1.单端关联<many-to-one><one-to_one>上的抓取策略。
可以给单端关联的映射元素添加fetch属性。fetch属性有2个可选值
select:作为默认值,它的策略是当需要使用到关联对象的数据时,另外单独发送一条select语句抓取当前对象的关联对象的数据。即延迟加载。
join:它的策略是在同一条select语句使用连接来获得对象的数据和它关联对象的数据,此时关联对象的延迟加载失效。
集合属性上的抓取策略
在集合属性的映射元素上可以添加fetch属性,它有3个可选值。
select:作为默认值,它的策略是当需要使用所关联集合的数据时,另外单独发送一条select语句抓取当前对象的关联集合,即延迟加载。
join:在同一条select语句使用连接来获得对方的关联集合。此时关联集合上的lazy会失效。
subselect:另外发送一条查询语句(或子查询语句)抓取在前面查询到的所有实体对象的关联集合。这个策略对HQL的查询也起作用。
•简述hibernate的缓存机制
缓存的作用就是降低应用程序直接读写永久性数据存储源的频率,从而增强应用的运行性能。
缓存的实现不仅需要作为物理介质的硬件(内存),同时还需要用于管理缓存并发访问和过期等策略的软件。
缓存的范围分为以下三类:
1)事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。
2)进程范围:缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。
3)集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据一致性。对于大多数应用来说,应该慎用集群范围的缓存,因为访问的速度并不一定比直接访问数据库数据的速度快很多。
缓存的并发访问策略:
1)事务型(Transactional)策略
2)读写型(Read-Write)策略
3)非严格读写型(Nonstrict-read-write)策略
4)只读型(Read-only)策略
•归纳一级缓存的管理方式
Session的CRUD方法及调用查询接口的list(),iterate()等方法时
,不存在加入到缓存中,存在就直接使用,不去加载数据库里了,当Hibernate清理缓存时默认的是提交事务的时候,关闭session时清除所有的对象。
一级缓存不能控制缓存的数量,所以要注意大批量操作数据时可能造成内存溢出;可以用
evict(Objectobj):从缓存中清除指定的持久化对象。
clear():清空缓存中所有持久化对象。
flush():进行清理缓存(此时缓存中的数据并不丢失)的操作,让缓存和数据库同步执行一些列sql语句,但不提交事务。
commit():先调用flush()方法,然后提交事务.则意味着提交事务意味着对数据库操作永久保存下来。
当做批量插入或批量更新时,必须通过经常调用Session的flush()以及稍后调用clear()来控制一级缓存的大小,这样内存才能保证足够的空间。
•session的load()和get()的区别。
1.load()方法从来就是假定数据在数据库中是存在的,在使用时如果不存在则会抛出ObjectNotFoundException;而get()方法不会假定数据在数据库中存在,如果不存在则返回null
2.load()方法返回的是实体类的代理类,因此load()可以使用延迟加载策略来加载对象
get()方法返回的不一定是实体类,可能是代理类,因为get()方法如果在session缓存(一级缓存)中找到了该id对应的对象,如果刚好该对象前面是被代理过的,如被load方法使用过,或者被其他关联对象延迟加载过,那么返回的还是原先的代理对象,而不是实体类对象。
3.load()方法查询数据时会先从session缓存(一级缓存)中查找,如果没有找到则会创建代理类,该代理类仅仅初始化了OID属性,当第一次访问其他属性值时,则会依次从二级缓存-->数据库查找,直到找到数据,最后将所有属性值赋给代理类。而get()方法则会直接按照一级缓存-->二级缓存-->数据库的顺序查找。
4.也许别人把数据库中的数据修改了,load如何在缓存中找到了数据,则不会再访问数据库,而get则会返回最新数据
•说明Hibernate应用优化的手段
一、批量修改和删除
在Hibernate2中,如果需要对任何数据进行修改和删除操作,都需要先执行查询操作,在得到要修改或者删除的数据后,再对该数据进行相应的操作处理。在数据量少的情况下采用这种处理方式没有问题,但需要处理大量数据的时候就可能存在以下的问题:
占用大量的内存。
需要多次执行update/delete语句,而每次执行只能处理一条数据。
以上两个问题的出现会严重影响系统的性能。因此,在Hibernate3中引入了用于批量更新或者删除数据的HQL语句。这样,开发人员就可以一次更新或者删除多条记录,而不用每次都一个一个地修改或者删除记录了。
如果要删除所有的User对象(也就是User对象所对应表中的记录),则可以直接使用下面的HQL语句:
deleteUser
而在执行这个HQL语句时,需要调用Query对象的executeUpdate()方法,具体的实例如下所示:
StringHQL="deleteUser";
Queryquery=session.createQuery(HQL);
intsize=query.executeUpdate();
采用这种方式进行数据的修改和删除时与直接使用JDBC的方式在性能上相差无几,是推荐使用的正确方法。
如果不能采用HQL语句进行大量数据的修改,也就是说只能使用取出再修改的方式时,也会遇到批量插入时的内存溢出问题,所以也要采用上面所提供的处理方法来进行类似的处理。
二、使用SQL执行批量操作
在进行批量插入、修改和删除操作时,直接使用JDBC来执行原生态的SQL语句无疑会获得最佳的性能,这是因为在处理的过程中省略或者简化了以下处理内容:
●HQL语句到SQL语句的转换。
●Java对象的初始化。
●Java对象的缓存处理。
但是在直接使用JDBC执行SQL语句时,有一个最重要的问题就是要处理缓存中的Java对象。因为通过这种底层方式对数据的修改将不能通知缓存去进行相应的更新操作,以保证缓存中的对象与数据库中的数据是一致的。
三、提升数据库查询的性能
数据库查询性能的提升也是涉及到开发中的各个阶段,在开发中选用正确的查询方法无疑是最基础也最简单的。
1、SQL语句的优化
使用正确的SQL语句可以在很大程度上提高系统的查询性能。获得同样数据而采用不同方式的SQL语句在性能上的差距可能是十分巨大的。
由于Hibernate是对JDBC的封装,SQL语句的产生都是动态由Hibernate自动完成的。Hibernate产生SQL语句的方式有两种:一种是通过开发人员编写的HQL语句来生成,另一种是依据开发人员对关联对象的访问来自动生成相应的SQL语句。
至于使用什么样的SQL语句可以获得更好的性能要依据数据库的结构以及所要获取数据的具体情况来进行处理。在确定了所要执行的SQL语句后,可以通过以下三个方面来影响Hibernate所生成的SQL语句:
HQL语句的书写方法。
查询时所使用的查询方法。
对象关联时所使用的抓取策略。
2、使用正确的查询方法
在前面已经介绍过,执行数据查询功能的基本方法有两种:一种是得到单个持久化对象的get()方法和load()方法,另一种是Query对象的list()方法和iterator()方法。在开发中应该依据不同的情况选用正确的方法。
get()方法和load()方法的区别在于对二级缓存的使用上。load()方法会使用二级缓存,而get()方法在一级缓存没有找到的情况下会直接查询数据库,不会去二级缓存中查找。在使用中,对使用了二级缓存的对象进行查询时最好使用load()方法,以充分利用二级缓存来提高检索的效率。
list()方法和iterator()方法之间的区别可以从以下几个方面来进行比较。
执行的查询不同
list()方法在执行时,是直接运行查询结果所需要的查询语句,而iterator()方法则是先执行得到对象ID的查询,然后再根据每个ID值去取得所要查询的对象。因此,对于list()方式的查询通常只会执行一个SQL语句,而对于iterator()方法的查询则可能需要执行N+1条SQL语句(N为结果集中的记录数)。
iterator()方法只是可能执行N+1条数据,具体执行SQL语句的数量取决于缓存的情况以及对结果集的访问情况。
缓存的使用
list()方法只能使用二级缓存中的查询缓存,而无法使用二级缓存对单个对象的缓存(但是会把查询出的对象放入二级缓存中)。所以,除非重复执行相同的查询操作,否则无法利用缓存的机制来提高查询的效率。
iterator()方法则可以充分利用二级缓存,在根据ID检索对象的时候会首先到缓存中查找,只有在找不到的情况下才会执行相应的查询语句。所以,缓存中对象的存在与否会影响到SQL语句的执行数量。
对于结果集的处理方法不同
list()方法会一次获得所有的结果集对象,而且它会依据查询的结果初始化所有的结果集对象。这在结果集非常大的时候必然会占据非常多的内存,甚至会造成内存溢出情况的发生。
iterator()方法在执行时不会一次初始化所有的对象,而是根据对结果集的访问情况来初始化对象。因此在访问中可以控制缓存中对象的数量,以避免占用过多缓存,导致内存溢出情况的发生。使用iterator()方法的另外一个好处是,如果只需要结果集中的部分记录,那么没有被用到的结果对象根本不会被初始化。所以,对结果集的访问情况也是调用iterator()方法时执行数据库SQL语句多少的一个因素。
所以,在使用Query对象执行数据查询时应该从以上几个方面去考虑使用何种方法来执行数据库的查询操作。
四、使用正确的抓取策略
所谓抓取策略(fetchingstrategy)是指当应用程序需要利用关联关系进行对象获取的时候,Hibernate获取关联对象的策略。抓取策略可以在O/R映射的元数据中声明,也可以在特定的HQL或条件查询中声明。
Hibernate3定义了以下几种抓取策略。
连接抓取(Joinfetching)
连接抓取是指Hibernate在获得关联对象时会在SELECT语句中使用外连接的方式来获得关联对象。
查询抓取(Selectfetching)
查询抓取是指Hibernate通过另外一条SELECT语句来抓取当前对象的关联对象的方式。这也是通过外键的方式来执行数据库的查询。与连接抓取的区别在于,通常情况下这个SELECT语句不是立即执行的,而是在访问到关联对象的时候才会执行。
子查询抓取(Subselectfetching)
子查询抓取也是指Hibernate通过另外一条SELECT语句来抓取当前对象的关联对象的方式。与查询抓取的区别在于它所采用的SELECT语句的方式为子查询,而不是通过外连接。
批量抓取(Batchfetching)
批量抓取是对查询抓取的优化,它会依据主键或者外键的列表来通过单条SELECT语句实现管理对象的批量抓取。
以上介绍的是Hibernate3所提供的抓取策略,也就是抓取关联对象的手段。为了提升系统的性能,在抓取关联对象的时机上,还有以下一些选择。