Java知识体系
目的:为了更好的认识java体系
1、java基础知识
1.1 基础知识
1.1.1 配置环境变量
新建java_home变量(安装目录),值为:C:\Program Files\Java\jdk1.6.0_14;此值为JDK的安装位置。
新建classpath变量(类加载路径),值为:.;%java_home%\lib;%java_home%\lib\tools.jar
修改path变量(使得系统可以在任何路径下识别java命令),值为:%java_home%\bin;%java_home%\jre\bin
1.1.2 访问修饰符
Public:任何地方可以访问
Private:只有自身可以访问
Protected:同一个包和子类可以访问
默认:只有同一个包内可以访问
1.2 对象的清理
1.2.1认识Java的自动垃圾回收
垃圾回收是Java语言的一大特性,方便了编程,是以消耗性能为代价的。而垃圾在这里只无用的对象。而C++是需要程序员自己写析构函数来释放内存的,麻烦,也有可能忘记而导致内存泄露。
Java语言对内存的分配管理是通过JVM内部机制决定的。程序员可以不关心其处理。
1.2.2垃圾回收的原理和意义
Java虚拟机中有个称之为垃圾回收器的东西,实际上这个东西也许真正不存在,或者是已经集成到JVM中了,但这无关紧要,我们仍然可以称为为垃圾回收器。
垃圾回收器的作用是查找和回收(清理)无用的对象。以便让JVM更有效的使用内存。
垃圾回收器的运行时间是不确定的,由JVM决定,在运行时是间歇执行的。虽然可以通过System.gc()来强制回收垃圾,但是这个命令下达后无法保证JVM会立即响应执行,但经验表明,下达命令后,会在短期内执行你的请求。JVM通常会感到内存紧缺时候去执行垃圾回收操作。
垃圾回收过于频繁会导致性能下降,过于稀疏会导致内存紧缺。这个JVM会将其控制到最好,不用程序员担心。但有些程序在短期会吃掉大量内存,而这些恐怖的对象很快使用结束了,这时候也许有必要强制下达一条垃圾回命令,这是很有必要的,以便有更多可用的物理内存。
从上面了解到,没有用的对象就是垃圾。准确的说,当没有任何线程访问一个对象时,该对象就符合垃圾回收的条件。
对于String,存在一个字符串池,这个不属于本文讨论的范围,字符串池中的垃圾回收,算法和这里所讨论的垃圾回收完全是两码事。但是不得不说的是,字符串的胡乱拼接,往往导致性能急剧下降,尤其是在庞大的循环语句中,拼接字符串就是在让程序慢性自杀。这也是很多Java程序员容易犯的毛病。
字符串既然是池,就是为了缓冲,为了有更高的命中率,因此垃圾回收的频率也许会比JVM对象垃圾回收器要低很多。
垃圾回收器仅仅能做的是尽可能保证可用内存的使用效率,让可用内存得到高效的管理。程序员可以影响垃圾回收的执行,但不能控制。
1.2.3通过编程影响垃圾回收
虽然程序员无法控制JVM的垃圾回收机制。但是可以通过编程的手段来影响,影响的方法是,让对象符合垃圾回收条件。
分别说来有一下几种:
1、将无用对象赋值为null.
2、重新为引用变量赋值。比如:
Person p = new Person("aaa");
p = new Person("bbb");
这样,new Person("aaa")这个对象就是垃圾了——符合垃圾回收条件了。
3、让相互联系的对象称为“岛”对象
Person p1 = new Person("aaa");
Person p2 = new Person("bbb");
Person p3 = new Person("ccc");
p1=p2; p2=p3; p3=p1;
p1=null; p2=null; p3=null;
在没有对p1、p2、p3置null之前,它们之间是一种三角恋关系。分别置null,三角恋关系依然存在,但是三个变量不在使用它们了。三个Person对象就组成了一个孤岛,最后死在堆上——被垃圾回收掉。
1.2.4强制的垃圾回收System.gc()
实际上这里的强制,是程序员的意愿、建议,什么时候执行是JVM的垃圾回收器说了算。
调用垃圾回收也不一定能保证未使用的对象一定能从内存中删除。
唯一能保证的是,当你内存在极少的情况,垃圾回收器在程序抛出OutofMemaryException之前运行一次。
1.3 集合
1.4 文件流
1.4.1输出字节流
1.4.2 输入字节流
1.4.3 输入输出字符流
1.5 网络
1.5.1 TCP和UDP的区别
TCP是面向连接的通信协议,TCP提供两台计算机之间的可靠的无差别错的数据传输。
UDP是无连接通信协议,UDP不保证可靠的数据的传输,但能够向若干个目标发送数据,接受发自若干个源的数据。
1.6 多线程
1.6.1 实现多线程
实现多线程:继承自Thread类,可以重写run()方法去覆盖去Thread中的run()方法;实现Runnable接口并实现它的run()方法。
线程的优先级代表该线程的重要程度(不是绝对的),当有多个线程同时处于可执行状态并等待获得 CPU 时间时,线程调度系统根据各个线程的优先级来决定给谁分配 CPU 时间,优先级高的线程有更大的机会获得 CPU 时间,优先级低的线程也不是没有机会,只是机会要小一些罢了。
1.6.2 多线程方法的解释和区别
1.start()是启动一个线程。
2.join()是直到执行完(或强制执行一段时间)当前的线程后才往下执行主线程或其他的线程
3.stop()是停止当前的线程。
阻塞线程比较多
sleep() 方法:sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。可能给其他线程执行的机会(自私,睡着了,不释放锁,时间到了才放锁)
suspend() 和 resume() 方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。
yield() 方法:yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。不过yield()只能使同等级别的线程获取执行的机会(公平竞争,释放大家再次选举)。而sleep(1000)使同级别或不同级别的都有可能。
wait() 和 notify() 和notifyAll()方法是Object中定义的方法:
必须在synchronized代码块中使用,在synchronized代码被执行期间,线程可以调用对象的wait()方法,释放对象的锁标志,进入等待的状态,并且可以调用notify()或者notifyAll()方法通知正在等待的其他线程。notify()通知的是等待队列中的第一个线程,notifyAll()通知的是等待队列中的所有数量。
几个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用。
1.6.3 wait和sleep的区别
wait()在object类里定义;sleep()在Thread类里定义。
wait()方法只能放在同步方法或同步块中,表示资源同步时,线程需要等待。
sleep()方法可放在任何位置,表示当前线程睡眠。
wait()方法会释放对象锁;sleep()不会释放对象锁。
wait()方法要等待唤醒之后,线程才会继续执行。
sleep()则是休眠一段时间,线程自动恢复执行.
sleep()必须捕获异常,而wait(),notify()和notifyAll()不需要捕获异常
1.6.4 notify()和notifyAll()的区别
notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。
notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。
1.6.5 run和start的区别
run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;
start()方法是启动(即开辟)一个线程的方法,因此线程的启动必须通过此方法,
而run()方法,只是Thread类的一个方法,它本身并不能开辟线程。
1.7 异常
运行时异常和Error是catch不了的。
throw和throws的区别
throw是抛出一个具体的异常类,产生一个异常。
throws则是在方法名后标出该方法会产生何种异常需要方法的使用者捕获并处理。
自定义异常必须继承Exception类。
1.8 格式化
略
1.9 数字运算
略
1.10 正则表达式
1.10.1次数限定符
* 0次或多次
+ 至少1次
? 0或1次
{n} 指定n次
{n,} 至少n次
{n,m} 匹配n-m次
2、JAVA面向对象的基础知识
2.1 面向对象的三个特征
2.1.1 封装
属性是私有,方法的公开的。
自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
2.1.2 继承
它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
2.1.3 多态
实现多态有2种方式:静态多态(编译时的多态,在同一个类中)和动态多态(一个对象可以有多种类型)。
是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
一句话:允许将子类对象赋值给父类的引用或父类指向子类的对象。
2.1.4 总结
封装可以隐藏实现细节,使得代码模块化。
继承可以扩展已存在的代码模块(类)。
封装和继承属于代码重用。
多态是为了接口重用。
2.1.5 继承、聚合、组合、关联的区别
继承很好理解,下图表示(实线):
实现也很好理解,下图表示(虚线):
依赖(代码中通常表示的是局部变量、方法参数、返回值),下图表示:
关联(代码中通常表示的类的属性),下图表示:
聚合(关联的一种,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享;has-a,空心菱形),下图表示
组合(关联的一种,表示更强的关系,整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束,contains-a,实心菱形),下图表示
2.2 抽象、接口定义及区别
抽象类里面可以有非抽象方法
但接口里只能有抽象方法
声明方法的存在而不去实现它的类被叫做抽像类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其类型是一个抽像类,并让它指向具体子类的一个实例。不能有抽像构造函数或抽像静态方法。Abstract 类的子类为它们父类中的所有抽像方法提供实现,否则它们也是抽像类为。取而代之,在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。
接口(interface)是抽像类的变体。在接口中,所有方法都是抽像的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽像的,没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。然后,它可以在实现了该接口的类的任何对像上调用接口的方法。由于有抽像类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口。
2.3 匿名类、内部类、匿名内部类
在一个类中定义另外一个类,这个类就叫做内部类或内置类 (inner class) 。
内部类分为成员内部类、静态嵌套类、方法内部类、匿名内部类。
内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类命和$符号。内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是private的。
我们为什么使用内部类?
A、在内部类(inner class)中,可以随意的访问外部类的成员,这可以让我们更好地组织管理我们的代码,增强代码的可读性。
B、内部类可以用于创建适配器类,适配器类是用于实现接口的类。使用内部类来实现接口,可以更好地定位与接口关联的方法在代码中的位置。
C、内部类的更多用法(匿名inner class实现一个接口,不过切记最后那个分号;实现继承和接口实现时出现同名方法的问题;实现多重继承的功能)
2.4 反射
这种动态的获取信息及动态调用方法的机制在Java中称为“反射”(reflection)。
Reflection 是Java被视为动态(或准动态)语言的一个关键性质。
在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中:
Class类:代表一个类;
Field 类:代表类的成员变量(成员变量也称为类的属性);
Method类:代表类的方法;
Constructor 类:代表类的构造方法;
Array类:提供了动态创建数组,以及访问数组的元素的静态方法;
在java.lang.Object
类中定义了getClass()方法,因此对于任意一个Java对象,都可以通过此方法获得对象的类型。Class类是Reflection API 中的核心类,它有以下方法:
getName():获得类的完整名字;
getFields():获得类的public类型的属性(包括继承的类的public属性);
getDeclaredFields():获得类的所有属性;
getMethods():获得类的public类型的方法; (包括继承的类的public方法);
getDeclaredMethods():获得类的所有方法;
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes
参数指定方法的参数类型;
getConstructors():获得类的public类型的构造方法;
getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型;
newInstance():通过类的不带参数的构造方法创建这个类的一个对象;
通过默认构造方法创建一个新对象:
Object objectCopy=classType.getConstructor(new
Class[]{}).newInstance(new Object[]{});
以上代码先调用Class类的getConstructor()方法获得一个Constructor 对象,它代表默认的构造方法,然后调用Constructor对象的newInstance()方法构造一个实例。
获得对象的所有属性:
Field fields[]=classType.getDeclaredFields();
Class 类的getDeclaredFields()方法返回类的所有属性,包括public、protected、默认和private访问级别的属性。
Java允许我们从多种途径为一个class生成对应的Class object
(一)运用getClass()方法
(二)运用Class.getSuperclass()方法
(三)运用静态方法Class.forName(),这个最常用
(四)运用.class语法
(五)运用原始包装类的TYPE方法
2.5 浅复制和深复制
浅负责也叫浅克隆,深复制也叫深克隆。
克隆一般通过实现Cloneable中的clone方法来实现浅克隆和深克隆,但是深克隆有一个问题,如果引用对象有很多,或者说引用套引用很多重,那么太麻烦了。
业界常用的方法是使用串行化然后反串行化的方法来实现深克隆。由于串行化后,对象写到流中,所有引用的对象都包含进来了,所以反串行化后,等于生成了一个完全克隆的对象。绝!
这个方法的要求是对象(包括被引用对象)必须事先了Serializable接口,否则就要用transient关键字将其排除在复制过程中。
3、UML相关知识
3.1软件工程的生命周期
3.1.1 软件工程的生命周期
需求捕获、系统分析与设计、系统实现、测试、维护
3.2.UML(Unified Modeling
Language)概述
3.3 UML组成
视图(View)是一个或多个图组成的对系统某个角度的抽象
图(Diagram)是模型元素集的图形表示
模型元素(Model Element)代表面向对象中的类、对象、接口、消息和关系的概念
通用机制(General Mechanism)用于表示其他信息,如注释、模型元素的语义等
3.4视图
视图由图组成,UML提供9种不同的视图,其中视图的包括
用例视图(强调系统功能)也称用户模型视图
用例图
逻辑视图(展现系统的静态或结构组成特征)也称为结构模型视图或静态视图
类图、对象图
并发视图(体现系统的动态或行为特征)也称为行为模型视图或动态视图
时序图、协作图、状态图、活动图
组件视图(体现系统实现的结构和行为特征)也成为实现模型视图
组件图
配置视图(体现系统实现的环境的结构和行为特征)也称环境模型视图或物理视图
配置图
3.5静态建模机制和动态建模机制
UML内容归纳为2大类:静态建模机制和动态建模机制
静态建模机制包括用例图、类图、对象图、组件图、配置图、包等
动态建模机制包括时序图、协作图、状态图、活动图、消息等
3.6 UML用来描述模型内容的有3种:事物、关系、图(静态视图和动态视图)
主要说说事物和关系
事物包括如下
结构事物:用例(椭圆)、类(矩形)、接口(圆心)、协作(虚椭圆)、活动类(粗线矩形)、组件、节点(资源:电脑)
行为事物:交互(通常画成带箭头的信息)、状态机(对象的一个或多个状态的集合)
组织事物:包
辅助事物:注释
关系:关联、依赖、泛化、实现
3.7 类与类关系
类(矩形:名称、属性、操作、职责)
其中表示+、-、#是public、private、protected
关系
依赖(有方向的虚线):使用依赖、抽象依赖、授权依赖、绑定依赖
泛化(父类和子类的关系):描述了类之间的“is a kind of ”的关系,用子类指向父类的空心三角形箭头(实线)表示该关系。泛
化使得多态成为可能。
关联:描述一组具有相同结构特征、行为特征、关系和语义的链接。
关联的修饰有:名称(描述关系的性质)、角色(职责)、多重性(0..n)、聚合(整体和部分,即has a的关系)、
组合(是更强的关系,是另外一种聚合,整体有管理部分特有的职责并且有一致的生命周期)等。
实现:实现规格说明和其实现间的关系。它表示不继承结构而只继承行为。大多数情况下,实现关系用来规定接口和实现接口的类或
组件之间的关系。(带空心的箭头表示,线是虚线)
3.8类图
描述类、接口、协作以及它们之间关系的图。
类图包括7个元素:类、接口、协作、依赖关系、泛化关系、实现关系以及关联关系
类图的作用:对系统的词汇建模、对简单的协作建模、对逻辑数据库模式建模
3.9对象图
表示某一刻一组对象以及它们之间关系的图
3.10包图(略)
3.11用例视图
用例(Use Case):对一个系统或一个应用的一种单一的使用方式所作的描述。
参与者(Actor):系统外部的一个实体(可以说任何事物或人)
参与者之间的关系(泛化关系、包含关系、扩展关系)
3.12时序图
描述对象之间传递消息的时间顺序,他用来表示用例中的行为顺序,是强调消息时间的交互图
包括4个元素:对象、生命线、激活、消息
3.13协作图
强调参与交互的各对象结构的信息。协作图是一种类图。
协作图中包含3个元素:对象、链、消息。
如图:
3.14状态图
通过类对象的生命周期建立模型来描述对象随时间变化的动态行为。
它包括:状态和转换
注意:初始状态:实圆心、终止状态:半实圆心。
状态机、状态、转化
其中状态包括:名字、入口/出口动作、内部转换、延迟事件、子状态。
转换包括:源状态、目标状态、出发事件、监护条件、动作
3.15活动图
描述一个过程或操作的步骤。描述状态外,更突出活动
动作状态、活动状态、动作流、分支、分叉和汇合、泳道、对象流。
3.16组件图
描述软件组件及组件之间的关系。
包括:组件、接口、关系(依赖、泛化、关联、实现)。
3.17配置图
显示软件系统运行的物理硬件。
包括节点(单个物理对象)、组件和关系(依赖和关联)
4、熟悉LINUX
(略)
5、SSH框架的配置及相关知识
5.1 MVC
5.1.1 简介
MVC模式的目的就是实现Web系统的职能分工。 Model层实现系统中的业务逻辑,通常可以用JavaBean或EJB来实现。
View层用于与用户的交互,通常用JSP来实现。
Controller层是Model与View之间沟通的桥梁,它可以分派用户的请求并选择恰当的视图以用于显示,同时它也可以解释用户的输入并将它们映射为模型层可执行的操作。
5.1.2 MVC的优缺点
优点:低耦合、高可复用性和可适用性、较低的生命周期成本、快速部署、可维护性、有利于软件工程化管理。
缺点:原理较复杂花费时间思考、程序分成三个部分需要管理更多的文件。
5.1.3 MVC的现有框架
Struts: Apache的,最流行的MVC组件
Struts2 :Apache用Struts 和 WebWork的组合出来的新产品,目前上升势头强劲
WebWork: 这个可是老牌的MVC组件,后来组合成了Struts2, 不过自身仍在发展
Spring MVC:SpringFramework自己整合自己Spring的优势推出的MVC组件,用户也不少。
JSF: 这个是一个规范,Sun的和 Apache的都有各自的实现。用户量很大,被众多IDE支持。
5.1.4 Model1和Model2
Model1是将业务、控制、视图都放在jsp页面、Model2是由业务(javaBean)、控制(ActionServlet)、视图(Jsp)组成。
5.2 Struts
5.2.1简介
Struts 2是Struts的下一代产品,是在
struts 和WebWork的技术基础上进行了合并的全新的Struts 2框架。其全新的Struts 2的体系结构与Struts 1的体系结构的差别巨大。Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与Servlet API完全脱离开,所以Struts 2可以理解为WebWork的更新产品。虽然从Struts 1到Struts 2有着太大的变化,但是相对于WebWork,Struts 2只有很小的变化。
目的:是为了帮助我们减少在运用MVC设计模型来开发Web应用的时间。
Struts2的体系与Struts1体系的差别非常大,因为Struts2使用了WebWork的设计核心,而不是Struts1的设计核心。Struts2中大量使用拦截器来处理用户的请求,从而允许用户的业务逻辑控制器与Servlet API分离。
Struts2框架的大概处理流程如下:
1、加载类(FilterDispatcher)
2、读取配置(struts配置文件中的Action)
3、派发请求(客户端发送请求)
4、调用Action(FilterDispatcher从struts配置文件中读取与之相对应的Action )
5、启用拦截器(WebWork拦截器链自动对请求应用通用功能,如验证)
6、处理业务(回调Action的execute()方法)
7、返回响应(通过execute方法将信息返回到FilterDispatcher)
8、查找响应(FilterDispatcher根据配置查找响应的是什么信息如:SUCCESS、ERROER,将跳转到哪个jsp页面)
9、响应用户(jsp--->客户浏览器端显示)
10、struts2标签库(相比struts1的标签库,struts2是大大加强了,对数据的操作功能很强大)。
5.2.2
Struts2和Struts1的对比
在Action的实现方面:Struts1要求必须统一扩展自Action类,而Struts2中可以是一个普通的POJO。
线程模型方面:Struts1的Action工作在单例模式,一个Action的实例处理所有的请求。Struts2的Action是一个请求对应一个实例。没有线程安全方面的问题。
Servlet依赖方面:Struts1的Action依赖于Servlet API,比如Action的execute方法的参数就包括request和response对象。这使程序难于测试。Struts2中的Action不再依赖于Servlet API,有利于测试,并且实现TDD。
封装请求参数:Struts1中强制使用ActionForm对象封装请求的参数。Struts2可以选择使用POJO类来封装请求的参数,或者直接使用Action的属性。
表达式语言方面:Struts1中整合了EL,但是EL对集合和索引的支持不强,Struts2整合了OGNL(Object Graph NavigationLanguage)。
绑定值到视图技术:Struts1使用标准的JSP,Struts2使用“ValueStack”技术。
参见文档:http://www.blogjava.net/freeman1984/archive/2011/02/16/344447.html
类型转换:Struts1中的ActionForm基本使用String类型的属性。Struts2中使用OGNL进行转换,可以更方便的使用。
数据校验:Struts1中支持覆盖validate方法或者使用Validator框架。Struts2支持重写validate方法或者使用XWork的验证框架。
Action执行控制的对比:Struts1支持每一个模块对应一个请求处理,但是模块中的所有Action必须共享相同的生命周期。Struts2支持通过拦截器堆栈为每一个Action创建不同的生命周期。
5.2.3
在web.xml中的配置
<filter>
<filter-name>action2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>action2</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>
5.3 Hibernate
5.3.1 简介
Hibernate是一个对象关系映射框架,它对JDBC进行了非常轻量的对象封装,使得java程序员可以随心所欲地使用对象编程思维来操纵数据库。
5.3.2
Hibernate主键介绍
Assigned(依赖用户)
Assigned方式由用户生成主键值,并且要在save()之前指定否则会抛出异常
特点:主键的生成值完全由用户决定,与底层数据库无关。用户需要维护主键值,在调用session.save()之前要指定主键值。
Hilo(依赖数据表)
Hilo使用高低位算法生成主键,高低位算法使用一个高位值和一个低位值,然后把算法得到的两个值拼接起来作为数据库中的唯一主键。Hilo方式需要额外的数据库表和字段提供高位值来源。默认情况下使用的表是 hibernate_unique_key,默认字段叫作next_hi。next_hi必须有一条记录否则会出现错误。
特点:需要额外的数据库表的支持,能保证同一个数据库中主键的唯一性 ,但不能保证多个数据库之间主键的唯一性。Hilo主键生成方式由Hibernate 维护,所以Hilo方式与底层数据库无关,但不应该手动修改hi/lo算法使用的表的值,否则会引起主键重复的异常。
Increment(依赖数据Sequence)
Increment方式对主键值采取自动增长的方式生成新的主键值,但要求底层数据库的支持Sequence。如Oracle,DB2等。需要在映射文件xxx.hbm.xml中加入Increment标志符的设置。
这个是由Hibernate在内存中生成主键,每次增量为1,不依赖于底层的数据库,因此所有的数据库都可以使用,但问题也随之而来,由于是Hibernate生成的,所以只能有一个Hibernate应用进程访问数据库,否则就会产生主键冲突,不能在集群情况下使用插入数据的时候hibernate会给主键添加一个自增的主键,但是一个hibernate实例就维护一个计数器,所以在多个实例运行的时候不能使用这个方法。
特点:由Hibernate本身维护,适用于所有的数据库,不适合多进程并发更新数据库,适合单一进程访问数据库。不能用于群集环境。
Identity(自增)
Identity当时根据底层数据库,来支持自动增长,不同的数据库用不同的主键增长方式。
特点:与底层数据库有关,要求数据库支持Identity,如MySQl中是auto_increment, SQL Server 中是Identity,支持的数据库有MySql、SQL Server、DB2、Sybase和HypersonicSQL。 Identity无需Hibernate和用户的干涉,使用较为方便,但不便于在不同的数据库之间移植程序。
Sequence(依赖数据Sequence)
Sequence需要底层数据库支持Sequence方式,例如Oracle数据库等
特点:需要底层数据库的支持序列,支持序列的数据库有DB2、PostgreSql、Oracle、SAPDb等在不同数据库之间移植程序,特别从支持序列的数据库移植到不支持序列的数据库需要修改配置文件
Native(依赖具体的数据库)
Native主键生成方式会根据不同的底层数据库自动选择Identity、Sequence、Hilo主键生成方式
特点:根据不同的底层数据库采用不同的主键生成方式。由于Hibernate会根据底层数据库采用不同的映射方式,因此便于程序移植,项目中如果用到多个数据库时,可以使用这种方式。
UUID(依赖算法)
UUID使用128位UUID算法生成主键,能够保证网络环境下的主键唯一性,也就能够保证在不同数据库及不同服务器下主键的唯一性。
特点;能够保证数据库中的主键唯一性,生成的主键占用比较多的存贮空间
Foreign
GUID(外键)
Foreign用于一对一关系中,使用外部表的字段作为主键。
5.3.3 缓存管理
Hibernate 中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。 Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存。
一级缓存的管理
当应用程序调用Session的save()、update()、saveOrUpdate()、get()或load(),以及调用查询接口的
list()、iterate()或filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。 Session为应用程序提供了两个管理缓存的方法: evict(Object obj):从缓存中清除参数指定的持久化对象。 clear():清空缓存中所有持久化对象。
二级缓存的管理
Hibernate的二级缓存策略的一般过程如下:
1) 条件查询的时候,总是发出一条select
* from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。
2) 把获得的所有数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache。
什么样的数据适合存放到第二级缓存中?
1 很少被修改的数据 2 不是很重要的数据,允许出现偶尔并发的数据 3 不会被并发访问的数据 4 参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。
不适合存放到第二级缓存的数据?
1 经常被修改的数据 2 财务数据,绝对不允许出现并发 3 与其他应用共享的数据。
常用的缓存插件 Hibernater 的二级缓存是一个插件
下面是几种常用的缓存插件:
EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。
OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。
SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存。
JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。
上述4种缓存插件的对比情况列于表9-3中。
表9-3 4种缓存插件的对比情况
缓 存 插 件 |
支 持 只 读 |
支持非严格读写 |
支 持 读 写 |
支 持 事 务 |
EhCache |
是 |
是 |
是 |
|
OSCache |
是 |
是 |
是 |
|
SwarmCache |
是 |
是 |
||
JBossCache |
是 |
是 |
它们的提供器列于表9-4中。
表9-4 缓存策略的提供器
缓 存 插 件 |
提供器(Cache Providers) |
Hashtable(只能测试时使用) |
org.hibernate.cache.HashtableCacheProvider |
EhCache |
org.hibernate.cache.EhCacheProvider |
OSCache |
org.hibernate.cache.OSCacheProvider |
SwarmCache |
org.hibernate.cache.SwarmCacheProvider |
JBossCache |
org.hibernate.cache.TreeCacheProvider |
在默认情况下,Hibernate使用EhCache进行JVM级别的缓存。用户可以通过设置Hibernate配置文件中的hibernate.cache.provider_class的属性,指定其他的缓存策略,该缓存策略必须实现org.hibernate.cache.CacheProvider接口。
配置二级缓存的主要步骤:
1) 选择需要使用二级缓存的持久化类,设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤。
2) 选择合适的缓存插件,然后编辑该插件的配置文件。
缓存作用范围
事务范围(一级缓存——Session级别)
事务存在与内存中,事务范围内的对象都有一个对象ID(OID),它对应数据库表中的代理主键(没有实际意义仅用来标示记录号,相对于有实际意义的自然主键)
进程范围(二级缓存——SessionFactory级别)
缓存内的数据被进程中的事务共享操作数据时需运用所机制
集群范围(二级缓存——SessionFactory级别)
一级缓存Session
减少访问数据库的频率
Transaction tx = session . beginTransaction();
//第一次
Bird b1 = (Bird) session . get(Bird . class ,
new long(1));
//第二次
Bird b2 = (Bird) session . get(Bird . class ,
new long(1));
tx . commit();
session第一次加载对象时先从缓存中查找,如没找到在从数据库中查找并加载到缓存中;第二次加载时直接从缓存中加载而不需再次访问数据库。
保证数据库中的相关记录与缓存中相应对象保持同步
Session缓存中的数据在提交数据库之前进行了几次修改,当数据提交时只执行一次最后一次修改的update操作。
配置二级缓存步骤
选择需要使用二级缓存的持久化类,设置它的二级缓存并发访问策略(只读,只写,可读可写)。
选择合适的缓存插件,每一种缓存插件都有自带的配置文件,需要手动配置。
例如配置ehcache缓存
1、在Hibernate配置文件中,指定ehcache适配器
Hibernate . cfg . xml添加元素
//二级缓存存在的类
<property
name=”hibernate . cache . provider_class”>org . hibernate . cache .
EhCacheProvide</Property>
/查询时是否使用二级缓存
<property
name=”hibernate . cache . use_query_cache”>true</Property>
2、在映射文件中配置<cache>元素
XXX . hbm . xml
//指定并发访问策略
<cache usage=”read-write”/>
3、编写ehcache . xml,并将此文件放在src文件夹下
eacache . xml
<?xml
version=”1.0” encoding=”UTF-8”?>
<ehcache>
<diskStore path=”java . io .
tempdir”/>
<!--
maxElementsInMemory:允许缓存可存储的总记录数
eternal:当前缓存是否永远不过期
overflowToDisk:当前缓存中数据达到最大值时,是否把缓存数据写入硬盘
timeToIdleSeconds:当前缓存最大闲置时间,超过该时间则销毁缓存
timeToLiveSeconds:设置超时时间,当缓存创建后达到该时间就自动销毁
-->
<defaultCache
maxElementsInMemory=”5”
eternal=”false”
overflowToDisk=”true”
timeToIdleSeconds=”15”
timeToLiveSeconds=”120”
/>
</ehcache>
5.3.4 Hibernate映射
Hibernate映射分为:内置映射和自定义映射。
使用<property>定义属性
使用<many-to-one>配置多对一映射
使用<one-to-one>配置一对一映射(主键关联和唯一外键关联)
5.3.5 Hibernate核心编程
Hibernate的核心接口一共有5个,分别为:Session、SessionFactory、Transaction、Query和Configuration。这5个核心接口在任何开发中都会用到。通过这些接口,不仅可以对持久化对象进行存取,还能够进行事务控制。
Session接口
Session接口负责执行被持久化对象的CRUD操作(CRUD的任务是完成与数据库的交流,包含了很多常见的SQL语句。)。但需要注意的是Session对象是非线程安全的。同时,Hibernate的session不同于JSP应用中的HttpSession。这里当使用session这个术语时,其实指的是Hibernate中的session,而以后会将HttpSession对象称为用户session。
SessionFactory接口
SessionFactory接口负责初始化Hibernate。它充当数据存储源的代理,并负责创建Session对象。这里用到了工厂模式。需要注意的是SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就够,当需要操作多个数据库时,可以为每个数据库指定一个SessionFactory。
Configuration接口
Configuration接口负责配置并启动Hibernate,创建SessionFactory对象。在Hibernate的启动的过程中,Configuration类的实例首先定位映射文档位置、读取配置,然后创建SessionFactory对象。
Transaction接口
Transaction接口负责事务相关的操作。它是可选的,开发人员也可以设计编写自己的底层事务处理代码。
Query和Criteria接口
Query和Criteria接口负责执行各种数据库查询。它可以使用HQL语言或SQL语句两种表达方式。
注意:Configuration实例时一个启动期间的对象,一旦SessionFactory创建完成它就被丢弃。
5.3.6 Session的三个状态
自由态(不曾进行持久化,未与任何Session相关联)
持久态(仅与一个Session相关联)
游离态(已经进行了持久化,但未与当前任何Session相关联)
Transient(自由态)状态最大的特征是:
* 在数据库中没有与之匹配的数据
* 没有纳入session的管理
Persistent(持久态)状态最大的特征是:
* 在数据库中有与之匹配的数据
* 纳入了session的管理
* 在清理缓存(脏数据检查)的时候,会和数据库同步
Detached(游离态)状态最大的特征是:
* 在数据库中有与之匹配的数据
* 没有纳入session的管理
状态转换:
5.3.7 Session操作
save()方法保存对象
引发INSERT
load()方法装载对象
如果没有匹配的数据库记录,load()方法可能会抛出无法恢复的异常。
get()方法装载对象
会立即访问数据库,如果没有对象的记录,则返回null。
flush()方法强制提交刷新。
update()方法提交游离状态的对象。
delete()方法移除持久化对象。
refresh ()方法强制装载对象。
5.3.8 Session查询
不带参数查询
Query query = session.createQuery(“from User”);
带参数查询
Query query = session.createQuery(“from
User where username=:username”);
Query.setLockMode(“user”,LockMode.upgrade); 加锁
Query.setString(“username”,”admin”);
也可使用setParameterList()设置该参数
List names = new ArrayList();
names.add(“admin”);
names.add(“test”);
Query query = session.createQuery(“from
User where username in(:unameList)”);
Query.setString(“unameList”,names);
还可以带问号
Query query = session.createQuery(“from
User where username=?”);
Query.setString(0,”admin”);
setProperties()方法:
在Hibernate中可以使用setProperties()方法,将命名参数与一个对象的属性值绑定在一起,如下程序代码:
Customer customer=new Customer();
customer.setName(“pansl”);
customer.setAge(80);
Query query=session.createQuery(“from
Customer c where c.name=:name and c.age=:age ”);
query.setProperties(customer);
取得List结果集
List list =query.list();
取得迭代结果集
Iterator iter=query.iterate();
或
Iterator iter=query.list().iterator();
list()和iterator()区别:查询机制不同
list方法仅查询一次,查询全部数据
iterator可能会查询多次,首先从数据库中检索出所有符合条件的记录的id字段(仅有id),然后扫描缓存,如果缓存中包含全部数据则无需再查询数据库,直接引用。如果缓存中不包含任何数据需要再次查询数据库。
大多数情况下使用list进行查询,当对象包含大量属性,或者要加载的大部分数据已经在缓存中,可使用iterator;
取得一个对象
Query query = session.createQuery(“from
User where username=:username”);
Query.setString(“username”,”admin”);
User user = (User)query.uniqueResult();
标量查询(查某个具体的字段或是统计函数等)
略
分页查询
Query query = session.createQuery(“from
User”);
query.setFirstResult(10);//设置起始范围,从第11数据开始取
query.setMaxResults(20);//设置结束范围,最多能取20条数据(如果有的话)
List list =query.list();
创建SQL查询
Query query = session.createSQLQuery(“select {user.*} from User {user} where username=?”);
其他一样(别名需要大括号括起来)
5.3.9面向对象的Criteria查询
创建Criteria实例
Criteria criteria =
session.createCriteria(User.class);
criteria.setMaxResults(50);
List users= criteria.list();
添加查询条件
criteria.add(Restrictions.like(“username”,”%admin%”));
criteria.add(Restrictions.between(“ID”,1,10));
添加排序
criteria.add(Order.asc(“username”));
示例查询
User
user=new User();
user.setUsernem(“admin”);
criteria.add(Example.create(user));
5.3.10在Hibernate中实现增加(修改,删除)操作的步骤
1、读取并解析配置文件
Configuration config=new
Configuration() . configure();
2、读取并解析映射信息,并创建SessionFactory
SessionFactory sessionfactory=config .
buildSessionFactory();
3、打开session
this . session=sessionfactory .
openSession();
4、打开一个事务(增删改必选,查询可选)
tran=this.session . beginTransaction();
5、持久化操作
this . session . save(login);(增加操作)
this . session . update(login);(修改操作)
this . session . dalete(login)(删除操作)
6、提交事务
tran . commit();
7、关闭session
This . session.close();
5.3.11 事务
基于JTA的事物管理
JTA提供了跨Session的事物管理能力
JTA事物管理则由JTA容器实现,JTA容器对当前加入事务的众多Connection 进行调度
JTA的事务周期可横跨多个JDBC Connection生命周期
JTA事务是由JTA
Container维护,而参与事务的Connection无需对事务管理进行干涉
5.3.12 Hibernate加锁模式
LockMode.NONE : 无锁机制
LockMode.WRITE : Hibernate在Insert和Update记录时会自动获取
LockMode.READ :Hibernate在读取记录时会自动获取
LockMode.UPGRADE : 利用数据库的for update子句加锁
LockMode.UPGRADE_NOWAIT : Oracle的特定实现,利用Oracle的for
update nowait子句实现加锁
Criteria.setLockMode
Query.setLockMode
Session.lock
5.3.13 Session管理
Session不是线程安全的,所以让多个线程共享一个Session发生数据冲突,所以Session需要管理
ThreadLocal模式: ThreadLocal并不是一个线程的本地实现,即他不是一个线程(Thread),而是一个线程局部变量(Thread local variable)。它的作用就是为每一个使用这个变量的线程都提供一个变量值的副本,并且每一个线程都可以独立的改变自己的副本,而不会和其他线程的副本产生冲突。
openSession()和getCurrentSession()
创建session的方法除了有openSession()之外,getCurrentSession()也可以创建Session;
getCurrentSession()创建的Session会绑定到当前线程,而openSession()不会
getCurrentSession()创建的Session会在事务提交或回滚后自动关闭,而openSession()则需要手动关闭(调用Session的close()方法)
使用getCurrentSession()的方法
当使用本地事务时(JDBC事务)
需要在hibernate.cfg.xml文件的<session-factory>节点中添加
<property name=”hibernate.current_session_context_class”>thread</property>
当使用全局事务时(JTA事务)
需要在hibernate.cfg.xml文件的<session-factory>节点中添加
<property name=”hibernate.current_session_context_class”>jta</property>
5.3.14 关联关系
持久化对象不通过外键建立对象之间的关联关系,而是通过属性
持久化对象之间的关联关系的种类:一对一,一对多(多对一),多对多
持久化类之间关联关系的方向:单向关联,双向关联
一对多(在年级的xml中)
<set name=”students”>
<key
column=”gid”/> //年级 <!—此处gid为Student表中的gid外键字段-->
<one-to-many class=”com . pb .
hibernate . pb . Students”/>
</set>
多对一(在学生中)
<many-to-one name=”grade” class=”hib3.po.Grade” cascade=”save-update”>
<column name=”gid”/>
</many-to-one>
配置反转属性:inverse,将维护关联关系的任务反转,交给对方完成
inverse只能用于集合属性中,即关联关系中“一”的一方;反转关联关系给“多”的一方。
即:
<set name=”students” inverse=“true”>
<key
column=”gid”/>
<one-to-many class=”com . pb .
hibernate . pb . Students”/>
</set>
一对一关联
在People类中添加License类的属性,在License类中添加People的属性
People . hbm . xml中通过<one-to-one>标签实现映射
License . hbm . xml中通过<many-to-one>标签实现映射
People类 License类
pid :int lid :int
pname :String 1:1 ldesc :String
pgender :String people :People
license :License
People类People . hbm . xml(主动方)
<one-to-one
name=”license” class=”hib3.po.License” cascade=”all” lazy=”false”/>
注:在<one-to-one>标签中一般吧cascade属性值设为all,lazy属性设为false
License类License . hbm . xml(被动方)
<many-to-one
name=”people” class=”hib3.po.People” unique=”true” lazy=”false”>
<!--外键设置在被动方表中-->
<column name=”pid”/>
</many-to-one>
注:unique意为唯一,hibernate通过unique属性将多对一关系限制为一对一;
多对多关联关系
1、不创建中间表的持久化类,只创建两端表的持久化类,在映射文件中使用<many-to-many>标签设置映射
Student.hbm.xml
<set
name=”courses”
table=”sc” cascade=”save-update”>
<!-- Student与中间表发生关联的字段是“sid”-->
<key column=”sid”>
<!—中间表与Course类发生关联的字段是“cid”-->
<many-to-many class=”hib3.po.Course”
column=”cid”>
</set>
Course.hbm.xml
<set
name=”students”
table=”sc” cascade=”save-update” inverse=“true”>
<!-- Course与中间表发生关联的字段是“cid”-->
<key column=”cid”>
<!—中间表与Student类发生关联的字段是“sid”-->
<many-to-many class=”hib3.po.Student” column=”sid”>
</set>
注:在其中一方添加inverse属性,只有一方维护关联关系
2、创建中间表,两端表的持久化类,针对中间表的持久化类分别和两端表的持久化类创建一对多的关联关系
5.3.15 查询方式
分别是HQL查询 ,对象化查询Criteria方法,动态查询DetachedCriteria,例子查询,sql查询,命名查询。
HQL查询
通过Query接口使用HQL语言进行查询,HQL是hibernate自己的一套查询语言,于SQL语法不同,具有跨数据库的优点。
对象化查询方式
通过Criteria等接口和类进行查询。
动态分离查询DetachedCriteria
面向对象操作,分离业务与底层,不需要字段属性摄入到Dao实现层
SQL查询方式
使用原声SQL语言进行查询。
命名查询方式
适用情况:万能方法,有点像ibatis轻量级框架的操作,方便维护。 缺点:不面向对象。基于hql和sql,有一定缺陷。
属性查询有直接查询和通过构造方法查询。
fetch关键字
用来表示家在对象时查询语句的形式和加载时机。有3个取值:
select:加载关联对象时通过select语句实现。默认
subselect:通过带子查询语句实现
join:执行立即检索,采用迫切左外连接检索所有关联对象,lazy属性将被忽略
注:select和subselect适用于立即检索和延迟检索,jion仅适用于立即检索
select和subselect决定加载关联对象时查询语句的形式,join决定加载时机
5.3.16 命名查询
HQL语句混杂在代码中会破坏代码可读性
Hibernate允许在影射配置文件中定义字符串形式的查询语句,即是命名查询。
命名查询——映射配置文件
<hibernate-mapping>
<class name=”hib3.po.Login” table=”LOGIN”>
<!--。。。。。省略其他配置-->
</class>
<query name=”loginUser”>
<![CDATA[
from Login login where
login.username=:username and login.password=:password]]>
</query>
</hibernate-mapping>
以<![CDATA[HQL]]>方式保存HQL语句
在程序中使用Session的getNamedQuery(“hql语句名”)方法获取配置文件中的hql语句。
<!--设置JDBC单次批量处理数目(一般10-50)-->
<property name=”hibernate . jdbc . batch_size”>20</property>
(Session缓存为Hibernate一级缓存,是事务范围内的缓存;Session外置缓存是Hibernate二级缓存,是应用范围内的缓存,即所有事物共享二级缓存。二级缓存默认关闭)
<property name=”hibernate .
cache . use_second_level_cache”>false</property>
批量更新处理:使用可滚动结果集(ScrollableResults)
//ScrollableResults对象中存储的是游标,但需要此对象时才会到数据库中加载
ScrollableResults logins = session . createQuery(“from Login”) . scroll(ScrollMode . forward_only);
使用CallableStatement调用存储过程
CallableStatement cst = session . connection() . prepareCall(“{call login_insert(?,?,?)}”)
cst . setString(1,“aaa”);
cst . setString(2,“22222”);
cst . setLong (3,“21”);
cst . executeUpadte();
调用带有返回结果集的存储过程
Session
session = new Configuration() .
configure() . buildSessionFactory() . openSession();
Transaction
tran = session . beginTransaction();
CallableStatement cst = session . connection() . prepareCall(“{call login_getlist(?)}”)
cst . registerOutParameter(1 , oracle .
jdbc . OracleType . cursor);
cst . execute();
ResultSet
rs=(ResultSet)cst . getObject(1);
while(rs.next()){
……
}
使用命名SQL调用存储过程
使用命名SQL的插入方法(更新功能类似)
5.3.17 Hibernate注解
使用注解方式来注释类,属性
可以省略类——表映射文件(hbm . xml文件)
Hibernate注解的优势
简化对象关系映射的开发
通过注解方式可以自动生成数据库表
使用注解进行对象映射步骤
1、为每一个实体Bean使用@Entity注解
2、使用@ID指定实体Bean的标识属性
3、使用@Table指明当前实体Bean对应数据库那张表
4、使用@GeneratedValue指定主键生成类型
5、修改Hibernate配置文件
5.4 Spring
5.4.1 简介
Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。
简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
◆轻量——从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。
◆控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
◆面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
◆容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
◆框架——Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。
5.4.2 特点
◆方便解耦,简化开发
通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
◆AOP编程的支持
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
◆声明式事务的支持
在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
◆方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。
◆方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,相反,Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts,Hibernate、Hessian、Quartz)等的直接支持。
◆降低Java EE API的使用难度
Spring对很多难用的Java
EE API(如JDBC,JavaMail,远程调用等)提供了一个薄薄的封装层,通过Spring的简易封装,这些Java EE API的使用难度大为降低。
5.4.3 模块结构
Spring核心容器(提供框架的基本功能,核心容器的组件是BeanFactory,是工厂模式的体现,使用IOC(控制反转)模式将应用程序的配置和依赖性规范与实际的应用代码分开)。
Spring上下文 是一个配置文件(applicationContext.xml),向框架提供上下文信息。包括的服务如JNDI,调度等。
Spring AOP 面向方面的编程,主要用于支持事务管理。
Spring Dao 提供异常层次结构,可管理异常处理和不同数据库供应商抛出的错误信息。
Spring ORM 集成了多个ORM框架,提供ORM的对象关系工具。
Spring Web 集成了MVC的框架,如Struts等
Spring MVC 自身提供的MVC框架,高度的可配置性。
5.4.4 Ioc(控制反转)
定义:由容器来控制业务和业务之间的依赖关系,而非传统的通过代码来直接操控,此处控制权的转移就是所谓的反转。
实现策略:依赖查找(类似与JDNI的实现)、依赖注入(DI)
其中依赖注入是一种更加合适的方法,让容器去全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法或者带参数的构造子或者接口,使容器可以在初始化时组装对象的依赖关系。
好处是:查询依赖操作和应用代码分离;受控对象不会使用到容器特定的API,这样我们的受控对象可以搬出容器单独使用。
依赖注入有三种:又有接口注入(Interface Injection),设值注入(Setter Injection)和构造子注入(Constructor Injection)三种方式。
5.4.5 Ioc容器装载机制
4种Bean封装机制:
BeanWrapper机制
BeanFactory机制
ApplicationContext机制:由BeanFactory扩展而来。
WebContext机制
配置XML元数据:服务层对象、DAO数据访问层对象、Action表示层对象、Hibernate SessionFactory对象、JMS Queue对象等。
举例:
Object obj=Class.forName("domain.Test").newInstance();
BeanWrapperwrapper=newBeanWrapperImpl(obj);//BeanWrapper实现了Wrapper接口
wrapper.setPropertyValue("testN","sasa");
Testte=(Test)obj;
System.out.println(te.getTestN());
实例化容器
Resource r = new
FileSystemResource(“beans.xml”);
BeanFactory factory = new XmlBeanFactory(r);
或者
ClassPathResource r = new ClassPathResource(“beans.xml”);
BeanFactory factory = new XmlBeanFactory(r);
或者
ApplicationContext ctx = null;
ctx = new ClassPathXmlApplicationContext(new
String[]{“beans1.xml”,”beans2.xml”});
BeanFactory factory = (BeanFactory)ctx;
注意:以上都是相对路径。
BeanFactory提供的方法
Boolean containsBean(String);
Object getBean(String)
Object getBean(String,Class)
Class getType(String name)
Boolean isSingleton(String)
String[] getAliases(String) 通过bean名称的所有别名
配置<bean>的属性
id、class、factory-method(必须静态的方法)、factory-bean(使用是class属性必须为空)、scope(singleton、prototype、request、session、global session)、depends-on (指定依赖的bean))、lazy-init(true、false)、init-method(初始化回调)、destory-method(bean析构回调)、parent(继承bean)
配置<bean>的子元素
<property>(主要是针对属性注入)
简单:name value
集合:
Properties 使用<props><prop key>value</prop></props>
List 使用<list><value>value</value></list>
Map 使用
<map>
<entry>
<key>
<value>value</value>
</key>
<value>value</value>
</entry>
</map>
Set 使用<set><value>value</value></set>
注意:map的key或value值,或set的value值不能是以下元素:bean、ref、idref、list、set、map、props、value、null。
配置<constructor-arg>元素
1、用type指定输入参数的java类型,value指定输入参数的值
2、用ref属性指定其他的bean
3、使用<ref>子元素指向另一个bean
5.4.6 在web.xml中配置spring
上下文
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>
监听
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
字符集的设置
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
5.4.7 Spring AOP
一、简介
AOP也就是面向切面编程,作为面向对象编程的补充。面向切面编程(AOP)和面向对象编程(OOP)互为补充,面向对象编程将程序分解成各个层次的对象,而面向切面编程将程序运行过程分解成各个切面,可以这样理解,面向对象编程从静态角度考虑程序结构,面向切面编程是从动态角度考虑运行过程。AOP专门用来处理具有横切性质的系统服务,如事务管理、安全检查、缓存、对象池管理等。
二、概念
切面(Aspect):业务流程运行的某个特定步骤,也就是应用运行过程中的关注点,关注点可以横切多个对象,所以常常也称为横切关注点。
连接点(Joinpoint):程序运行过程中明确的点,如方法的调用,或者异常的抛出。Spring AOP中,连接点总是方法的调用,指的就是Method。
增强处理(Advice):AOP框架在特定的切入点执行的增强处理。处理有”around”、”before”、”after”等类型。
切入点(Pointcut):可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点。
一组Joinpoint,就是说一个Advice可能在多个地方织入。
引入:将方法或者字段添加到被处理的类中。Spring允许引入新的接口到任何被处理的对象。
目标对象(Target):被AOP框架进行增强处理的对象,也被称为增强的对象。如果AOP框架是通过运行时代理来实现的,那么这个对象是一个被代理的对象。
AOP代理:AOP框架创建的对象,简单地说代理就是对目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是DBLIB代理。前者为实现接口的目标对象的代理,后者为不实现接口的目标对象的代理。
织入(Weaving):将增强处理添加到目标对象中,并创建一个被增强的对象(AOP代理)的过程就是织入。织入有两种方式:编译时增强(AspectJ)和运行时增强(DGLIB)。
三、AOP 种类
1、静态织入:指在编译时期就织入Aspect代码,AspectJ好像是这样做的。
2、动态织入:在运行时期织入,Spring AOP属于动态织入,动态织入又分静动两种,静则指织入过程只在第一次调用时执行;动则指根据代码动态运行的中间状态来决定如何操作,每次调用Target的时候都执行(性能较差)。
四、Spring AOP 代理原理
Spring AOP 是使用代理来完成的,Spring 会使用下面两种方式的其中一种来创建代理:
1、JDK动态代理,特点只能代理接口,性能相对较差,需要设定一组代理接口。
2、CGLIB 代理,可代理接口和类(final method除外),性能较高(生成字节码)。
五、常用增强处理
@Before("execution(*
kuozhan.before.*.*(..))")
@AfterReturning(pointcut="execttion(*
kuozhan.after.*.*(..))",returning="rvt")
@AfterThrowing(pointcut="execution(*
lee.*.*(..))", throwing="ex")
@After("execution(*
lee.*.*(..))")
@Around("execution(* lee.*.*(..))")
5.5 如何集成三个框架
6、设计模式
6.1设计模式的目的
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、可维护性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。
6.2设计模式的原则
6.2.1开闭原则
开闭原则(对扩展开放,对修改关闭)
如简单工厂模式,只要再创建一个子类就可以了,然后在工厂的方法中返回一个新的子类对象就可以了。
6.2.2里氏替换原则
里氏替换原则(任何基类可以出现的地方,子类一定可以出现)
如果调用的是父类的话,那么换成子类也完全可以运行。Java编译程序会检查程序是否符合里氏代换原则。还记得java继承的一个原则吗?子类override方法的访问权限不能小于父类对应方法的访问权限。可以说:里氏代换原则是继承复用的一个基础。
6.2.3合成复用原则
合成复用原则(少用继承,多用合成/聚合)
在面向对象的设计里,有两种基本的办法可以在不同的环境中复用已有的设计和实现,即通过合成/聚合或通过继承。
合成/聚合复用:
优点:支持包装、黑箱复用看不见内部的细节。依赖较少。这种复用可以在运行时间内动态进行,新对象可以动态地引用与成分对象类型相同的对象。
缺点:是通过使用这种复用建造的系统会有较多的对象需要管理
继承复用:
优点:新的实现较为容易,因为超类的大部分功能都可以通过继承关系自动进入子类,修改或扩展继承而来的实现较为容易。
缺点:继承复用破坏包装,因为继承将超类的实现细节暴露给子类。因为超类的内部细节常常是对子类透明的,因此这种复用是透明的复用,又称“白箱”复用。如果超类的实现发生改变,那么子类的实现也不得不发生改变。从超类忌辰而来的实现是静态的,不可能在运行时间内发生改变,因此没有足够的灵活性。
一般来说,对违反里氏代换原则的设计进行重构时,可以采取两种办法:一是加入一个抽象超类;二是将继承关系改写为合成/聚合关系。
“IS-A”是严格的分类学意义上的定义,意思是一个类是另一个类的“一种”。而“HAS-A”则不同,它表示某一个角色具有某一项责任。
6.2.4 依赖倒置原则
依赖倒置原则(抽象不应该依赖于细节,细节应当依赖于抽象)
要针对接口编程,而不是针对实现编程。
传递参数,或者在组合聚合关系中,尽量引用层次高的类。
主要是在构造对象时可以动态的创建各种具体对象,当然如果一些具体类比较稳定,就不必在弄一个抽象类做它的父类,这样有画蛇添足的感觉。
6.2.5 接口隔离原则
接口隔离原则(使用多个专门的接口比使用单一的总接口要好)
一个类对另外一个类的依赖性应当是建立在最小的接口上的。
一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。
“不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。”这个说得很明白了,再通俗点说,不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。
6.2.6迪米特原则
迪米特原则(最少知识原则。不要和陌生人说话)
迪米特法则的各种表述
① 只与你直接的朋友们通信;
② 不要跟“陌生人”说话;
③ 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
狭义的迪米特法则
☆ 如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另外一个类的某一个方法,可以通过第三者转发这个调用。
在系统里造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的商务逻辑无关。
遵循类之间的迪米特法则会是一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。
门面模式和调停者模式实际上就是迪米特法则的应用。
广义的迪米特法则在类的设计上的体现:
优先考虑将一个类设置成不变类。
尽量降低一个类的访问权限。
谨慎使用Serializable。
尽量降低成员的访问权限。
注意:在进行设计的时候,我们尽量从抽象类继承,而不是从具体类继承。如果从继承等级树来看,所有叶子节点应当是具体类,而所有的树枝节点应当是抽象类或者接口。当然这个只是一个一般性的指导原则,使用的时候还要具体情况具体分析。
6.3模式的四个要素
6.3.1 模式名称
模式名称:一个助记名,它用一两个词来描述模式的问题、解决方案和效果。
6.3.2 问题
问题:描述了应该在何时使用模式。
6.3.3 解决方案
解决方案:描述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。
6.3.4 效果
效果:描述了模式应用的效果及使用模式应权衡的问题。
6.4创建模式
6.4.1抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类。客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。
6.4.2生成器模式
生成器模式也称为建造者模式。将一个复杂对象的构建与他的表示相分离,使得同样的构建过程可以创建不同的表示。将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。建造模式使得产品内部表象可以独立的变化,客户不必知道产品内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。
6.4.3工厂方法模式
核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
简单工厂模式
6.4.4原型模式
通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象。原始模型模式允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。缺点是每一个类都必须配备一个克隆方法。
在Java中Prototype模式变成clone()方法的使用,由于Java的纯洁的面向对象特性,使得在Java中使用设计模式变得很自然,两者已经几乎是浑然一体了。这反映在很多模式上,如Iterator遍历模式。
6.4.5单例模式
保证一个类只有一个实例,并提供一个访问它的全局访问点
6.5行为模式
6.5.1 迭代器模式
提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露该对象的内部表示。
6.5.2 观察者模式
定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知自动更新(之前写的天气预报)。
6.5.3 模板方法模式
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,TemplateMethod使得子类可以不改变一个算法的结构即可以重定义该算法得某些特定步骤。
6.5.4 命令模式
将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销的操作。Command模式是非常简单而又优雅的一种设计模式,它的根本目的在于将“行为请求者”与“行为实现者”解耦。
6.5.5 状态模式
允许对象在其内部状态改变时改变他的行为。对象看起来似乎改变了他的类。主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
意图:允许一个对象在其内部状态改变时改变它的行为
适用场景:
1.一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
2.一个操作中含有庞大的多分支结构,并且这些分支决定于对象的状态。
策略模式和状态模式是双胞胎,它们有相同的类图,但是它们的意图不同。策略模式是围绕可以互换的算法来成功创建业务的,然而状态模式是通过改变对象内部的状态来帮助对象控制自己的行为。
6.5.6 策略模式
定义一系列的算法,把他们一个个封装起来,并使他们可以互相替换,本模式使得算法可以独立于使用它们的客户。
优点:
1、 提供了一种替代继承的方法,而且既保持了继承的优点(代码重用)还比继承更灵活(算法独立,可以任意扩展)。
2、 避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展。
3、 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。
缺点:
1、 因为每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量。
见例子:
1. 抽象策略
package com.eekq.strategy;
public interface IStrategy {
/**策略方法*/
public abstract double add();
}
2. 具体策略,这里我以两个具体策略为例
package com.eekq.strategy;
public class ConcreteStrategy1 implements IStrategy {
/**示意性算法*/
public double add() {
// TODO 自动生成方法存根
System.out.println(this.getClass().getName() + "的加法运算");
return 0;
}
}
package com.eekq.strategy;
public class ConcreteStrategy2 implements IStrategy {
public double add() {
// TODO 自动生成方法存根
System.out.println(this.getClass().getName()
+ "的加法运算");
return 0;
}
}
3.环境角色
package com.eekq.strategy;
public class Context {
/**环境角色类*/
private IStrategy strategy;
public Context(IStrategy
strategy) {
this.strategy = strategy;
}
/**策略方法*/
public double add() {
this.strategy.add();
return 0;
}
}
4.客户端调用
package com.eekq.strategy;
public class Main {
/**
*@paramargs
*/
public static void main(String[]
args) {
// TODO 自动生成方法存根
Context context = new
Context(new ConcreteStrategy1());
context.add();//执行算法1
context = new Context(new
ConcreteStrategy2());
context.add();//执行算法2
}
}
5.执行结果:
com.eekq.strategy.ConcreteStrategy1的加法运算
com.eekq.strategy.ConcreteStrategy2的加法运算
6.总结
优点:动态改变算法或行为
缺点:客户端必须知道所有的策略类,并自行决定使用哪一个策略类,必须对每一个算法了解
6.5.7 职责链模式
使多个对象都有机会处理请求,从而避免请求的送发者和接收者之间的耦合关系
。一个链可以是一条线,一个树,也可以是一个环。如下图所示,责任链是一个树结构的一部分。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。
// "Handler"
abstract class Handler
{
// Fields
protected Handler successor;
// Methods
public void SetSuccessor( Handler successor )
{
this.successor = successor;
}
abstract public void HandleRequest( int request );
}
// "ConcreteHandler1"
class ConcreteHandler1 : Handler
{
// Methods
override public void HandleRequest( int request )
{
if( request >= 0 && request < 10 )
Console.WriteLine("{0} handled request {1}",
this, request );
else
if( successor != null )
successor.HandleRequest( request );
}
}
6.5.8 中介者模式
中介者又叫调停者,用一个中介对象封装一些列的对象交互。
6.5.9 访问者模式
表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这个元素的新操作。访问者模式用于对不同节点进行操作的情况(如List的中的多个元素并不相同),如果将操作放在节点中,会增加节点的复杂性,并不易维护,如果将操作放在调用函数中,则会出现多个判断语句,对不同的节点做不同的操作,增加了节点和调用函数之间的耦合。为了解决这样的问题,在调用函数(对节点进行操作的函数)和节点之间增加了vistor的类,在vistor中实现对各个节点的操作。
6.5.10 解释器模式
给定一个语言,定义他的文法的一个表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
6.5.11 备忘录模式
在不破坏对象的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
6.6结构模式
6.6.1组合模式
将对象组合成树形结构以表示部分整体的关系,Composite使得用户对单个对象和组合对象的使用具有一致性。
6.6.2 外观模式
为子系统中的一组接口提供一致的界面,fa?ade提供了一高层接口,这个接口使得子系统更容易使用。
6.6.3 代理模式
为其他对象提供一种代理以控制对这个对象的访问
6.6.4 适配器模式
将一类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作那些类可以一起工作。
6.6.5 装饰模式
动态地给一个对象增加一些额外的职责,就增加的功能来说,Decorator模式相比生成子类更加灵活。
6.6.6 桥模式
将抽象部分与它的实现部分相分离,使他们可以独立的变化。
6.6.7 享元模式
用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于当大量物件只是重复因而导致无法令人接受的使用大量内存。通常物件中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。
7、J2EE核心组件
7.1 JDBC(JAVA数据库连接)
JDBC(Java Database
Connectivity): JDBC API为访问不同的数据库提供了一种统一的途径,象ODBC一样,JDBC对开发者屏蔽了一些细节问题,另外,JDCB对数据库的访问也具有平台无关性。
7.2 JNDI(JAVA命名和目录服务接口)
1、JNDI(Java Name and Directory Interface): JNDI API被用于执行名字和目录服务。它提供了一致的模型来存取和操作企业级的资源如DNS和LDAP,本地文件系统,或应用服务器中的对象。
2、JNDI(Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。
3、JNDI可访问的现有的目录及服务有:
DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol 轻型目录访问协议)、 CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。
4、优点:
包含了大量的命名和目录服务,使用通用接口来访问不同种类的服务;
可以同时连接到多个命名或目录服务上;
建立起逻辑关联,允许把名称同Java对象或资源关联起来,而不必知道对象或资源的物理ID。
5、JNDI与JDBC
JNDI提供了一种统一的方式,可以用在网络上查找和访问服务。通过指定一个资源名称,该名称对应于数据库或命名服务中的一个记录,同时返回数据库连接建立所必须的信息。
JNDI主要有两部分组成:应用程序编程接口和服务供应商接口。应用程序编程接口提供了Java应用程序访问各种命名和目录服务的功能,服务供应商接口提供了任意一种服务的供应商使用的功能。
代码示例:
try{
Context cntxt
= new InitialContext();
DataSource ds
= (DataSource) cntxt.lookup("jdbc/dpt");
}
catch(NamingException
ne){
...
}
6、JNDI与JMS
消息通信是软件组件或应用程序用来通信的一种方法。JMS就是一种允许应用程序创建、发送、接收、和读取消息的JAVA技术。
代码示例:
try{
Properties env =
new Properties();
InitialContext
inictxt = new InitialContext(env);
TopicConnectionFactory
connFactory = (TopicConnectionFactory)
inictxt.lookup("TTopicConnectionFactory");
...
}
catch(NamingException
ne){
...
}
访问特定目录:举个例子,人是个对象,他有好几个属性,诸如这个人的姓名、电话号码、电子邮件地址、邮政编码等属性。
通过getAttributes()方法
Attribute attr =
directory.getAttributes(personName).get("email");
String email =
(String)attr.get();
通过使用JNDI让客户使用对象的名称或属性来查找对象:
foxes =
directory.search("o=Wiz,c=US", "sn=Fox", controls);
通过使用JNDI来查找诸如打印机、数据库这样的对象,查找打印机的例子:
Printer printer = (Printer)namespace.lookup(printerName); printer.print(document);
浏览命名空间:
NamingEnumeration list =
namespace.list("o=Widget, c=US"); while (list.hasMore()) {
NameClassPair entry = (NameClassPair)list.next(); display(entry.getName(), entry.getClassName());
}
7.3 EJB(JAVA企业bean)
EJB(Enterprise
JavaBean): J2EE技术之所以赢得某体广泛重视的原因之一就是EJB。它们提供了一个框架来开发和实施分布式商务逻辑,由此很显著地简化了具有可伸缩性和高度复杂的企业级应用的开发。EJB规范定义了EJB组件在何时如何与它们的容器进行交互作用。容器负责提供公用的服务,例如目录服务、事务管理、安全性、资源缓冲池以及容错性。但这里值得注意的是,EJB并不是实现J2EE的唯一途径。正是由于J2EE的开放性,使得有的厂商能够以一种和EJB平行的方式来达到同样的目的。
分类:分别是会话Bean(Session Bean),实体Bean(Entity Bean)和消息驱动Bean(MessageDriven Bean)。
1.Session Bean用于实现业务逻辑,它可以是有状态的,也可以是无状态的。每当客户端请求时,容器就会选择一个Session Bean来为客户端服务。Session
Bean可以直接访问数据库,但更多时候,它会通过Entity Bean实现数据访问。
2.Entity Bean是域模型对象,用于实现O/R映射,负责将数据库中的表记录映射为内存中的Entity对象,事实上,创建一个Entity Bean对象相当于新建一条记录,删除一个Entity Bean会同时从数据库中删除对应记录,修改一个Entity Bean时,容器会自动将Entity Bean的状态和数据库同步。
3.MessageDriven
Bean是EJB2.0中引入的新的企业Bean,它基于JMS消息,只能接收客户端发送的JMS消息然后处理。MDB实际上是一个异步的无状态Session
Bean,客户端调用MDB后无需等待,立刻返回,MDB将异步处理客户请求。这适合于需要异步处理请求的场合,比如订单处理,这样就能避免客户端长时间的等待一个方法调用直到返回结果。
7.4 RMI(远程方法调用)
RMI(Remote Method
Invoke): 正如其名字所表示的那样,RMI协议调用远程对象上方法。它使用了序列化方式在客户端和服务器端传递数据。RMI是一种被EJB使用的更底层的协议。
举例:
//接口RmiSample
import java.rmi.Remote;
import
java.rmi.RemoteException;
public interface RmiSample extends Remote {
public int sum(int a, int b) throws RemoteException;
}
//实现类RmiSampleImpl
import
java.rmi.RemoteException;
import
java.rmi.server.UnicastRemoteObject;
public class RmiSampleImpl
extends UnicastRemoteObject implements RmiSample {
protected
RmiSampleImpl() throws RemoteException {
super();
// TODO Auto-generated constructor stub
}
@Override
public int sum(int a, int b) throws RemoteException {
// TODO Auto-generated method stub
return a+b;
}
}
//服务器RmiSampleServer
import java.rmi.Naming;
import
java.rmi.RemoteException;
import
java.rmi.registry.LocateRegistry;
public class RmiSampleServer {
/**
* @param args
*/
public static void main(String[]
args) {
// TODO Auto-generated method stub
try {LocateRegistry.createRegistry(8083)
;
RmiSampleImpl Server = new RmiSampleImpl();
// 将该对象实例与名称“SAMPLE-SERVER”捆绑
Naming.rebind("rmi://localhost:8083/SAMPLE-SERVER" , Server);
} catch (java.net.MalformedURLException me) {
System.out.println("Malformed URL: " + me.toString());
} catch (RemoteException
re) {
System.out.println("Remote exception: " + re.toString());
}
}
}
//客户端
import java.rmi.Naming;
import java.rmi.RemoteException;
public class RmiSampleClient {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated
method stub
try {
String url = "rmi://localhost:8083/SAMPLE-SERVER";
RmiSample RmiObject = (RmiSample) Naming.lookup(url);
System.out.println(" 1 + 2 =
" + RmiObject.sum(1, 2));
} catch (RemoteException exc) {
System.out.println("Error in
lookup: " + exc.toString());
} catch (java.net.MalformedURLException exc) {
System.out.println("Malformed
URL: " + exc.toString());
} catch (java.rmi.NotBoundException exc) {
System.out.println("NotBound:
" + exc.toString());
}
}
}
7.5 Java IDL/CORBA
Java IDL/CORBA: 在Java IDL的支持下,开发人员可以将Java和CORBA集成在一起。他们可以创建Java对象并使之可在CORBA ORB中展开, 或者他们还可以创建Java类并作为和其它ORB一起展开的CORBA对象的客户。后一种方法提供了另外一种途径,通过它Java可以被用于将你的新的应用和旧的系统相集成。
CORBA的核心是开放软件总线——ORB,它提供了网络环境无关性、操作系统无关性和开发语言无关性的公共平台。在面向对象的应用环境中,CORBA对象(本文所说的“对象”是面向对象系统中的术语,是指一个实体)的请求者不必知道它所请求的对象是在哪里,是如何实现的,而是由ORB来负责跨平台的运作管理,无须应用系统的开发者干预。CORBA所具有的跨平台、分布式、面向对象的这些优点使它具有广阔的应用前景,日益受到软件行业的重视。
CORBA是一个中间件规范并不是一个实体软件。软件开发者通过使用第三方的ORB工具或IDL语言来定义CORBA对象,实现ORB功能。
7.6 JSP
JSP(Java Server
Pages): JSP页面由HTML代码和嵌入其中的Java代码所组成。服务器在页面被客户端所请求以后对这些Java代码进行处理,然后将生成的HTML页面返回给客户端的浏览器。
7.7 Java Servlet
Servlet是一种小型的Java程序,它扩展了Web服务器的功能。作为一种服务器端的应用,当被请求时开始执行,这和CGI Perl脚本很相似。Servlet提供的功能大多与JSP类似,不过实现的方式不同。JSP通常是大多数HTML代码中嵌入少量的Java代码,而servlets全部由Java写成并且生成HTML。
7.8 XML(Extensible Markup Language)
XML是一种可以用来定义其它标记语言的语言。它被用来在不同的商务过程中共享数据。XML的发展和Java是相互独立的,但是,它和Java具有的相同目标正是平台独立性。通过将Java和XML的组合,您可以得到一个完美的具有平台独立性的解决方案。
7.9 JMS(Java Message Service)
7.9.1 简介
JMS是用于和面向消息的中间件相互通信的应用程序接口(API)。它既支持点对点的域,有支持发布/订阅(publish/subscribe)类型的域,并且提供对下列类型的支持:经认可的消息传递,事务型消息的传递,一致性消息和具有持久性的订阅者支持。JMS还提供了另一种方式来对您的应用与旧的后台系统相集成。
JMS即Java消息服务(Java Message Service)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
JMS 使您能够通过消息收发服务(有时称为消息中介程序或路由器)从一个 JMS 客户机向另一个 JMS客户机发送消息。消息是 JMS 中的一种类型对象,由两部分组成:报头和消息主体。报头由路由信息以及有关该消息的元数据组成。消息主体则携带着应用程序的数据或有效负载。根据有效负载的类型来划分,可以将消息分为几种类型,它们分别携带:简单文本 (TextMessage)、可序列化的对象 (ObjectMessage)、属性集合 (MapMessage)、字节流 (BytesMessage)、原始值流 (StreamMessage),还有无有效负载的消息 (Message)。
7.9.2 JMS的组成
JMS提供者
连接面向消息中间件的,JMS接口的一个实现。提供者可以是Java平台的JMS实现,也可以是非Java平台的面向消息中间件的适配器。
JMS客户
生产或消费基于消息的Java的应用程序或对象。
JMS生产者
创建并发送消息的JMS客户。
JMS消费者
接收消息的JMS客户。
JMS消息
包括可以在JMS客户之间传递的数据的对象
JMS队列
一个容纳那些被发送的等待阅读的消息的区域。队列暗示,这些消息将按照顺序发送。一旦一个消息被阅读,该消息将被从队列中移走。
JMS主题
一种支持发送消息给多个订阅者的机制。
7.9.3
JMS应用程序接口
ConnectionFactory 接口(连接工厂)
用户用来创建到JMS提供者的连接的被管对象。JMS客户通过可移植的接口访问连接,这样当下层的实现改变时,代码不需要进行修改。 管理员在JNDI名字空间中配置连接工厂,这样,JMS客户才能够查找到它们。根据消息类型的不同,用户将使用队列连接工厂,或者主题连接工厂。
Connection 接口(连接)
连接代表了应用程序和消息服务器之间的通信链路。在获得了连接工厂后,就可以创建一个与JMS提供者的连接。根据不同的连接类型,连接允许用户创建会话,以发送和接收队列和主题到目标。
Destination 接口(目标)
目标是一个包装了消息目标标识符的被管对象,消息目标是指消息发布和接收的地点,或者是队列,或者是主题。JMS管理员创建这些对象,然后用户通过JNDI发现它们。和连接工厂一样,管理员可以创建两种类型的目标,点对点模型的队列,以及发布者/订阅者模型的主题。
MessageConsumer 接口(消息消费者)
由会话创建的对象,用于接收发送到目标的消息。消费者可以同步地(阻塞模式),或异步(非阻塞)接收队列和主题类型的消息。
MessageProducer 接口(消息生产者)
由会话创建的对象,用于发送消息到目标。用户可以创建某个目标的发送者,也可以创建一个通用的发送者,在发送消息时指定目标。
Message 接口(消息)
是在消费者和生产者之间传送的对象,也就是说从一个应用程序传送到另一个应用程序。一个消息有三个主要部分:
消息头(必须):包含用于识别和为消息寻找路由的操作设置。
一组消息属性(可选):包含额外的属性,支持其他提供者和用户的兼容。可以创建定制的字段和过滤器(消息选择器)。
一个消息体(可选):允许用户创建五种类型的消息(文本消息,映射消息,字节消息,流消息和对象消息)。
消息接口非常灵活,并提供了许多方式来定制消息的内容。
Session 接口(会话)
表示一个单线程的上下文,用于发送和接收消息。由于会话是单线程的,所以消息是连续的,就是说消息是按照发送的顺序一个一个接收的。会话的好处是它支持事务。如果用户选择了事务支持,会话上下文将保存一组消息,直到事务被提交才发送这些消息。在提交事务之前,用户可以使用回滚操作取消这些消息。一个会话允许用户创建消息生产者来发送消息,创建消息消费者来接收消息。
7.9.4 JMS特性
1.
消息通信模型
JMS
支持两种消息通信模型:点到点(point-to-point)(PTP)模型和发布/订阅(Pub/Sub)模型。除了下列不同之外,这两种消息通信模型非常地相似:
PTP
模型规定了一个消息只能有一个接收者;Pub/Sub 模型允许一个消息可以有多个接收者。
2.
消息组成
消息传递系统的中心就是消息。
一条 Message 分为三个组成部分:
头(header)是个标准字段集,客户机和供应商都用它来标识和路由消息。
属性(property)支持把可选头字段添加到消息。如果您的应用程序需要不使用标准头字段对消息编目和分类,您就可以添加一个属性到消息以实现这个编目和分类。提供 set<Type>Property(...) 和 get<Type>Property(...) 方法以设置和获取各种 Java 类型的属性,包括 Object。JMS 定义了一个供应商选择提供的标准属性集。
消息的主体(body)包含要发送给接收应用程序的内容。每个消息接口特定于它所支持的内容类型。
JMS
为不同类型的内容提供了它们各自的消息类型,但是所有消息都派生自 Message 接口。
StreamMessage:包含 Java 基本数值流,用标准流操作来顺序的填充和读取。
MapMessage:包含一组名/值对;名称为 string 类型,而值为
Java 的基本类型。
TextMessage:包含一个 String。
ObjectMessage:包含一个 Serializable Java 对象;能使用
JDK 的集合类。
BytesMessage:包含未解释字节流: 编码主体以匹配现存的消息格式。
XMLMessage:
包含XML内容。扩展TextMessage,XMLMessage
类型的使用,使得消息过滤非常便利。
3.
消息确认模式
非事务性会话中,应用程序创建的会话有5 种确认模式,而在事务性会话中,确认模式被忽略。
五种确认模式说明:
AUTO_ACKNOWLEDGE:自动确认模式。一旦接收方应用程序的方法调用从处理消息处返回,会话对象就会确认消息的接收。
CLIENT_ACKNOWLEDGE:客户端确认模式。会话对象依赖于应用程序对被接收的消息调用一个acknowledge()方法。一旦这个方法被调用,会话会确认最后一次确认之后所有接收到的消息。这种模式允许应用程序以一个调用来接收,处理并确认一批消息。注意:在管理控制台中,如果连接工厂的Acknowledge Policy(确认方针)属性被设置为"Previous"(提前),但是你希望为一个给定的会话确认所有接收到的消息,那么就用最后一条消息来调用acknowledge()方法。
DUPS_OK_ACKNOWLEDGE:允许副本的确认模式。一旦接收方应用程序的方法调用从处理消息处返回,会话对象就会确认消息的接收;而且允许重复确认。在需要考虑资源使用时,这种模式非常有效。注意:如果你的应用程序无法处理重复的消息的话,你应该避免使用这种模式。如果发送消息的初始化尝试失败,那么重复的消息可以被重新发送。
NO_ACKNOWLEDGE:不确认模式。不确认收到的消息是需要的。消息发送给一个NO_ACKNOWLEDGE 会话后,它们会被WebLogic 服务器立即删除。在这种模式下,将无法重新获得已接收的消息,而且可能导致下面的结果:1. 消息可能丢失;和(或者)另一种情况:2. 如果发送消息的初始化尝试失败,会出现重复消息被发送的情况。
MULTICAST_NO_ACKNOWLEDGE:IP组播下的不确认模式,同样无需确认。发送给一个MULTICAST_NO_ACKNOWLEDGE会话的消息, 会共享之前所述的NO_ACKNOWLEDGE 确认模式一样的特征。这种模式支持希望通过IP 组播方式进行消息通信的应用程序,而且无需依赖会话确认提供的服务质量。注意:如果你的应用程序无法处理消息的丢失或者重复,那么你应该避免使用这种模式。如果发送消息的初始化尝试失败的话,重复的消息可能会被再次发送。
注:在上表的5 种确认模式中,AUTO_ACKNOWLEDGE ,DUPS_OK_ACKNOWLEDGE 和CLIENT_ACKNOWLEDGE 是JMS
规范定义的,NO_ACKNOWLEDGE 和MULTICAST_NO_ACKNOWLEDGE是WebLogic JMS 提供的。
//获取初始化容器
private static InitialContext getInitialContext() throws NamingException {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
env.put(Context.PROVIDER_URL, PROVIDER_URL);
return new InitialContext(env);
}
具体列子,在weblogic中配置JMS服务器
1、新建JMS Servers
2、新建JMS Model
3、新建工厂
4、新建队列(点对点)(或新建发布/订阅)
代码:
点对点:写一个服务类(发送类),以及一个客户端类(接受类)
//代码略
订阅/发布模式
//发布者部分代码
ctx = new InitialContext();
// 1.通过JNDI查询ConnectionFactory。JMS中连接工厂分QueueConnectionFactory和
//
TopicConnectionFactory两种,Weblogic不区分这两种类型。
TopicConnectionFactory factory = (TopicConnectionFactory)
ctx
.lookup("ConnectionFactoryTest");
// 2.通过工厂建立连接connection
connection = factory.createTopicConnection();
// 3.创建session
session = connection.createTopicSession(false,
TopicSession.AUTO_ACKNOWLEDGE);
// 4.创建Topic,必须新建一个Topic JNDI。
Topic topic = (Topic) ctx.lookup("TopicTest");
// 5.创建TopicPublisher
TopicPublisher tp = session.createPublisher(topic);
// 构造消息体
TextMessage tm = session.createTextMessage();
tm.setText("csdn
tczxg");
tm.setStringProperty("user", "嘿嘿,JMS挺简单的123。。。");
tp.publish(tm);
// 必须关闭连接
connection.close();
//接收者(订阅者)部分代码
ctx = new InitialContext();
// 1.通过JNDI查询ConnectionFactory
TopicConnectionFactory factory = (TopicConnectionFactory)
ctx
.lookup("ConnectionFactoryTest");
// 2.通过工厂建立连接connection
connection = factory.createTopicConnection();
// 3.创建session
session = connection.createTopicSession(false,
TopicSession.AUTO_ACKNOWLEDGE);
// 4.创建Topic
Topic topic = (Topic) ctx.lookup("TopicTest");
// 5.创建订阅者
TopicSubscriber ts = session.createSubscriber(topic);
connection.start();
// 构造消息体
Message message = ts.receive();
if (message instanceof TextMessage)
{
TextMessage
tm = (TextMessage)
message;
String user = tm.getStringProperty("user");
System.out.println(tm.getText() + ", user : " + user);
}
connection.close();
7.10 JTA(Java Transaction Architecture)
JTA定义了一种标准的API,应用系统由此可以访问各种事务监控。
7.11 JTS(Java Transaction Service)
JTS是CORBA OTS事务监控的基本的实现。JTS规定了事务管理器的实现方式。该事务管理器是在高层支持Java Transaction API (JTA)规范,并且在较底层实现OMG OTS
specification的Java映像。JTS事务管理器为应用服务器、资源管理器、独立的应用以及通信资源管理器提供了事务服务。
7.12 JavaMail
JavaMail是用于存取邮件服务器的API,它提供了一套邮件服务器的抽象类。不仅支持SMTP服务器,也支持IMAP服务器。
7.13 JAF(JavaBeans Activation Framework)
JAF是一个专用的数据处理框架,它用于封装数据,并为应用程序提供访问和操作数据的接口。JAF的主要作用在于让java应用程序知道如何对一个数据源进行查看、编辑和打印等操作。
7.14 webservice
Web Services是由企业发布的完成其特定商务需求的在线应用服务,其他公司或应用软件能够通过Internet来访问并使用这项在线服务。
它是一种构建应用程序的普遍模型,可以在任何支持网络通信的操作系统中实施运行;它是一种新的web 应用程序分支,是自包含、自描述、模块化的应用,可以发布、定位、通过web调用。Web Service是一个应用组件,它逻辑性的为其他应用程序提供数据与服务.各应用程序通过网络协议和规定的一些标准数据格式(Http,XML,Soap)来访问Web
Service,通过Web Service内部执行得到所需结果.Web
Service可以执行从简单的请求到复杂商务处理的任何功能。一旦部署以后,其他Web Service应用程序可以发现并调用它部署的服务。
XFire是新一代的Java Web服务引擎,XFire使得在JavaEE应用中发布Web服务变得轻而易举。和其他Web服务引擎相比,XFire的配置非常简单,可以非常容易地和Spring集成,它使得Java开发人员终于可以获得和.Net开发人员一样的开发效率。
举例:首先应用相关的xfile的jar文件
1、Web.xml中配置
<servlet>
<servlet-name>XFireServlet</servlet-name>
<servlet-class>org.codehaus.xfire.transport.http.XFireConfigurableServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>XFireServlet</servlet-name>
<url-pattern>/servlet/XFireServlet/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>XFireServlet</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
2、service.xml中配置
<beans>
<service xmlns="http://xfire.codehaus.org/config/1.0">
<name>SayHiService</name>
<namespace>http://cn.com.pansky/SayHiService</namespace>
<serviceClass>com.xfire.SayHiService</serviceClass>
<implementationClass>com.xfire.SayHiServiceImpl</implementationClass>
</service>
</beans>
3、接口
public interface SayHiService {
public String sayHi(String name);
}
4、实现类
public class SayHiServiceImpl implements SayHiService {
@Override
public String
sayHi(String name) {
if (name == null) {
return "连名字也不肯告诉我吗?";
}
return name + ", 你吃了吗?没吃回家吃去吧。";
}
public String 不告诉你() {
return "我的名字不告诉你!";
}
}
5、客户端调用类
public class tt {
/**
* @param args
*/
public static void main(String[] args) {
String serviceURL = "http://localhost:8082/TestWebservice/services/SayHiService";
Service serviceModel = new ObjectServiceFactory().create(
SayHiService.class, null, "http://cn.com.pansky/SayHiService",
null);
XFireProxyFactory serviceFactory = new XFireProxyFactory();
try {
SayHiService service = (SayHiService) serviceFactory.create(
serviceModel, serviceURL);
Client client = Client.getInstance(service);
//
client.addOutHandler(new OutHeaderHandler());
// disable
timeout
client.setProperty(CommonsHttpMessageSender.HTTP_TIMEOUT, "1");
String hello = service.sayHi("张山疯");
System.out.println("服务器对[张山疯] 的回答是:" + hello);
hello = service.sayHi(null);
System.out.println("服务器胡言乱语说:" + hello);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
8、数据库
8.1事务
8.1.1脏读
脏读又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的。脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
8.1.2 更新丢失
第一类更新丢失(回滚丢失)
第二类更新丢失(覆盖丢失)
两个事务都同时更新一行数据,但是后来提交的事务所做的修改会覆盖前面提交的事务的修改或者后面的事务回滚了前面的事务提交的数据,导致结果不是用户想要的结果。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。
一、解决思路
基本两种思路,一种是悲观锁,另外一种是乐观锁; 简单的说就是一种(悲观锁)假定这样的问题是高概率的,最好一开始就锁住,免得更新老是失败;另外一种(乐观锁)假定这样的问题是小概率的,最后一步做更新的时候再锁住,免得锁住时间太长影响其他人做有关操作。
二、解决方案
1、悲观锁
a.传统的悲观锁法(不推荐):
在弹出修改页面初始化时(这种情况下一般会去从数据库查询出来),在这个初始化查询中使用select ...for update nowait, 通过添加for update
nowait语句,将这条记录锁住,避免其他用户更新,从而保证后续的更新是在正确的状态下更新的。然后在保持这个链接的状态下,在做更新提交。当然这个有个前提就是要保持链接,就是要对链接要占用较长时间,这个在现在web系统高并发高频率下显然是不现实的。
b.现在的悲观锁法(推荐优先使用):
在修改这个页面做提交时先查询下,当然这个查询必须也要加锁(select ...for update nowait),有人会说,在这里做个查询确认记录是否有改变不就行了吗,是的,是要做个确认,只是你不加for update就不能保证你在查询到更新提交这段时间里这条记录没有被其他会话更新过,所以这种方式也需要在查询时锁定记录,保证在这条记录没有变化的基础上再做更新,若有变化则提示告知用户。
2、乐观锁
a.旧值条件(前镜像)法:
就是在sql更新时使用旧的状态值做条件,SQL大致如下 Update table set col1 =
newcol1value, col2 = newcol2value…. where col1 =
oldcol1value and col2 = oldcol2value….,在上面的例子中我们就可以把当前工资作为条件进行更新,如果这条记录已经被其他会话更新过,则本次更新了0行,这里我们应用系统一般会做个提示告知用户重新查询更新。这个取哪些旧值作为条件更新视具体系统实际情况而定。(这种方式有可能发生阻塞,如果应用其他地方使用悲观锁法长时间锁定了这条记录,则本次会话就需要等待,所以使用这种方式时最好统一使用乐观锁法。)
b.使用版本列法(推荐优先使用):
其实这种方式是一个特殊化的前镜像法,就是不需要使用多个旧值做条件,只需要在表上加一个版本列,这一列可以是NUMBER或 DATE/TIMESTAMP列,加这列的作用就是用来记录这条数据的版本(在表设计时一般我们都会给每个表增加一些NUMBER型和DATE型的冗余字段,以便扩展使用,这些冗余字段完全可以作为版本列用),在应用程序中我们每次操作对版本列做维护即可。在更新时我们把上次版本作为条件进行更新。
综上所述,我们对丢失更新问题建议采取上面的悲观锁b方法或乐观锁b方法(蓝色字体已标注),其实这两种方式的本质都一样,都是在更新提交时做一次查询确认在更新提交,我个人觉得都是乐观的做法,区别在于悲观锁b方法是通过select..for update方式,这个可能会导致其他会话的阻塞,而乐观锁b方法需要多一个版本列的维护。
个人建议:在用户并发数比较少且冲突比较严重的应用系统中选择悲观锁b方法,其他情况首先乐观锁版本列法。
8.1.3 不可重复读
一个事务对同一行数据重复读取两次,但是却得到了不同的结果。它包括以下情况:
(1) 事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。
(2) 幻读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据或删除数据造成的。
8.1.4 解决方案
为了避免上面出现的几种情况,在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同。
8.1.5未授权读取
也称为读未提交(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
8.1.6授权读取
也称为读提交(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
8.1.7可重复读取
可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
8.1.8序列化
序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
隔离级别 |
更新丢失 |
脏读取 |
重复读取 |
幻读 |
未授权读取(排他写锁) |
N |
Y |
Y |
Y |
授权读取(瞬间共享读锁、排他写锁) |
N |
N |
Y |
Y |
可重复读取(共享读锁、排他写锁) |
N |
N |
N |
Y |
串行(行级锁) |
N |
N |
N |
N |
8.1.9事务的四个属性
原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。
8.2 锁机制
8.2.1简介
在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上排它锁时,其他的事务不能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。
8.2.2类型
根据保护的对象不同,Oracle数据库锁可以分为以下几大类:DML锁(data locks,数据锁),用于保护数据的完整性;DDL锁(dictionary locks,字典锁),用于保护数据库对象的结构,如表、索引等的结构定义;内部锁和闩(internal
locks and latches),保护 数据库的内部结构。
DML锁的目的在于保证并发情况下的数据完整性,。在Oracle数据库中,DML锁主要包括TM锁和TX锁,其中TM锁称为表级锁,TX锁称为事务锁或行级锁。
当Oracle 执行DML语句时,系统自动在所要操作的表上申请TM类型的锁。当TM锁获得后,系统再自动申请TX类型的锁,并将实际锁定的数据行的锁标志位进行置位。这样在事务加锁前检查TX锁相容性时就不用再逐行检查锁标志,而只需检查TM锁模式的相容性即可,大大提高了系统的效率。TM锁包括了SS、SX、S、X 等多种模式,在数据库中用0-6来表示。不同的SQL操作产生不同类型的TM锁。
在数据行上只有X锁(排他锁)。在 Oracle数据库中,当一个事务首次发起一个DML语句时就获得一个TX锁,该锁保持到事务被提交或回滚。当两个或多个会话在表的同一条记录上执行 DML语句时,第一个会话在该条记录上加锁,其他的会话处于等待状态。当第一个会话提交后,TX锁被释放,其他会话才可以加锁。
当Oracle数据库发生TX锁等待时,如果不及时处理常常会引起Oracle数据库挂起,或导致死锁的发生,产生ORA-60的错误。这些现象都会对实际应用产生极大的危害,如长时间未响应,大量事务失败等。
8.2.3悲观封锁和乐观封锁
一、悲观封锁
锁在用户修改之前就发挥作用:
Select ..for update(nowait)
Select * from tab1 for update
用户发出这条命令之后,oracle将会对返回集中的数据建立行级封锁,以防止其他用户的修改。
如果此时其他用户对上面返回结果集的数据进行dml或ddl操作都会返回一个错误信息或发生阻塞。
二、乐观锁
乐观的认为数据在select出来到update进取并提交的这段时间数据不会被更改。这里面有一种潜在的危险就是由于被选出的结果集并没有被锁定,是存在一种可能被其他用户更改的可能。因此Oracle仍然建议是用悲观封锁,因为这样会更安全。
8.2.3 DML锁分类表
表1 Oracle的TM锁类型 |
|||
锁模式 |
锁描述 |
解释 |
SQL操作 |
0 |
none |
||
1 |
NULL |
空 |
Select |
2 |
SS(Row-S) |
行级共享锁,其他对象只能查询这些数据行 |
Select for update、Lock for update、Lock row share |
3 |
SX(Row-X) |
行级排它锁,在提交前不允许做DML操作 |
Insert、Update、Delete、Lock row share |
4 |
S(Share) |
共享锁 |
Create index、Lock share |
5 |
SSX(S/Row-X) |
共享行级排它锁 |
Lock share row exclusive |
6 |
X(Exclusive) |
排它锁 |
Alter table、Drop able、Drop index、Truncate table 、Lock exclusive |
8.2.4数据库封锁方式
1、内部级封锁
内部级封锁是用于保护ORACLE内部结构,由系统内部实现,用户不能访问,因此我们不必对此做过多的了解。
2、DDL级封锁(字典/语法分析封锁)
DDL级封锁也是由ORACLE RDBMS来控制,它用于保护数据字典和数据定义改变时的一致性和完整性。它是系统在对SQL定义语句作语法分析时自动地加锁,无需用户干予。字典/语法分析封锁共分三类:
(1)、字典操作锁:用于对字典操作时,锁住数据字典,此封锁是独占的,从而保护任何一个时刻仅能对一个字典操作。
(2)、字典定义锁:用于防止在进行字典操作时又进行语法分析,这样可以避免在查询字典的同时改动某个表的结构。
(3)、表定义锁:用于 一个SQL语句正当访问某个表时,防止字典中与该表有关的项目被修改。
3、DML级封锁
DML级封锁用于控制并发事务中的数据操纵,保证数据的一致性和完整性,其封锁对象可以是表或行。
对用户的数据操纵,Oracle可以自动为操纵的数据进行封锁,但如果有操纵授权,则为满足并发操纵的需要另外实施封锁。DML封锁可由一个用户进程以显式的方式加锁,也可通过某些SQL语句隐含方式实现。
DML锁有如下三种封锁方式:
(1)、共享封锁方式(SHARE)
(2)、独占封锁方式(EXCLUSIVE)
(3)、共享更新封锁(SHARE UPDATE)
1、共享方式的表封锁
共享方式的表封锁是对表中的所有数据进行封锁,该锁用于保护查询数据的一致性,防止其它用户对已封锁的表进行更更新。其它用户只能对该表再施加共享方式的锁,而不能再对该表施加独占方式的封锁,共享更新锁可以再施加,但不允许持有共享更新封锁的进程做更新。共享该表的所有用户只能查询表中的数据,但不能更新。
2、独占方式表封锁
独占方式表封锁是用于封锁表中的所有数据,拥有该独占方式表封锁的用户,即可以查询该表,又可以更新该表,其它的用户不能再对该表施加任何封锁(包括共享、独占或共享更新封锁)。其它用户虽然不能更新该表,但可以查询该表。
3、共享更新封锁方式
共享更新封锁是对一个表的一行或多行进行封锁,因而也称作行级封锁。表级封锁虽然保证了数据的一致性,但却减弱了操作数据的并行性。行级封锁确保在用户取得被更新的行到该行进行更新这段时间内不被其它用户所修改。因而行级锁即可保证数据的一致性又能提高数据操作的迸发性。
9、SOAP
9.1 简介
SOAP:简单对象访问协议,简单对象访问协议(SOAP)是一种轻量的、简单的、基于 XML 的协议,它被设计成在
WEB 上交换结构化的和固化的信息。 SOAP 可以和现存的许多因特网协议和格式结合使用,包括超文本传输协议( HTTP),简单邮件传输协议(SMTP),多用途网际邮件扩充协议(MIME)。它还支持从消息系统到远程过程调用(RPC)等大量的应用程序。
SOAP 包括四个部分:
SOAP 封装:它定义了一个框架
, 该框架描述了消息中的内容是什么,谁应当处理它以及它是可选的还是必须的。
SOAP 编码规则:它定义了一种序列化的机制,用于交换应用程序所定义的数据类型的实例。
SOAP RPC 表示:它定义了用于表示远程过程调用和应答的协定。
SOAP 绑定:定义了一种使用底层传输协议来完成在节点间交换SOAP封装的约定。
SOAP 消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式。所有的 SOAP 消息都使用 XML 编码。一条 SOAP 消息就是一个包含有一个必需的 SOAP 的封装包,一个可选的
SOAP 标头和一个必需的 SOAP 体块的 XML 文档。
协议结构
SOAP 消息格式:
SOAP 标头
<SOAP-ENV: Envelope Attributes>
<SOAP-ENV:Body Attributes>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
目前主要在web服务中运用。
如:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<funName xmlns="http://tempuri.org/">
<parm1>value1</parm1>
<parm2>value2</parm2>
</funName>
</soap:Body>
</soap:Envelope>
SOAP 通讯协议使用 HTTP 来发送XML 格式的信息。HTTP与RPC 的协议很相似,它简单、 配置广泛,并且对防火墙比其它协议更容易发挥作用。HTTP 请求一般由 Web 服务器软件(如 IIS 和Apache)来处理, 但越来越多的应用服务器产品正在支持HTTP。XML 作为一个更好的网络数据表达方式( NDR)。SOAP 把 XML 的使用代码化为请求和响应参数编码模式, 并用HTTP 作传输。具体地讲, 一个SOAP 方法可以简单地看作遵循SOAP编码规则的HTTP请求和响应, 一个 SOAP 终端则可以看作一个基于HTTP 的URL, 它用来识别方法调用的目标。像CORBA/ IIOP一样, SOAP不需要具体的对象绑定到一个给定的终端, 而是由具体实现程序来决定怎样把对象终端标识符映像到服务器端的对象。
SOAP是一种基于XML的协议,它用于在分布式环境中发送消息,并执行远程过程调用。使用SOAP,不用考虑任何特定的传输协议(尽管通常选用HTTP协议), 就能使数据序列化。用SOAP来构建平台与语言中性的互操作系统是一个好的选择。总之,SOAP和 Web服务已为在XML上构建分布式应用程序基础结构所需的一切都考虑好了。通过解决COM和Java组件对象模型之间的冲突,SOAP把多个平台在访问数据时所出现的 不兼容性问题减至最少。
10、终结面试
10.1基础面试题
10.1.1、forward和redirect的区别
forward仅是容器中控制权的转向,在客户端浏览器地址栏中不会显示出转向后的地址;
redirect则是完全的跳转,浏览器将会得到跳转的地址,并重新发送请求链接。这样,从浏览器的地址栏中可以看到跳转后的链接地址。
所以,forward更加高效,在forward可以满足需要时,尽量使用forward()方法,并且,这样也有助于隐藏实际的链接。