Java面试宝典.docx
- 文档编号:11096005
- 上传时间:2023-02-25
- 格式:DOCX
- 页数:26
- 大小:30.32KB
Java面试宝典.docx
《Java面试宝典.docx》由会员分享,可在线阅读,更多相关《Java面试宝典.docx(26页珍藏版)》请在冰豆网上搜索。
Java面试宝典
Hibernate缓存机制:
缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。
缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
缓存的介质一般是内存,所以读写速度很快。
但如果缓存中存放的数据量非常大时,也会用硬盘作为缓存介质。
缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期。
Hibernate的缓存包括Session的缓存和SessionFactory的缓存,其中SessionFactory的缓存又可以分为两类:
内置缓存和外置缓存。
Session的缓存是内置的,不能被卸载,也被称为Hibernate的第一级缓存。
SessionFactory的内置缓存和Session的缓存在实现方式上比较相似,前者是SessionFactory对象的一些集合属性包含的数据,后者是指Session的一些集合属性包含的数据。
SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的拷贝,而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来,SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。
SessionFactory的外置缓存是一个可配置的插件。
在默认情况下,SessionFactory不会启用这个插件。
外置缓存的数据是数据库数据的拷贝,外置缓存的介质可以是内存或者硬盘。
SessionFactory的外置缓存也被称为Hibernate的第二级缓存。
Hibernate的这两级缓存都位于持久化层,存放的都是数据库数据的拷贝,那么它们之间的区别是什么呢?
为了理解二者的区别,需要深入理解持久化层的缓存的两个特性:
缓存的范围和缓存的并发访问策略。
持久化层的缓存的范围
缓存的范围决定了缓存的生命周期以及可以被谁访问。
缓存的范围分为三类。
1事务范围:
缓存只能被当前事务访问。
缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。
在此范围下,缓存的介质是内存。
事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的的对象形式。
2进程范围:
缓存被进程内的所有事务共享。
这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。
缓存的生命周期依赖于进程的生命周期,进程结束时,缓存也就结束了生命周期。
进程范围的缓存可能会存放大量的数据,所以存放的介质可以是内存或硬盘。
缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据形式。
松散的对象数据形式有点类似于对象的序列化数据,但是对象分解为松散的算法比对象序列化的算法要求更快。
3集群范围:
在集群环境中,缓存被一个机器或者多个机器的进程共享。
缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式。
对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定会比直接访问数据库数据的速度快多少。
持久化层可以提供多种范围的缓存。
如果在事务范围的缓存中没有查到相应的数据,还可以到进程范围或集群范围的缓存内查询,如果还是没有查到,那么只有到数据库中查询。
事务范围的缓存是持久化层的第一级缓存,通常它是必需的;进程范围或集群范围的缓存是持久化层的第二级缓存,通常是可选的。
持久化层的缓存的并发访问策略
当多个并发的事务同时访问持久化层的缓存的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。
在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。
因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。
事务型:
仅仅在受管理环境中适用。
它提供了RepeatableRead事务隔离级别。
对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。
读写型:
提供了ReadCommitted事务隔离级别。
仅仅在非集群的环境中适用。
对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。
非严格读写型:
不保证缓存与数据库中数据的一致性。
如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。
对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。
只读型:
对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。
事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。
事务隔离级别越高,并发性能就越低。
什么样的数据适合存放到第二级缓存中?
1、很少被修改的数据
2、不是很重要的数据,允许出现偶尔并发的数据
3、不会被并发访问的数据
4、参考数据
不适合存放到第二级缓存的数据?
1、经常被修改的数据
2、财务数据,绝对不允许出现并发
3、与其他应用共享的数据。
Hibernate的二级缓存
如前所述,Hibernate提供了两级缓存,第一级是Session的缓存。
由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存。
第一级缓存是必需的,不允许而且事实上也无法比卸除。
在第一级缓存中,持久化类的每个实例都具有唯一的OID。
第二级缓存是一个可插拔的的缓存插件,它是由SessionFactory负责管理。
由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此第二级缓存是进程范围或者集群范围的缓存。
这个缓存中存放的对象的松散数据。
第二级对象有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。
缓存适配器用于把具体的缓存实现软件与Hibernate集成。
第二级缓存是可选的,可以在每个类或每个集合的粒度上配置第二级缓存。
Hibernate的二级缓存策略的一般过程如下:
1)条件查询的时候,总是发出一条select*fromtable_namewhere….(选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。
2)把获得的所有数据对象根据ID放入到第二级缓存中。
3)当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4)删除、更新、增加数据的时候,同时更新缓存。
Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。
为此,Hibernate提供了针对条件查询的Query缓存。
Hibernate的Query缓存策略的过程如下:
1)Hibernate首先根据这些信息组成一个QueryKey,QueryKey包括条件查询的请求一般信息:
SQL,SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。
2)Hibernate根据这个QueryKey到Query缓存中查找对应的结果列表。
如果存在,那么返回这个结果列表;如果不存在,查询数据库,获取结果列表,把整个结果列表根据QueryKey放入到Query缓存中。
3)QueryKey中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的QueryKey都要从缓存中清空。
Hibernate事务管理机制
JTA提供了跨Session的事务管理能力。
这一点是与JDBCTransaction最大的差异。
JDBC事务由Connnection管理,也就是说,事务管理实际上是在JDBCConnection中实现。
事务周期限于Connection的生命周期之类。
同样,对于基于JDBCTransaction的Hibernate事务管理机制而言,事务管理在Session所依托的JDBCConnection中实现,事务周期限于Session的生命周期。
JTA事务管理则由JTA容器实现,JTA容器对当前加入事务的众多Connection进行调度,实现其事务性要求。
JTA的事务周期可横跨多个JDBCConnection生命周期。
同样对于基于JTA事务的Hibernate而言,JTA事务横跨可横跨多个Session。
JTA事务是由JTAContainer维护,而参与事务的Connection无需对事务管理进行干涉。
这也就是说,如果采用JTATransaction,我们不应该再调用HibernateTransaction功能。
1引子
try…catch…finally恐怕是大家再熟悉不过的语句了,而且感觉用起来也是很简单,逻辑上似乎也是很容易理解。
不过,我亲自体验的“教训”告诉我,这个东西可不是想象中的那么简单、听话。
不信?
那你看看下面的代码,“猜猜”它执行后的结果会是什么?
不要往后看答案、也不许执行代码看真正答案哦。
如果你的答案是正确,那么这篇文章你就不用浪费时间看啦。
publicclassTestException
{
publicTestException()
{
}
booleantestEx()throwsException
{
booleanret=true;
try
{
ret=testEx1();
}
catch(Exceptione)
{
System.out.println("testEx,catchexception");
ret=false;
throwe;
}
finally
{
System.out.println("testEx,finally;returnvalue="+ret);
returnret;
}
}
booleantestEx1()throwsException
{
booleanret=true;
try
{
ret=testEx2();
if(!
ret)
{
returnfalse;
}
System.out.println("testEx1,attheendoftry");
returnret;
}
catch(Exceptione)
{
System.out.println("testEx1,catchexception");
ret=false;
throwe;
}
finally
{
System.out.println("testEx1,finally;returnvalue="+ret);
returnret;
}
}
booleantestEx2()throwsException
{
booleanret=true;
try
{
intb=12;
intc;
for(inti=2;i>=-2;i--)
{
c=b/i;
System.out.println("i="+i);
}
returntrue;
}
catch(Exceptione)
{
System.out.println("testEx2,catchexception");
ret=false;
throwe;
}
finally
{
System.out.println("testEx2,finally;returnvalue="+ret);
returnret;
}
}
publicstaticvoidmain(String[]args)
{
TestExceptiontestException1=newTestException();
try
{
testException1.testEx();
}
catch(Exceptione)
{
e.printStackTrace();
}
}
}
你的答案是什么?
是下面的答案吗?
i=2
i=1
testEx2,catchexception
testEx2,finally;returnvalue=false
testEx1,catchexception
testEx1,finally;returnvalue=false
testEx,catchexception
testEx,finally;returnvalue=false
如果你的答案真的如上面所说,那么你错啦。
^_^,那就建议你仔细看一看这篇文章或者拿上面的代码按各种不同的情况修改、执行、测试,你会发现有很多事情不是原来想象中的那么简单的。
现在公布正确答案:
i=2
i=1
testEx2,catchexception
testEx2,finally;returnvalue=false
testEx1,finally;returnvalue=false
testEx,finally;returnvalue=false
2基础知识
2.1相关概念
例外是在程序运行过程中发生的异常事件,比如除0溢出、数组越界、文件找不到等,这些事件的发生将阻止程序的正常运行。
为了加强程序的鲁棒性,程序设计时,必须考虑到可能发生的异常事件并做出相应的处理。
C语言中,通过使用if语句来判断是否出现了例外,同时,调用函数通过被调用函数的返回值感知在被调用函数中产生的例外事件并进行处理。
全程变量ErroNo常常用来反映一个异常事件的类型。
但是,这种错误处理机制会导致不少问题。
Java通过面向对象的方法来处理例外。
在一个方法的运行过程中,如果发生了例外,则这个方法生成代表该例外的一个对象,并把它交给运行时系统,运行时系统寻找相应的代码来处理这一例外。
我们把生成例外对象并把它提交给运行时系统的过程称为抛弃(throw)一个例外。
运行时系统在方法的调用栈中查找,从生成例外的方法开始进行回朔,直到找到包含相应例外处理的方法为止,这一个过程称为捕获(catch)一个例外。
2.2Throwable类及其子类
用面向对象的方法处理例外,就必须建立类的层次。
类Throwable位于这一类层次的最顶层,只有它的后代才可以做为一个例外被抛弃。
图1表示了例外处理的类层次。
从图中可以看出,类Throwable有两个直接子类:
Error和Exception。
Error类对象(如动态连接错误等),由Java虚拟机生成并抛弃(通常,Java程序不对这类例外进行处理);Exception类对象是Java程序处理或抛弃的对象。
它有各种不同的子类分别对应于不同类型的例外。
其中类RuntimeException代表运行时由Java虚拟机生成的例外,如算术运算例外ArithmeticException(由除0错等导致)、数组越界例外ArrayIndexOutOfBoundsException等;其它则为非运行时例外,如输入输出例外IOException等。
Java编译器要求Java程序必须捕获或声明所有的非运行时例外,但对运行时例外可以不做处理。
2.3 异常处理关键字
Java的异常处理是通过5个关键字来实现的:
try,catch,throw,throws,finally。
JB的在线帮助中对这几个关键字是这样解释的:
Throws:
Liststheexceptionsamethodcouldthrow.
Throw:
Transferscontrolofthemethodtotheexceptionhandler.
Try:
Openingexception-handlingstatement.
Catch:
Capturestheexception.
Finally:
Runsitscodebeforeterminatingtheprogram.
2.3.1try语句
try语句用大括号{}指定了一段代码,该段代码可能会抛弃一个或多个例外。
2.3.2catch语句
catch语句的参数类似于方法的声明,包括一个例外类型和一个例外对象。
例外类型必须为Throwable类的子类,它指明了catch语句所处理的例外类型,例外对象则由运行时系统在try所指定的代码块中生成并被捕获,大括号中包含对象的处理,其中可以调用对象的方法。
catch语句可以有多个,分别处理不同类的例外。
Java运行时系统从上到下分别对每个catch语句处理的例外类型进行检测,直到找到类型相匹配的catch语句为止。
这里,类型匹配指catch所处理的例外类型与生成的例外对象的类型完全一致或者是它的父类,因此,catch语句的排列顺序应该是从特殊到一般。
也可以用一个catch语句处理多个例外类型,这时它的例外类型参数应该是这多个例外类型的父类,程序设计中要根据具体的情况来选择catch语句的例外处理类型。
2.3.3finally语句
try所限定的代码中,当抛弃一个例外时,其后的代码不会被执行。
通过finally语句可以指定一块代码。
无论try所指定的程序块中抛弃或不抛弃例外,也无论catch语句的例外类型是否与所抛弃的例外的类型一致,finally所指定的代码都要被执行,它提供了统一的出口。
通常在finally语句中可以进行资源的清除工作。
如关闭打开的文件等。
2.3.4throws语句
throws总是出现在一个函数头中,用来标明该成员函数可能抛出的各种异常。
对大多数Exception子类来说,Java编译器会强迫你声明在一个成员函数中抛出的异常的类型。
如果异常的类型是Error或RuntimeException,或它们的子类,这个规则不起作用,因为这在程序的正常部分中是不期待出现的。
如果你想明确地抛出一个RuntimeException,你必须用throws语句来声明它的类型。
2.3.5throw语句
throw总是出现在函数体中,用来抛出一个异常。
程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。
3关键字及其中语句流程详解
3.1try的嵌套
你可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部,写另一个try语句保护其他代码。
每当遇到一个try语句,异常的框架就放到堆栈上面,直到所有的try语句都完成。
如果下一级的try语句没有对某种异常进行处理,堆栈就会展开,直到遇到有处理这种异常的try语句。
下面是一个try语句嵌套的例子。
classMultiNest{
staticvoidprocedure(){
try{
inta=0;
intb=42/a;
}catch(java.lang.ArithmeticExceptione){
System.out.println("inprocedure,catchArithmeticException:
"+e);
}
}
publicstaticvoidmain(Stringargs[]){
try{
procedure();
}catch(java.lang.Exceptione){
System.out.println("inmain,catchException:
"+e);
}
}
}
这个例子执行的结果为:
inprocedure,catchArithmeticException:
java.lang.ArithmeticException:
/byzero
成员函数procedure里有自己的try/catch控制,所以main不用去处理ArrayIndexOutOfBoundsException;当然如果如同最开始我们做测试的例子一样,在procedure中catch到异常时使用throwe;语句将异常抛出,那么main当然还是能够捕捉并处理这个procedure抛出来的异常。
例如在procedure函数的catch中的System.out语句后面增加throwe;语句之后,执行结果就变为:
inprocedure,catchArithmeticException:
java.lang.ArithmeticException:
/byzero
inmain,catchException:
java.lang.ArithmeticException:
/byzero
3.2try-catch程序块的执行流程以及执行结果
相对于try-catch-finally程序块而言,try-catch的执行流程以及执行结果还是比较简单的。
首先执行的是try语句块中的语句,这时可能会有以下三种情况:
1.如果try块中所有语句正常执行完毕,那么就不会有其他的“动做”被执行,整个try-catc
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java 面试 宝典