try { threads[i].join();
//等待所有线程运行结束
} catch(InterruptedException e){
// ignore } } System.out.println(“Finally, John's balance is:” + acc.getBalance());}}
注意,上面在Account的deposit和withdraw方法中之所以要把对amount的运算使用一个临时变量首先存储,sleep一段时间,然后,再赋值给amount,是为了模拟真实运行时的情况。因为在真实系统中,账户信息肯定是存储在持久媒介中,比如RDBMS中,此处的睡眠的时间相当于比较耗时的数据库操作,最后把临时变量tmp的值赋值给amount相当于把amount的改动写入数据库中。运行AccountTest,结果如下(每一次结果都会不同): E:javaexerin>java AccountTest Finally, John's balance is:3900.0 E:javaexerin>java AccountTest Finally, John's balance is:4900.0 E:javaexerin>java AccountTest Finally, John's balance is:4700.0
E:javaexerin>java AccountTest
Finally, John's balance is:3900.0
E:javaexerin>java AccountTest
Finally, John's balance is:3900.0
E:javaexerin>java AccountTest
Finally, John's balance is:5200.0
为什么会出现这样的问题?这就是多线程中的同步的问题。在我们的程序中,Account中的amount会同时被多个线程所访问,这就是一个竞争资源,通常称作竞态条件。对于这样的多个线程共享的资源我们必须进行同步,以避免一个线程的改动被另一个线程所覆盖。在我们这个程序中,Account中的amount是一个竞态条件,所以所有对amount的修改访问都要进行同步,我们将deposit()和withdraw()方法进行同步,修改为:
public synchronized void deposit(float amt){ float tmp = amount;tmp += amt;try {
Thread.sleep(1);
//模拟其它处理所需要的时间,比如刷新数据库等
} catch(InterruptedException e){
// ignore } amount = tmp;}
public synchronized void withdraw(float amt){ float tmp = amount;tmp-= amt;try {
Thread.sleep(1);
//模拟其它处理所需要的时间,比如刷新数据库等
} catch(InterruptedException e){ // ignore } amount = tmp;}
此时,再运行,我们就能够得到正确的结果了。Account中的getBalance()也访问了amount,为什么不对getBalance()同步呢?因为getBalance()并不会修改amount的值,所以,同时多个线程对它访问不会造成数据的混乱。
String StringBuffer 代码示例
public class Test{
public static void stringReplace(String text){
text=text+”c”;//1 }
public static void bufferReplace(StringBuffer text){ text=text.append(“c”);//2 }
public static void main(String args[]){ String textString=new String(“java”);
StringBuffer textBuffer=new StringBuffer(“java”);stringReplace(textString);bufferReplace(textBuffer);
System.out.println(textString+textBuffer);} }
代码分析:
这段代码先产生一个字符串对象textString,然后调用stringReplace方法,在方法里产生一个text局部变量指向new String(“java”)对象。这时,执行到第一行时,由于字符串对象不可改变,所以会新产生一个字符串对象”javac”,然后将局部变量text指向他。在做返回的时候,textString所指向的对象并没有改变。所以textString 的值还是”java”。
当使用StringBuffer的时候,调用bufferReplace方法,在方法里产生了一个text这样一个局部变量,在对text对象进行操作时,指的是对text所指向对象进行操作。由于StringBuffer不会产生新的对象。所以在进行到第2行时,它的改变会影响对象值的改变,这时返回之后,结果是javac
所以最后的结果是javajavac I/O注意要点
(2008-07-23 12:52:40)转载 标签:
杂谈
1.掌握常用的输入流读取操作方法
– int read()//读取一个字节,返回字节的ASC码
–
int read(char cbuf[])//读取字节进入chuf字符数组,返回读取的字节数 –
int read(char cbuf[], int offset, int length)//读取字节进入chuf字符数组,返回读取的字节数,offset为数据起始位置,length读入字节长度 –
– int available();//获得读入流的字节长度 –
– close();//关闭流
对于读入流,在建立流的时候,要求数据源必须存在。如建一个文件流: FileInputStream fin = new FileInputStream(“c:/2.mp3”);这时候,如果c盘下的2.mp3不存在的话会抛出一个FileNotFoundException异常。表示文件未找到。
2.掌握常用的输出流写入操作方法
– void write(int b)//写入一个字节进输出流。注意int b是这个字节的ASC码。
– void close()//关闭流 – void flush()//刷新流。
对于写入流,在建立流的时候,如果数据源不存在,系统会自动创建。如建立一个写入流。
FileOutputStream fout = new FileOutputStream(“c:/2.mp3”);
这时候,如果文件c:/2.mp3不存在的话,系统会自动创建一个2.mp3的文件,而不会抛出异常。
在操作写入流时要注意:写入流里的数据必须经过flush刷新或关闭流之后,数据才能写入目标数据源。如果不关闭或刷新流,则可能写不进数据源。如建立了一个文件写入流,如果在写入时不关闭或刷新流,则文件里的内容可能是空的。
类的封装性
方法的访问说明符
public protected default private 同类 ok ok ok ok 同包 ok ok ok error 子类 ok ok error error 通用 ok error error error 方法的其他修饰符
final 用于该方法不想被子类覆盖的情况
static:static修饰的也不能被覆盖 只是产生了一个新的static方法
类的继承性
类的继承性的特点 子类可以继承的部分:(1)父类中公开级的成员;(2)父类中保护级的成员;
(3)如果子类和父类在同一个包里,则子类继承父类中缺省的包访问级的成员; 子类不能继承的部分:(1)父类中私有级的成员;
(2)如果不在同一个包里,则不能继承缺省级的成员;(3)同名的成员函数或成员变量; 继承中的构造函数
构造函数是比较特殊的一类函数
在继承时,构造函数不会被继承,也不会被覆盖
父类和子类的构造函数依然是独立存在,并且分别发挥着作用
成员的覆盖
如果子类的成员函数或成员变量与父类同名,则子类的成员函数或成员变量将隐蒇父类的同名成员。
如果一定要访问父类的变量,可以有两种方法:
(1)在子类内部用“super”关键字来访问父类的同名变量;(2)用多态性的功能来访问父类对象
类方法的覆盖
方法覆盖时应遵循的原则:
覆盖的方法的存取权限必须高于被覆盖的方法,如:父类的方法为protected的,子类的方法为public可以;反之,编译报错。覆盖后的方法不能比被覆盖的方法产生更多的异常
如果用一个父类的变量指向子类,再调用同名的函数,会出现什么情况呢? 调用的还是子类的函数。从这点看,成员函数和成员变量是完全不同的。
类的多态性 利用动态联编,一个函数调用语句可能实现不同类型的函数,这种现象就是多态性
当你通过父类调用函数时,如果变量所指向的是一个子类对象,那么所调的仍然是子类函数,这就是多态性 抽象函数和抽象类的概念
抽象函数:仅有定义,没有具体实现的函数 抽象类:含有抽象函数的类
定义一个抽象类,需要在类的定义前面加上“abstract”关键字 定义一个抽象函数,需要在函数定义的前面加上“abstract”关键字
一个类如果被定义为抽象类,它就不能实例化,也就是说,不能有自己的对象
抽象类的使用
抽象函数的意义是没有具体实现的函数
它的作用就是被子类的相同函数覆盖,或通过多态性指向子类的相同函数 通过抽象函数,可以定义一整套完整的函数功能,再派生出若干子类来实现 不同的子类可以以不同的形式实现这些功能,但函数形式是完全一致的 抽象类必须有子类,不然就没有意义
抽象类中不仅仅有抽象函数,也可以有普通的成员函数和成员变量 但如果一个类中有抽象函数,那么这个类必须定义为抽象类 如果一个类继承了父类的几个抽象函数,但没有全部实现,那么这个类也必须定义为抽象类
OutOfMemoryError
(一)(2008-07-31 00:46:25)转载 标签:
杂谈
OutOfMemoryError
一、内存溢出类型
1、java.lang.OutOfMemoryError: PermGen space JVM管理两种类型的内存,堆和非堆。堆是给开发人员用的上面说的就是,是在JVM启动时创建;非堆是留给JVM自己用的,用来存放类的信息的。它和堆不同,运行期内GC不会释放空间。如果web app用了大量的第三方jar或者应用有太多的class文件而恰好MaxPermSize设置较小,超出了也会导致这块内存的占用过多造成溢出,或者tomcat热部署时侯不会清理前面加载的环境,只会将context更改为新部署的,非堆存的内容就会越来越多。
2、java.lang.OutOfMemoryError: Java heap space 第一种情况是个补充,主要存在问题就是出现在这个情况中。其默认空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。如果内存剩余不到40%,JVM就会增大堆到Xmx设置的值,内存剩余超过70%,JVM就会减小堆到Xms设置的值。所以服务器的Xmx和Xms设置一般应该设置相同避免每次GC后都要调整虚拟机堆的大小。假设物理内存无限大,那么JVM内存的最大值跟操作系统有关,一般32位机是1.5g到3g之间,而64位的就不会有限制了。
注意:如果Xms超过了Xmx值,或者堆最大值和非堆最大值的总和超过了物理内存或者操作系统的最大限制都会引起服务器启动不起来。垃圾回收GC的角色
JVM调用GC的频度还是很高的,主要两种情况下进行垃圾回收:
当应用程序线程空闲;另一个是java内存堆不足时,会不断调用GC,若连续回收都解决不了内存堆不足的问题时,就会报out of memory错误。因为这个异常根据系统运行环境决定,所以无法预期它何时出现。
根据GC的机制,程序的运行会引起系统运行环境的变化,增加GC的触发机会。为了避免这些问题,程序的设计和编写就应避免垃圾对象的内存占用和GC的开销。显示调用System.GC()只能建议JVM需要在内存中对垃圾对象进行回收,但不是必须马上回收,一个是并不能解决内存资源耗空的局面,另外也会增加GC的消耗。
二、JVM内存区域组成
简单的说java中的堆和栈
java把内存分两种:一种是栈内存,另一种是堆内存
1。在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配; 2。堆内存用来存放由new创建的对象和数组
在函数(代码块)中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由java虚拟机的自动垃圾回收器来管理
堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。缺点就是要在运行时动态分配内存,存取速度较慢; 栈的优势是存取速度比堆要快,缺点是存在栈中的数据大小与生存期必须是确定的无灵活性。
java堆分为三个区:New、Old和Permanent GC有两个线程:
新创建的对象被分配到New区,当该区被填满时会被GC辅助线程移到Old区,当Old区也填满了会触发GC主线程遍历堆内存里的所有对象。Old区的大小等于Xmx减去-Xmn java栈存放
栈调整:参数有+UseDefaultStackSize-Xss256K,表示每个线程可申请256k的栈空间
每个线程都有他自己的Stack
三、JVM如何设置虚拟内存
提示:在JVM中如果98%的时间是用于GC且可用的Heap size不足2%的时候将抛出此异常信息。
提示:Heap Size最大不要超过可用物理内存的80%,一般的要将-Xms和-Xmx选项设置为相同,而-Xmn为1/4的-Xmx值。
提示:JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。
默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC后调整堆的大小。
提示:假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了
提示:注意:如果Xms超过了Xmx值,或者堆最大值和非堆最大值的总和超过了物理内存或者操作系统的最大限制都会引起服务器启动不起来。提示:设置NewSize、MaxNewSize相等,“new”的大小最好不要大于“old”的一半,原因是old区如果不够大会频繁的触发“主” GC,大大降低了性能 JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64; 由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。解决方法:手动设置Heap size 修改TOMCAT_HOME/bin/catalina.bat 在“echo “Using CATALINA_BASE: $CATALINA_BASE””上面加入以下行: JAVA_OPTS=“-server-Xms800m-Xmx800m-XX:MaxNewSize=256m”
OutOfMemoryError
四、性能检查工具使用
定位内存泄漏:
JProfiler工具主要用于检查和跟踪系统(限于Java开发的)的性能。JProfiler可以通过时时的监控系统的内存使用情况,随时监视垃圾回收,线程运行状况等手段,从而很好的监视JVM运行情况及其性能。
1.应用服务器内存长期不合理占用,内存经常处于高位占用,很难回收到低位; 2.应用服务器极为不稳定,几乎每两天重新启动一次,有时甚至每天重新启动一次;
3.应用服务器经常做Full GC(Garbage Collection),而且时间很长,大约需要30-40秒,应用服务器在做Full GC的时候是不响应客户的交易请求的,非常影响系统性能。
因为开发环境和产品环境会有不同,导致该问题发生有时会在产品环境中发生,通常可以使用工具跟踪系统的内存使用情况,在有些个别情况下或许某个时刻确实是使用了大量内存导致out of memory,这时应继续跟踪看接下来是否会有下降,如果一直居高不下这肯定就因为程序的原因导致内存泄漏。
五、不健壮代码的特征及解决办法
1、尽早释放无用对象的引用。好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。
对于仍然有指针指向的实例,jvm就不会回收该资源,因为垃圾回收会将值为null的对象作为垃圾,提高GC回收机制效率;
2、我们的程序里不可避免大量使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域; String str = “aaa”;String str2 = “bbb”;String str3 = str + str2;//假如执行此次之后str ,str2以后再不被调用,那它就会被放在内存中等待Java的gc去回收,程序内过多的出现这样的情况就会报上面的那个错误,建议在使用字符串时能使用StringBuffer就不要用String,这样可以省不少开销;
3、尽量少用静态变量,因为静态变量是全局的,GC不会回收的;
4、避免集中创建对象尤其是大对象,JVM会突然需要大量内存,这时必然会触发GC优化系统内存环境;显示的声明数组空间,而且申请数量还极大。这是一个案例想定供大家警戒
使用jspsmartUpload作文件上传,运行过程中经常出现java.outofMemoryError的错误,检查之后发现问题:组件里的代码
m_totalBytes = m_request.getContentLength();m_binArray = new byte[m_totalBytes];问题原因是totalBytes这个变量得到的数极大,导致该数组分配了很多内存空间,而且该数组不能及时释放。解决办法只能换一种更合适的办法,至少是不会引发outofMemoryError的方式解决。参考:http://bbs.xml.org.cn/blog/more.asp?name=hongrui&id=3747
5、尽量运用对象池技术以提高系统性能;生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。
6、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用hashtable,vector创建一组对象容器,然后从容器中去取那些对象,而不用每次new之后又丢弃
7、一般都是发生在开启大型文件或跟数据库一次拿了太多的数据,造成Out Of Memory Error的状况,这时就大概要计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。
设计模式之Singleton(单态)
定义: Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。
在很多操作中,比如建立目录数据库连接都需要这样的单线程操作。
还有, singleton能够被状态化;这样,多个单态类在一起就可以作为一个状态仓库一样向外提供服务,比如,你要论坛中的帖子计数器,每次浏览一次需要计数,单态类能否保持住这个计数,并且能synchronize的安全自动加1,如果你要把这个数字永久保存到数据库,你可以在不修改单态接口的情况下方便的做到。
另外方面,Singleton也能够被无状态化。提供工具性质的功能,Singleton模式就为我们提供了这样实现的可能。使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。
我们常常看到工厂模式中类装入器(class loader)中也用Singleton模式实现的,因为被装入的类实际也属于资源。如何使用?
一般Singleton模式通常有几种形式: public class Singleton {
private Singleton(){}
//在自己内部定义自己一个实例,是不是很奇怪?
//注意这是private 只供内部调用
private static Singleton instance = new Singleton();
}
第二种形式: public class Singleton {
}
使用Singleton.getInstance()可以访问单态类。
上面第二中形式是lazy initialization,也就是说第一次调用时初始Singleton,以后就不用再生成了。
注意到lazy initialization形式中的synchronized,这个synchronized很重要,如果没有synchronized,那么使用getInstance()是有可能得到多个Singleton实例。关于lazy initialization的Singleton有很多涉及double-checked locking(DCL)的讨论,有兴趣者进一步研究。一般认为第一种形式要更加安全些。
使用Singleton注意事项:
有时在某些情况下,使用Singleton并不能达到Singleton的目的,如有多个Singleton对象同时被不同的类装入器装载;在EJB这样的分布式系统中使用也要注意这种情况,因为EJB是跨服务器,跨JVM的。
private static Singleton instance = null;public static synchronized Singleton getInstance(){
//这个方法比上面有所改进,不用每次都进行生成对象,只是第一次
//使用时生成实例,提高了效率!if(instance==null)
instance=new Singleton();return instance;} //这里提供了一个供外部访问本class的静态方法,可以直接访问
public static Singleton getInstance(){
return instance;
} 我们以SUN公司的宠物店源码(Pet Store 1.3.1)的ServiceLocator为例稍微分析一下:
在Pet Store中ServiceLocator有两种,一个是EJB目录下;一个是WEB目录下,我们检查这两个ServiceLocator会发现内容差不多,都是提供EJB的查询定位服务,可是为什么要分开呢?仔细研究对这两种ServiceLocator才发现区别:在WEB中的ServiceLocator的采取Singleton模式,ServiceLocator属于资源定位,理所当然应该使用Singleton模式。但是在EJB中,Singleton模式已经失去作用,所以ServiceLocator才分成两种,一种面向WEB服务的,一种是面向EJB服务的。
Singleton模式看起来简单,使用方法也很方便,但是真正用好,是非常不容易,需要对Java的类线程 内存等概念有相当的了解。
设计模式之Factory(2008-08-07 12:59:17)转载 标签:
杂谈
设计模式之Factory
定义:提供创建对象的接口.为何使用?
工厂模式是我们最常用的模式了,著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。
为什么工厂模式是如此常用?因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A()工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑实用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。我们以类Sample为例,如果我们要创建Sample的实例对象: Sample sample=new Sample();可是,实际情况是,通常我们都要在创建sample实例时做点初始化的工作,比如赋值查询数据库等。
首先,我们想到的是,可以使用Sample的构造函数,这样生成实例就写成: Sample sample=new Sample(参数);但是,如果创建sample实例时所做的初始化工作不是象赋值这样简单的事,可能是很长一段代码,如果也写入构造函数中,那你的代码很难看了(就需要Refactor重整)。
为什么说代码很难看,初学者可能没有这种感觉,我们分析如下,初始化工作如果是很长一段代码,说明要做的工作很多,将很多工作装入一个方法中,相当于将很多鸡蛋放在一个篮子里,是很危险的,这也是有背于Java面向对象的原则,面向对象的封装(Encapsulation)和分派(Delegation)告诉我们,尽量将长的代码分派“切割”成每段,将每段再“封装”起来(减少段和段之间偶合联系性),这样,就会将风险分散,以后如果需要修改,只要更改每段,不会再发生牵一动百的事情。
在本例中,首先,我们需要将创建实例的工作与使用实例的工作分开, 也就是说,让创建实例所需要的大量初始化工作从Sample的构造函数中分离出去。这时我们就需要Factory工厂模式来生成对象了,不能再用上面简单new Sample(参数)。还有,如果Sample有个继承如MySample, 按照面向接口编程,我们需要将Sample抽象成一个接口.现在Sample是接口,有两个子类MySample 和HisSample.我们要实例化他们时,如下: Sample mysample=new MySample();Sample hissample=new HisSample();随着项目的深入,Sample可能还会“生出很多儿子出来”, 那么我们要对这些儿子一个个实例化,更糟糕的是,可能还要对以前的代码进行修改:加入后来生出儿子的实例.这在传统程序中是无法避免的.但如果你一开始就有意识使用了工厂模式,这些麻烦就没有了.回调机制
(2008-08-13 23:55:39)转载 标签:
杂谈 什么是回调
软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。同步调用是三者当中最简单的,而回调又常常是异步调用的基础,因此,下面我们着重讨论回调机制在不同软件架构中的实现。
对于不同类型的语言(如结构化语言和对象语言)、平台(Win32、JDK)或构架(CORBA、DCOM、WebService),客户和服务的交互除了同步方式以外,都需要具备一定的异步通知机制,让服务方(或接口提供方)在某些情况下能够主动通知客户,而回调是实现异步的一个最简捷的途径。
对于一般的结构化语言,可以通过回调函数来实现回调。回调函数也是一个函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类成为回调类,回调类的对象成为回调对象。对于象C++或Object Pascal这些兼容了过程特性的对象语言,不仅提供了回调对象、回调方法等特性,也能兼容过程语言的回调函数机制。Windows平台的消息机制也可以看作是回调的一种应用,我们通过系统提供的接口注册消息处理函数(即回调函数),从而实现接收、处理消息的目的。由于Windows平台的API是用C语言来构建的,我们可以认为它也是回调函数的一个特例。
对于分布式组件代理体系CORBA,异步处理有多种方式,如回调、事件服务、通知服务等。事件服务和通知服务是CORBA用来处理异步消息的标准服务,他们主要负责消息的处理、派发、维护等工作。对一些简单的异步处理过程,我们可以通过回调机制来实现。
反射机制
(2008-08-14 00:04:19)转载 标签:
杂谈
JAVA反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。1.得到某个对象的属性 public Object getProperty(Object owner, String fieldName)throws Exception { 2 Class ownerClass = owner.getClass();3 4 Field field = ownerClass.getField(fieldName);5 6 Object property = field.get(owner);7 8 return property;9 } Class ownerClass = owner.getClass():得到该对象的Class。
Field field = ownerClass.getField(fieldName):通过Class得到类声明的属性。
Object property = field.get(owner):通过对象得到该属性的实例,如果这个属性是非公有的,这里会报IllegalAccessException。
2.得到某个类的静态属性
public Object getStaticProperty(String className, String fieldName)2 throws Exception { 3 Class ownerClass = Class.forName(className);4 5 Field field = ownerClass.getField(fieldName);6 7 Object property = field.get(ownerClass);8 9 return property;10 }
Class ownerClass = Class.forName(className):首先得到这个类的Class。
Field field = ownerClass.getField(fieldName):和上面一样,通过Class得到类声明的属性。
Object property = field.get(ownerClass):这里和上面有些不同,因为该属性是静态的,所以直接从类的Class里取。
3.执行某对象的方法
public Object invokeMethod(Object owner, String methodName, Object[] args)throws Exception { 2 3 Class ownerClass = owner.getClass();4 5 Class[] argsClass = new Class[args.length];6 7 for(int i = 0, j = args.length;i < j;i++){ 8 argsClass[i] = args[i].getClass();9 } 10 11 Method method = ownerClass.getMethod(methodName, argsClass);12 13 return method.invoke(owner, args);14 } Class owner_class = owner.getClass():首先还是必须得到这个对象的Class。
5~9行:配置参数的Class数组,作为寻找Method的条件。
Method method = ownerClass.getMethod(methodName, argsClass):通过Method名和参数的Class数组得到要执行的Method。
method.invoke(owner, args):执行该Method,invoke方法的参数是执行这个方法的对象,和参数数组。返回值是Object,也既是该方法的返回值。
4.执行某个类的静态方法
public Object invokeStaticMethod(String className, String methodName, 2 Object[] args)throws Exception { 3 Class ownerClass = Class.forName(className);4 5 Class[] argsClass = new Class[args.length];6 7 for(int i = 0, j = args.length;i < j;i++){ 8 argsClass[i] = args[i].getClass();9 } 10 11 Method method = ownerClass.getMethod(methodName, argsClass);12 13 return method.invoke(null, args);14 }
基本的原理和实例3相同,不同点是最后一行,invoke的一个参数是null,因为这是静态方法,不需要借助实例运行。
5.新建实例 1 2 public Object newInstance(String className, Object[] args)throws Exception { 3 Class newoneClass = Class.forName(className);4 5 Class[] argsClass = new Class[args.length];6 7 for(int i = 0, j = args.length;i < j;i++){ 8 argsClass[i] = args[i].getClass();9 } 10 11 Constructor cons = newoneClass.getConstructor(argsClass);12 13 return cons.newInstance(args);14 15 }
这里说的方法是执行带参数的构造函数来新建实例的方法。如果不需要参数,可以直接使用newoneClass.newInstance()来实现。
Class newoneClass = Class.forName(className):第一步,得到要构造的实例的Class。
第5~第9行:得到参数的Class数组。
Constructor cons = newoneClass.getConstructor(argsClass):得到构造子。
cons.newInstance(args):新建实例。
6.判断是否为某个类的实例 public boolean isInstance(Object obj, Class cls){ 2 return cls.isInstance(obj);3 }
7.得到数组中的某个元素 public Object getByArray(Object array, int index){ 2 return Array 在了解Oracle体系结构之前必须掌握以下两个基本的概念: 数据库和实例。
?/P>
一、数据库 ?/P> 数据库(database)是一个数据集合.?/P> 无论数据库是采用关系结构还是面向对象结构, oracle数据库都将 ?/P> 其数据存放在数据文件中.在其内部, 数据库结构数据对文件的逻辑 ?/P> 映射, 使不同的数据分开存储, 这些逻辑划分称为表空间.?/P> 表空间和文件介绍: ?/P> 1: 表空间 ?/P> 表空间(tablespace)是数据库的逻辑划分, 每个数据库至少有一个表空间,叫做系统表空间(system 表空间).一个表空间只能属于一个数据库。
?/P> 每个表空间由同一个磁盘上的一个或多个文件组成, 这些文件称为数据文件.?/P> 表空间的特性: ?/P>(1)控制数据库数据磁盘分配 ?/P>(2)限制用户在表空间中可以使用的磁盘空间大小 ?/P>(3)表空间具有 online, offline, readonly, readwrite属性 ?/P> 修改表空间的属性: ?/P> SQL> alter tablespace 表空间名称属性;查询表空间状态: SQL> select tablespace_name, status from dba_tablespaces;?/P> 注意: system, undo, temp表空间不能设为offline属性.?/P>(4)完成部分数据库的备份与恢复 ?/P>(5)表空间通过数据文件来扩大, 表空间的大小等于构成该表空间的所以数据文件的大小只和.?/P> 查询表空间与数据文件对应关系: ?/P> SQL> select tablespace_name, bytes, file_name from dba_data_files;?/P> 基于表空间的几个操作: ?/P>(1)查询用户缺省表空间: SQL> select username, default_tablespace from dba_users;(2)查询表与存储该表的表空间: SQL> select table_name, tablespace_name from user_tables;(3)修改用户缺省表空间: SQL> alter user username default tablespace tablespace_name;(4)将数据从一个表空间移动到另一个表空间: SQL> alter table table_name move tablespace tablespace_name;分享 2.数据文件 每个表空间由同一个磁盘上的一个或多个文件组成, 这些文件叫做数据文件(datafile),数据文件只能属于一个表空间.数据文件创建后可以改变大小.创建新的表空间需要创建新的数据文件.数据文件一旦加入到表空间中, 就不能从表空间中移走, 也不能与其他表空间发生联系。
数据库必须的三类文件是 data file, control file, redolog file.其他文件 prameter file,password file, archived log files并不是数据库必须的, 他们只是辅助数据库的。
查看数据库的物理文件组成:(1)查看数据文件: SQL> select * from v$datafile;(2)查看控制文件: SQL> select * from v$controlfile;(3)查看日志文件: SQL> select * from v$logfile;二: 实例
通俗的讲实例就是操作oracle数据库的一种手段.数据库实例也称作服务器, 是用来访问数据库文件集的存储结构及后台进程的集合.一个数据库可以被多个实例访问(称为真正的应用群集选项).决定实例的大小及组成的各种参数或者存储在名称init.ora的初始化文件中, 或者隐藏
在数据库内部的服务器参数文件中.通过spfile引用该文件, spfile存储在spfile.ora文件中.实例启动时读取初始化文件, 数据库系统管理员可以修改该文件, 对初始化文件的修改只有在下次启动时才有效。
Instance分为两部分:
1: memory structure(内存结构)
memory structure分为两部分:SGA(System Global Area)区是用于存储数据库信息的内存区,该信息为数据库进程所共享。它包含Oracle 服务器的数据和控制信息, 它是在Oracle 服务器所驻留的计算机的实际内存中得以分配,如果实际内存不够再往虚拟内存中写。
包括:.share pool.datafase buffer cache.redo log buffer.other structures PGA(Program Global Area)区包含单个服务器进程或单个后台进程的数据和控制信息,与几个进程共享的SGA 正相反PGA 是只被一个进程使用的区域,PGA 在创建进程时分配在终止进程时回收。
2: background process(后台进程)包括:
.PMON 负责在一个Oracle 进程失败时清理资源
.SMON 检查数据库的一致性如有必要还会在数据库打开时启动数据库的恢复
.DBWR 负责将更改的数据从数据库缓冲区高速缓存写入数据文件.LGWR 将重做日志缓冲区中的更改写入在线重做日志文件
.CKPT 负责在每当缓冲区高速缓存中的更改永久地记录在数据库中时,更新控制文件和数据文件中的数据库状态信息。
全面解析JDBC
综述:Java数据库连接体系结构是用于Java应用程序连接数据库的标准方法。JDBC对Java程序员而言是API,对实现与数据库连接的服务提供商而言是接口模型。作为API,JDBC为程序开发提供标准的接口,并为数据库厂商及第三方中间件厂商实现与数据库的连接提供了标准方法。JDBC使用已有的SQL标准并支持与其它数据库连接标准,如ODBC之间的桥接。JDBC实现了所有这些面向标准的目标并且具有简单、严格类型定义且高性能实现的接口。
如何选择合适的JDBC产品?
有关JDBC最新的信息,有兴趣的读者可以查阅JDBC的官方网站--即JavaSoft的主页,其URL为:http://Java.sun.com/products/jdbc
1.JavaSoft框架
JavaSoft提供三种JDBC产品组件,它们是Java开发工具包(JDK)的组成部份:JDBC驱动程序管理器、JDBC驱动程序测试工具包和JDBC-ODBC桥。
JDBC驱动程序管理器是JDBC体系结构的支柱。它实际上很小,也很简单;其主要作用是把Java应用程序连接到正确的JDBC驱动程序上,然后即退出。
JDBC驱动程序测试工具包为使JDBC驱动程序运行您的程序提供一定的可信度。只有通过JDBC驱动程序测试的驱动程序才被认为是符合JDBC标准TM的。
JDBC-ODBC桥使ODBC驱动程序可被用作JDBC驱动程序。它的实现为JDBC的快速发展提供了一条途径,其长远目标提供一种访问某些不常见的DBMS(如果对这些不常见的DBMS未实现JDBC)的方法。
2.JDBC驱动程序的类型
目前比较常见的JDBC驱动程序可分为以下四个种类:
(1)JDBC-ODBC桥加ODBC驱动程序
JavaSoft桥产品利用ODBC驱动程序提供JDBC访问。注意,必须将ODBC二进制代码(许多情况下还包括数据库客户机代码)加载到使用该驱动程序的每个客户机上。因此,这种类型的驱动程序最适合于企业网(这种网络上客户机的安装不是主要问题),或者是用Java编写的三层结构的应用程序服务器代码。
(2)本地API
这种类型的驱动程序把客户机API上的JDBC调用转换为Oracle、Sybase、Informix、DB2或其它DBMS的调用。注意,象桥驱动程序一样,这种类型的驱动程序要求将某些二进制代码加载到每台客户机上。
(3)JDBC网络纯Java驱动程序
这种驱动程序将JDBC转换为与DBMS无关的网络协议,之后这种协议又被某个服务器转换为一种DBMS协议。这种网络服务器中间件能够将它的纯Java客户机连接到多种不同的数据库上。所用的具体协议取决于提供者。通常,这是最为灵活的JDBC驱动程序。有可能所有这种解决方案的提供者都提供适合于Intranet用的产品。为了使这些产品也支持Internet访问,它们必须处理Web所提出的安全性、通过防火墙的访问等方面的额外要求。几家提供者正将JDBC驱动程序加到他们现有的数据库中间件产品中。
(4)本地协议纯Java驱动程序
这种类型的驱动程序将JDBC调用直接转换为DBMS所使用的网络协议。这将允许从客户机机器上直接调用DBMS服务器,是Intranet访问的一个很实用的解决方法。由于许多这样的协议都是专用的,因此数据库提供者自己将是主要来源,有几家提供者已在着手做这件事了。
据专家预计第(3)、(4)类驱动程序将成为从JDBC访问数据库的首方法。第(1)、(2)类驱动程序在直接的纯Java驱动程序还没有上市前会作为过渡方案来使用。对第(1)、(2)类驱动程序可能会有一些变种,这些变种要求有连接器,但通常这些是更加不可取的解决方案。第(3)、(4)类驱动程序提供了Java的所有优点,包括自动安装(例如,通过使用JDBC驱动程序的appletapplet来下载该驱动程序)。
3.JDBC驱动程序的获取
目前已有几十个(1)类的驱动程序,即可与Javasoft桥联合使用的ODBC驱动程序的驱动程序。有大约十多个属于种类(2)的驱动程序是以DBMS的本地API为基础编写的。只有几个属于种类(3)的驱动程序,其首批提供者是SCO、OpenHorizon、Visigenic和WebLogic。此外,JavaSoft和数据库连接的领先提供者Intersolv还合作研制了JDBC-ODBC桥和JDBC驱动程序测试工具包。
如何建立JDBC连接?
Connection 对象代表与数据库的连接。连接过程包括所执行的 SQL 语句和在该连接上所返回的结果。一个应用程序可与单个数据库有一个或多个连接,或者可与许多数据库有连接。
1.打开连接
与数据库建立连接的标准方法是调用DriverManager.getConnection方法。该方法接受含有某个URL的字符串。DriverManager类(即所谓的JDBC管理层)将尝试找到可与那个URL所代表的数据库进行连接的驱动程序。DriverManager类存有已注册的Driver类的清单。当调用方法getConnection时,它将检查清单中的每个驱动程序,直到找到可与URL中指定的数据库进行连接的驱动程序为止。Driver的方法connect使用这个URL来建立实际的连接。
用户可绕过JDBC管理层直接调用Driver方法。这在以下特殊情况下将很有用:当两个驱动器可同时连接到数据库中,而用户需要明确地选用其中特定的驱动器。但一般情况下,让DriverManager类处理打开连接这种事将更为简单。
下述代码显示如何打开一个与位于URL“jdbc:odbc:wombat”的数据库的连接。所用的用户标识符为“freely”,口令为“ec”:
String url = “jdbc:odbc:wombat”;
Connection con = DriverManager.getConnection(url,“freely”,“ec”);
2.一般用法的URL
由于URL常引起混淆,我们将先对一般URL作简单说明,然后再讨论JDBCURL。URL(统一资源定位符)提供在Internet上定位资源所需的信息。可将它想象为一个地址。URL的第一部份指定了访问信息所用的协议,后面总是跟着冒号。常用的协议有“ftp”(代表“文件传输协议”)和“http”(代表“超文本传输协议”)。如果协议是“file”,表示资源是在某个本地文件系统上而非在Internet上(下例用于表示我们所描述的部分;它并非URL的组成部分)。
ftp://Javasoft.com/docs/JDK-1_apidocs.zip
http://Java.sun.com/products/jdk/CurrentRelease
file:/home/haroldw/docs/books/tutorial/summary.html
URL的其余部份(冒号后面的)给出了数据资源所处位置的有关信息。如果协议是file,则URL的其余部份是文件的路径。对于ftp和http协议,URL的其余部份标识了主机并可选地给出某个更详尽的地址路径。例如,以下是JavaSoft主页的URL。该URL只标识了主机:http://Java.sun.com。从该主页开始浏览,就可以进到许多其它的网页中,其中之一就是JDBC主页。JDBC主页的URL更为具体,它具体表示为: http://Java.sun.com/products/jdbc
3.JDBC URL
JDBC URL提供了一种标识数据库的方法,可以使相应的驱动程序能识别该数据库并与之建立连接。实际上,驱动程序编程员将决定用什么JDBC URL来标识特定的驱动程序。用户不必关心如何来形成JDBC URL;他们只须使用与所用的驱动程序一起提供的URL即可。JDBC的作用是提供某些约定,驱动程序编程员在构造他们的JDBC URL时应该遵循这些约定。
由于JDBC URL要与各种不同的驱动程序一起使用,因此这些约定应非常灵活。首先,它们应允许不同的驱动程序使用不同的方案来命名数据库。例如,odbc子协议允许(但并不是要求)URL含有属性值。
其次,JDBC URL应允许驱动程序编程员将一切所需的信息编入其中。这样就可以让要与给定数据库对话的applet打开数据库连接,而无须要求用户去做任何系统管理工作。
最后,JDBC URL应允许某种程度的间接性。也就是说,JDBC URL可指向逻辑主机或数据库名,而这种逻辑主机或数据库名将由网络命名系统动态地转换为实际的名称。这可以使系统管理员不必将特定主机声明为JDBC名称的一部份。网络命名服务(例如DNS、NIS和DCE)有多种,而对于使用哪种命名服务并无限制。
JDBC URL的标准语法如下所示。它由三部分组成,各部分间用冒号分隔: jdbc:<子协遥荆海甲用?疲?br>
JDBC URL的三个部分可分解如下:
(1)jdbc协议:JDBC URL中的协议总是jdbc。
(2)<子协议>:驱动程序名或数据库连接机制(这种机制可由一个或多个驱动程序支持)的名称。子协议名的典型示例是“odbc”,该名称是为用于指定ODBC风格的数据资源名称的URL专门保留的。例如,为了通过JDBC-ODBC桥来访问某个数据库,可以用如下所示的URL:jdbc:odbc:book。本例中,子协议为“odbc”,子名称“book”是本地ODBC数据资源。如果要用网络命名服务(这样JDBC URL中的数据库名称不必是实际名称),则命名服务可以作为子协议。例如,可用如下所示的URL:jdbc:dcenaming:accounts。本例中,该URL指定了本地DCE命名服务应该将数据库名称“accounts”解析为更为具体的可用于连接真实数据库的名称。
(3)<子名称>:种标识数据库的方法。子名称可以依不同的子协议而变化。它还可以有子名称的子名称(含有驱动程序编程员所选的任何内部语法)。使用子名称的目的是为定位数据库提供足够的信息。前例中,因为ODBC将提供其余部份的信息,因此用“book”就已足够。然而,位于远程服务器上的数据库需要更多的信息。例如,如果数据库是通过Internet来访问的,则在JDBC URL中应将网络地址作为子名称的一部份包括进去,且必须遵循如下所示的标准URL命名约定://主机名:端口/子协议。
假设“dbnet”是个用于将某个主机连接到Internet上的协议,则JDBC URL应为:jdbc:dbnet://wombat:356/fred。
4.“odbc”子协议
子协议odbc是一种特殊情况。它是为用于指定ODBC风格的数据资源名称的URL而保留的,并具有下列特性:允许在子名称(数据资源名称)后面指定任意多个属性值。odbc子协议的完整语法为:
jdbc:odbc:<数据资源名称>[;<属性名>=<属性值>],因此,以下都是合法的jdbc:odbc名称:
jdbc:odbc:qeor7
jdbc:odbc:wombat
jdbc:odbc:wombat;CacheSize=20;ExtensionCase=LOWER
jdbc:odbc:qeora;UID=kgh;PWD=fooey
5.注册子协议
驱动程序编程员可保留某个名称以将之用作JDBC URL的子协议名。当DriverManager类将此名称加到已注册的驱动程序清单中时,为之保留该名称的驱动程序应能识别该名称并与它所标识的数据库建立连接。例如,odbc是为JDBC-ODBC桥而保留的。假设有个Miracle公司,它可能会将“miracle”注册为连接到其Miracle DBMS上的JDBC驱动程序的子协议,从而使其他人都无法使用这个名称。
JavaSoft目前作为非正式代理负责注册JDBC子协议名称。要注册某个子协议名称,请发送电子邮件到下述地址:jdbc@wombat.eng.sun.com。
6.发送SQL语句
连接一旦建立,就可用来向它所涉及的数据库传送SQL语句。JDBC对可被发送的SQL语句类型不加任何限制。这就提供了很大的灵活性,即允许使用特定的数据库语句或甚至于非SQL语句。然而,它要求用户自己负责确保所涉及的数据库可以处理所发送的SQL语句,否则将自食其果。例如,如果某个应用程序试图向不支持储存程序的DBMS发送储存程序调用,就会失败并将抛出异常。JDBC要求驱动程序应至少能提供ANSI SQL-2 Entry Level功能才可算是符合JDBC标准TM的。这意味着用户至少可信赖这一标准级别的功能。
JDBC提供了三个类,用于向数据库发送SQL语句。Connection接口中的三个方法可用于创建这些类的实例。下面列出这些类及其创建方法:
(1)Statement:由方法createStatement所创建。Statement对象用于发送简单的SQL语句。
(2)PreparedStatement:由方法prepareStatement所创建。PreparedStatement对象用于发送带有一个或多个输入参数(IN参数)的SQL语句。PreparedStatement拥有一组方法,用于设置IN参数的值。执行语句时,这些IN参数将被送到数据库中。PreparedStatement的实例扩展了Statement,因此它们都包括了Statement的方法。PreparedStatement对象有可能比Statement对象的效率更高,因为它已被预编译过并存放在那以供将来使用。
(3)CallableStatement:由方法prepareCall所创建。CallableStatement对象用于执行SQL储存程序─一组可通过名称来调用(就象函数的调用那样)的SQL语句。CallableStatement对象从PreparedStatement中继承了用于处理IN参数的方法,而且还增加了用于处理OUT参数和INOUT参数的方法。
不过通常来说createStatement方法用于简单的SQL语句(不带参数)、prepareStatement方法用于带一个或多个IN参数的SQL语句或经常被执行的简单SQL语句,而prepareCall方法用于调用已储存过程。
7.事务
事务由一个或多个这样的语句组成:这些语句已被执行、完成并被提交或还原。当调用方法commit或rollback时,当前事务即告就结束,另一个事务随即开始。缺省情况下,新连接将处于自动提交模式。也就是说,当执行完语句后,将自动对那个语句调用commit方法。这种情况下,由于每个语句都是被单独提交的,因此一个事务只由一个语句组成。如果禁用自动提交模式,事务将要等到commit或rollback方法被显式调用时才结束,因此它将包括上一次调用commit或rollback方法以来所有执行过的语句。对于第二种情况,事务中的所有语句将作为组来提交或还原。
方法commit使SQL语句对数据库所做的任何更改成为永久性的,它还将释放事务持有的全部锁。而方法rollback将弃去那些更改。有时用户在另一个更改生效前不想让此更改生效。这可通过禁用自动提交并将两个更新组合在一个事务中来达到。如果两个更新都是成功,则调用commit方法,从而使两个更新结果成为永久性的;如果其中之一或两个更新都失败了,则调用rollback方法,以将值恢复为进行更新之前的值。
大多数JDBC驱动程序都支持事务。事实上,符合JDBC的驱动程序必须支持事务。DatabaseMetaData给出的信息描述DBMS所提供的事务支持水平。
8.事务隔离级别
如果DBMS支持事务处理,它必须有某种途径来管理两个事务同时对一个数据库进行操作时可能发生的冲突。用户可指定事务隔离级别,以指明DBMS应该花多大精力来解决潜在冲突。例如,当事务更改了某个值而第二个事务却在该更改被提交或还原前读取该值时该怎么办。
假设第一个事务被还原后,第二个事务所读取的更改值将是无效的,那么是否可允许这种冲突?JDBC用户可用以下代码来指示DBMS允许在值被提交前读取该值(“dirty读取”),其中con是当前连接:
con.setTransactionIsolation(TRANSACTION_READ_UNCOMMITTED);
事务隔离级别越高,为避免冲突所花的精力也就越多。Connection接口定义了五级,其中最低级别指定了根本就不支持事务,而最高级别则指定当事务在对某个数据库进行操作时,任何其它事务不得对那个事务正在读取的数据进行任何更改。通常,隔离级别越高,应用程序执行的速度也就越慢(由于用于锁定的资源耗费增加了,而用户间的并发操作减少了)。在决定采用什么隔离级别时,开发人员必须在性能需求和数据一致性需求之间进行权衡。当然,实际所能支持的级别取决于所涉及的DBMS的功能。
当创建Connection对象时,其事务隔离级别取决于驱动程序,但通常是所涉及的数据库的缺省值。用户可通过调用setIsolationLevel方法来更改事务隔离级别。新的级别将在该连接过程的剩余时间内生效。要想只改变一个事务的事务隔离级别,必须在该事务开始前进行设置,并在该事务结束后进行复位。我们不提倡在事务的中途对事务隔离级别进行更改,因为这将立即触发commit方法的调用,使在此之前所作的任何更改变成永久性的。JDBC驱动管理内幕是怎么样的?
DriverManager 类是 JDBC 的管理层,作用于用户和驱动程序之间。它跟踪可用的驱动程序,并在数据库和相应驱动程序之间建立连接。另外,DriverManager类也处理诸如驱动程序登录时间限制及登录和跟踪消息的显示等事务。
对于简单的应用程序,一般程序员需要在此类中直接使用的唯一方法是DriverManager.getConnection。正如名称所示,该方法将建立与数据库的连接。JDBC允许用户调用DriverManager的方法getDriver、getDrivers和registerDriver及Driver的方法connect。但多数情况下,让DriverManager类管理建立连接的细节为上策。
1.跟踪可用驱动程序
DriverManager类包含一列Driver类,它们已通过调用方法DriverManager.registerDriver对自己进行了注册。所有Driver类都必须包含有一个静态部分。它创建该类的实例,然后在加载该实例时DriverManager类进行注册。这样,用户正常情况下将不会直接调用DriverManager.registerDriver;而是在加载驱动程序时由驱动程序自动调用。加载Driver类,然后自动在DriverManager中注册的方式有两种:
(1)调用方法Class.forName
这将显式地加载驱动程序类。由于这与外部设置无关,因此推荐使用这种加载驱动程序的方法。以下代码加载类acme.db.Driver:Class.forName(“acme.db.Driver”)。
如果将acme.db.Driver编写为加载时创建实例,并调用以该实例为参数的DriverManager.registerDriver(本该如此),则它在DriverManager的驱动程序列表中,并可用于创建连接。
(2)将驱动程序添加到Java.lang.System的属性jdbc.drivers中
这是一个由DriverManager类加载的驱动程序类名的列表,由冒号分隔:初始化DriverManager类时,它搜索系统属性jdbc.drivers,如果用户已输入了一个或多个驱动程序,则DriverManager类将试图加载它们。以下代码说明程序员如何在~/.hotJava/properties中输入三个驱动程序类(启动时,HotJava将把它加载到系统属性列表中):
jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.test.ourDriver;
对DriverManager方法的第一次调用将自动加载这些驱动程序类。注意:加载驱动程序的第二种方法需要持久的预设环境。如果对这一点不能保证,则调用方法Class.forName显式地加载每个驱动程序就显得更为安全。这也是引入特定驱动程序的方法,因为一旦DriverManager类被初始化,它将不再检查jdbc.drivers属性列表。
在以上两种情况中,新加载的Driver类都要通过调用DriverManager.registerDriver类进行自我注册。如上所述,加载类时将自动执行这一过程。
由于安全方面的原因,JDBC管理层将跟踪哪个类加载器提供哪个驱动程序。这样,当DriverManager类打开连接时,它仅使用本地文件系统或与发出连接请求的代码相同的类加载器提供的驱动程序。
2.建立连接
加载Driver类并在DriverManager类中注册后,它们即可用来与数据库建立连接。当调用DriverManager.getConnection方法发出连接请求时,DriverManager将检查每个驱动程序,查看它是否可以建立连接。
有时可能有多个JDBC驱动程序可以与给定的URL连接。例如,与给定远程数据库连接时,可以使用JDBC-ODBC桥驱动程序、JDBC到通用网络协议驱动程序或数据库厂商提供的驱动程序。在这种情况下测试驱动程序的顺序至关重要,因为DriverManager将使用它所找到的第一个可以成功连接到给定URL的驱动程序。
首先DriverManager试图按注册的顺序使用每个驱动程序(jdbc.drivers中列出的驱动程序总是先注册)。它将跳过代码不可信任的驱动程序,除非加载它们的源与试图打开连接的代码的源相同。它通过轮流在每个驱动程序上调用方法Driver.connect,并向它们传递用户开始传递给方法DriverManager.getConnection的URL来对驱动程序进行测试,然后连接第一个认出该URL的驱动程序。这种方法初看起来效率不高,但由于不可能同时加载数十个驱动程序,因此每次连接实际只需几个过程调用和字符串比较。
以下代码是通常情况下用驱动程序(例如JDBC-ODBC桥驱动程序)建立连接所需所有步骤的示例:
Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);//加载驱动程序 String url = “jdbc:odbc:fred”;
DriverManager.getConnection(url,“userID”,“passwd”); 如何利用JDBC发送SQL语句?
Statement对象用于将SQL语句发送到数据库中。实际上有三种Statement对象,它们都作为在给定连接上执行SQL语句的包容器:Statement、PreparedStatement(它从Statement继承而来)和CallableStatement(它从PreparedStatement继承而来)。它们都专用于发送特定类型的SQL语句:Statement对象用于执行不带参数的简单SQL语句;PreparedStatement对象用于执行带或不带IN参数的预编译SQL语句;CallableStatement对象用于执行对数据库已存储过程的调用。
Statement接口提供了执行语句和获取结果的基本方法;PreparedStatement接口添加了处理IN参数的方法;而CallableStatement添加了处理OUT参数的方法。
1.创建Statement对象
建立了到特定数据库的连接之后,就可用该连接发送SQL语句。Statement对象用Connection的方法createStatement创建,如下列代码段中所示:
Connection con = DriverManager.getConnection(url,“sunny”,“"); Statement stmt = con.createStatement();
为了执行Statement对象,被发送到数据库的SQL语句将被作为参数提供给Statement的方法:
ResultSet rs = stmt.executeQuery(”SELECT a,b,c FROM Table2“);
2.使用Statement对象执行语句
Statement接口提供了三种执行SQL语句的方法:executeQuery、executeUpdate和execute。使用哪一个方法由SQL语句所产生的内容决定。
方法executeQuery用于产生单个结果集的语句,例如SELECT语句。方法executeUpdate用于执行INSERT、UPDATE或DELETE语句以及SQL DDL(数据定义语言)语句,例如CREATE TABLE和DROP TABLE。INSERT、UPDATE或DELETE语句的效果是修改表中零行或多行中的一列或多列。executeUpdate的返回值是一个整数,指示受影响的行数(即更新计数)。对于CREATE TABLE或DROP TABLE等不操作行的语句,executeUpdate的返回值总为零。
执行语句的所有方法都将关闭所调用的Statement对象的当前打开结果集(如果存在)。这意味着在重新执行Statement对象之前,需要完成对当前ResultSet对象的处理。应注意,继承了Statement接口中所有方法的PreparedStatement接口都有自己的executeQuery、executeUpdate和execute方法。Statement对象本身不包含SQL语句,因而必须给Statement.execute方法提供SQL语句作为参数。PreparedStatement对象并不需要SQL语句作为参数提供给这些方法,因为它们已经包含预编译SQL语句。
CallableStatement对象继承这些方法的PreparedStatement形式。对于这些方法的PreparedStatement或CallableStatement版本,使用查询参数将抛出SQLException。
3.语句完成
当连接处于自动提交模式时,其中所执行的语句在完成时将自动提交或还原。语句在已执行且所有结果返回时,即认为已完成。对于返回一个结果集的executeQuery方法,在检索完ResultSet对象的所有行时该语句完成。对于方法executeUpdate,当它执行时语句即完成。但在少数调用方法execute的情况中,在检索所有结果集或它生成的更新计数之后语句才完成。
有些DBMS将已存储过程中的每条语句视为独立的语句;而另外一些则将整个过程视为一个复合语句。在启用自动提交时,这种差别就变得非常重要,因为它影响什么时候调用commit方法。在前一种情况中,每条语句单独提交;在后一种情况中,所有语句同时提交。
4.关闭Statement对象
Statement对象将由Java垃圾收集程序自动关闭。而作为一种好的编程风格,应在不需要Statement对象时显式地关闭它们。这将立即释放DBMS资源,有助于避免潜在的内存问题。
5.使用方法execute
execute方法应该仅在语句能返回多个ResultSet对象、多个更新计数或ResultSet对象与更新计数的组合时使用。当执行某个已存储过程或动态执行未知SQL字符串(即应用程序程序员在编译时未知)时,有可能出现多个结果的情况,尽管这种情况很少见。例如,用户可能执行一个已存储过程,并且该已存储过程可执行更新,然后执行选择,再进行更新,再进行选择,等等。通常使用已存储过程的人应知道它所返回的内容。
因为方法execute处理非常规情况,所以获取其结果需要一些特殊处理并不足为怪。例如,假定已知某个过程返回两个结果集,则在使用方法execute执行该过程后,必须调用方法getResultSet获得第一个结果集,然后调用适当的getXXX方法获取其中的值。要获得第二个结果集,需要先调用getMoreResults方法,然后再调用getResultSet方法。如果已知某个过程返回两个更新计数,则首先调用方法getUpdateCount,然后调用getMoreResults,并再次调用getUpdateCount。
对于不知道返回内容,则情况更为复杂。如果结果是ResultSet对象,则方法execute返回true;如果结果是Javaint,则返回false。如果返回int,则意味着结果是更新计数或执行的语句是DL命令。在调用方法execute之后要做的第一件事情是调用getResultSet或getUpdateCount。调用方法getResultSet可以获得两个或多个ResultSet对象中第一个对象;或调用方法getUpdateCount可以获得两个或多个更新计数中第一个更新计数的内容。
当SQL语句的结果不是结果集时,则方法getResultSet将返回null。这可能意味着结果是一个更新计数或没有其它结果。在这种情况下,判断null真正含义的唯一方法是调用方法getUpdateCount,它将返回一个整数。这个整数为调用语句所影响的行数;如果为-1则表示结果是结果集或没有结果。如果方法getResultSet已返回null(表示结果不是ResultSet对象),则返回值-1表示没有其它结果。也就是说,当下列条件为真时表示没有结果(或没有其它结果):
((stmt.getResultSet()==null)&&(stmt.getUpdateCount()==-1))
如果已经调用方法getResultSet并处理了它返回的ResultSet对象,则有必要调用方法getMoreResults以确定是否有其它结果集或更新计数。如果getMoreResults返回true,则需要再次调用getResultSet来检索下一个结果集。如上所述,如果getResultSet返回null,则需要调用getUpdateCount来检查null是表示结果为更新计数还是表示没有其它结果。
当getMoreResults返回false时,它表示该SQL语句返回一个更新计数或没有其它结果。因此需要调用方法getUpdateCount来检查它是哪一种情况。在这种情况下,当下列条件为真时表示没有其它结果:
通过JDBC连接oracle数据库的十大技巧
(2008-08-27 22:42:57)转载 标签:
杂谈
Java数据库连接(JDBC)API是一系列能够让Java编程人员访问数据库的接口,各个开发商的接口并不完全相同。在使用多年的Oracle公司的JDBC后,我积累了许多技巧,这些技巧能够使我们更好地发挥系统的性能和实现更多的功能。
1、在客户端软件开发中使用Thin驱动程序
在开发Java软件方面,Oracle的数据库提供了四种类型的驱动程序,二种用于应用软件、applets、servlets等客户端软件,另外二种用于数据库中的Java存储过程等服务器端软件。在客户机端软件的开发中,我们可以选择OCI驱动程序或Thin驱动程序。OCI驱动程序利用Java本地化接口(JNI),通过Oracle客户端软件与数据库进行通讯。Thin驱动程序是纯Java驱动程序,它直接与数据库进行通讯。为了获得最高的性能,Oracle建议在客户端软件的开发中使用OCI驱动程序,这似乎是正确的。但我建议使用Thin驱动程序,因为通过多次测试发现,在通常情况下,Thin驱动程序的性能都超过了OCI驱动程序。
2、关闭自动提交功能,提高系统性能
在第一次建立与数据库的连接时,在缺省情况下,连接是在自动提交模式下的。为了获得更好的性能,可以通过调用带布尔值false参数的Connection类的setAutoCommit()方法关闭自动提交功能,如下所示:
conn.setAutoCommit(false);
值得注意的是,一旦关闭了自动提交功能,我们就需要通过调用Connection类的commit()和rollback()方法来人工的方式对事务进行管理。
3、在动态SQL或有时间限制的命令中使用Statement对象
在执行SQL命令时,我们有二种选择:可以使用PreparedStatement对象,也可以使用Statement对象。无论多少次地使用同一个SQL命令,PreparedStatement都只对它解析和编译一次。当使用Statement对象时,每次执行一个SQL命令时,都会对它进行解析和编译。这可能会使你认为,使用PreparedStatement对象比使用Statement对象的速度更快。然而,我进行的测试表明,在客户端软件中,情况并非如此。因此,在有时间限制的SQL操作中,除非成批地处理SQL命令,我们应当考虑使用Statement对象。
此外,使用Statement对象也使得编写动态SQL命令更加简单,因为我们可以将字符串连接在一起,建立一个有效的SQL命令。因此,我认为,Statement对象可以使动态SQL命令的创建和执行变得更加简单。
4、利用helper函数对动态SQL命令进行格式化
在创建使用Statement对象执行的动态SQL命令时,我们需要处理一些格式化方面的问题。例如,如果我们想创建一个将名字O'Reilly插入表中的SQL命令,则必须使用二个相连的“''”号替换O'Reilly中的“'”号。完成这些工作的最好的方法是创建一个完成替换操作的helper方法,然后在连接字符串心服用公式表达一个SQL命令时,使用创建的helper方法。与此类似的是,我们可以让helper方法接受一个Date型的值,然后让它输出基于Oracle的to_date()函数的字符串表达式。
5、利用PreparedStatement对象提高数据库的总体效率
在使用PreparedStatement对象执行SQL命令时,命令被数据库进行解析和编译,然后被放到命令缓冲区。然后,每当执行同一个PreparedStatement对象时,它就会被再解析一次,但不会被再次编译。在缓冲区中可以发现预编译的命令,并且可以重新使用。在有大量用户的企业级应用软件中,经常会重复执行相同的SQL命令,使用PreparedStatement对象带来的编译次数的减少能够提高数据库的总体性能。如果不是在客户端创建、预备、执行PreparedStatement任务需要的时间长于Statement任务,我会建议在除动态SQL命令之外的所有情况下使用PreparedStatement对象。
6、在成批处理重复的插入或更新操作中使用PreparedStatement对象
如果成批地处理插入和更新操作,就能够显著地减少它们所需要的时间。Oracle提供的Statement和 CallableStatement并不真正地支持批处理,只有PreparedStatement对象才真正地支持批处理。我们可以使用addBatch()和executeBatch()方法选择标准的JDBC批处理,或者通过利用PreparedStatement对象的setExecuteBatch()方法和标准的executeUpdate()方法选择速度更快的Oracle专有的方法。要使用Oracle专有的批处理机制,可以以如下所示的方式调用setExecuteBatch():
PreparedStatement pstmt3D null;try {((OraclePreparedStatement)pstmt).setExecuteBatch(30);...pstmt.executeUpdate();}
调用setExecuteBatch()时指定的值是一个上限,当达到该值时,就会自动地引发SQL命令执行,标准的executeUpdate()方法就会被作为批处理送到数据库中。我们可以通过调用PreparedStatement类的sendBatch()方法随时传输批处理任务。
7、使用Oracle locator方法插入、更新大对象(LOB)
Oracle的PreparedStatement类不完全支持BLOB和CLOB等大对象的处理,尤其是Thin驱动程序不支持利用PreparedStatement对象的setObject()和setBinaryStream()方法设置BLOB的值,也不支持利用setCharacterStream()方法设置CLOB的值。只有locator本身中的方法才能够从数据库中获取LOB类型的值。可以使用PreparedStatement对象插入或更新LOB,但需要使用locator才能获取LOB的值。由于存在这二个问题,因此,我建议使用locator的方法来插入、更新或获取LOB的值。
8、使用SQL92语法调用存储过程
在调用存储过程时,我们可以使用SQL92或Oracle PL/SQL,由于使用Oracle PL/SQL并没有什么实际的好处,而且会给以后维护你的应用程序的开发人员带来麻烦,因此,我建议在调用存储过程时使用SQL92。
9、使用Object SQL将对象模式转移到数据库中
既然可以将Oracle的数据库作为一种面向对象的数据库来使用,就可以考虑将应用程序中的面向对象模式转到数据库中。目前的方法是创建Java bean作为伪装的数据库对象,将它们的属性映射到关系表中,然后在这些bean中添加方法。尽管这样作在Java中没有什么问题,但由于操作都是在数据库之外进行的,因此其他访问数据库的应用软件无法利用对象模式。如果利用Oracle的面向对象的技术,可以通过创建一个新的数据库对象类型在数据库中模仿其数据和操作,然后使用JPublisher等工具生成自己的Java bean类。如果使用这种方式,不但Java应用程序可以使用应用软件的对象模式,其他需要共享你的应用中的数据和操作的应用软件也可以使用应用软件中的对象模式。
10、利用SQL完成数据库内的操作
我要向大家介绍的最重要的经验是充分利用SQL的面向集合的方法来解决数据库处理需求,而不是使用Java等过程化的编程语言。
如果编程人员要在一个表中查找许多行,结果中的每个行都会查找其他表中的数据,最后,编程人员创建了独立的UPDATE命令来成批地更新第一个表中的数据。与此类似的任务可以通过在set子句中使用多列子查询而在一个UPDATE命令中完成。当能够在单一的SQL命令中完成任务,何必要让数据在网上流来流去的?我建议用户认真学习如何最大限度地发挥SQL的功能
1、这是oracle的规定,不能对执行触发器的表进行操作。可以对new.xxx进行操作啊,对于oracle行级触发器(for each row),不能对本表做任何操作,包括读取
原则:
在before insert触发器中,可以实现对本表的访问; 在after insert触发器中,不能实现对本表的访问;
在before/after update/delete触发器中,都不能实现对本表的访问
其实原因很简单,就是为了防止脏读
2、写oracle行级触发器时,不能操作本表,报”表 *** 发生了变化,触发器/函数不能读"的错误的解决办法
原因已经很明显了就是行级的触发器代码中不能操作该表,包括select,是挺郁闷的
当然解决方法就是要根据原因了,正因为限定了行级触发器的操作,只能选择表级的触发器了,但是在表级的触发器又不能获得:new和:old的值,那就只能采取两种触发器并用的方法了,并且还要包或者临时表加以辅助.首先在行级触发器中将所需的,:new或者:old的值,写到包或者临时表中
然后在表级触发器中处理包或者临时表中已经写入的数据,操作成功后可以按照需求再删除临时表的数据.3、ORACLE 触发器
ORACLE产生数据库触发器的语法为:
create [or replace] trigger 触发器名 触发时间 触发事件
on 表名
[for each row]
pl/sql 语句
其中:
触发器名:触发器对象的名称。由于触发器是数据库自动执行 的,因此该名称只是一个名称,没有实质的用途。
触发时间:指明触发器何时执行,该值可取:
before---表示在数据库动作之前触发器执行;
after---表示在数据库动作之后出发器执行。
触发事件:指明哪些数据库动作会触发此触发器:
insert:数据库插入会触发此触发器;
update:数据库修改会触发此触发器;
delete:数据库删除会触发此触发器。
表 名:数据库触发器所在的表。
for each row:对表的每一行触发器执行一次。如果没有这一
选项,则只对整个表执行一次。
举例:下面的触发器在更新表auths之前触发,目的是不允许在
周末修改表:
create trigger auth_secure
before insert or update or delete //对整表更新前触发
on auths
begin
if(to_char(sysdate,'DY')='SUN'
RAISE_APPLICATION_ERROR(-20600,'不能在周末修改表auths');
end if;end 分析函数
(2008-09-04 00:11:55)转载 标签:
杂谈 分析函数
1、ROW_NUMBER(为有序组中的每一行返回一个唯一的排序值,序号由order by子句指定,从1开始。)例80:对所有员工的工资进行排名,即使工资相同,其排名也不能相同。select ename,job,deptno,sal, row_number()over(order by sal desc)as sal_rank from emp;例81:对员工的工资按部门进行排名,即使工资相同,排名也不同。select ename,job,deptno,sal, row_number()over(partition by deptno order by sal desc)as sal_rank from emp;
2、RANK(计算一个值在一组值中的排位,排位是以1开头的连续整数。具有相等值的行排位相同,序数随后跳跃相应的数值,即如果两行的序数为1,则没有序数2,下一行的序号为3)例82:如何根据员工的工资和俑金对员工在每个部门中进行排位。相同的工资排位相同,并且排位不连续。
select ename,sal,comm,deptno, rank()over(partition by deptno order by sal desc)as RANK from emp;
3、DENSE_RANK(计算一个行在一组有序行中的排位,排位是以1开头的连续整数。具有相等值的行排位相同,并且排位是连续的。)例83:分别计算每个员工的工资在部门中的排位。相等的工资排位相同。select d.dname,e.ename,e.sal, dense_rank()over(partition by e.deptno order by e.sal desc)as denserank from emp e,dept d where e.deptno=d.deptno;
基础部分:
编译命令javac
编译命令:java
JVM:java虚拟机,类声明:
1、一个java文件中只能有一个public class 的声明,但是容许多个class声明
Class 声明:生成的.class 文件和类文件名相同
2、类声明是每个单词首字母大写(习惯)标志符:字母 数字 下划线 $符号 不能用数字开头 数据类型:基本数据类型 :数值型 字符型 布尔型
引用数据类型 :接口 类 数组
运算: 判断语句: 循环语句: 数组: 方法:
java.util.Arrays.sort(x)安升序排列
system.arraycopy(源数组,源数组开始点 ,目标数组 , 目标数组点,长度)面向对象:
面向对象的三个特征:封装 继承 多态 类包括:属性 方法
对象的产生步骤:声明对象,实例化对象 封装 常见的
构造方法:每一个类中都有一个构造方法,那么这个方法实际上就是构造方法
必须和类名相同
没有返回值类型的声明
不能使用return返回一个内容 匿名对象:只使用一次的对象
String 类:
字符:根据字符串的索引找到指定位置的字符 char charAt(index)
将字符数组变成字符串
全部变成字符串 String(char c[])部分变成字符串 new Sting(char c[],int offset,int count)字节:string 变成字节数组: byte[] getByte()将字符串变成字符数组 char[] toCharArray String 变成字符数组:插入char[] getChar(start,end,char des[],目的字符串数组的复制起始位置)字节数组到String 同上面 String 类中的常用方法
length()一个字符串的长度: equals()
equalsIgnoreCase()忽略大小写 startsWith()endsWith()
compareTo()按字典顺序比较难-1,0,1 根据字符串的索引找到指定位置的字符 char charAt(index)根据字符,字符串,找到字符串的位置 int indexOf(char c,int fromIndex)int indexOf(String c,int fromIndex)
字符串的截取: string substring(int beginIndex,int endIndex)字符串的拆分: String[] split(string regex)字符串的替换: replace(char old,char new)replaceAll(string old,string new)trim()去掉字符串前后的空格 toUpperCase()转换成大写: toLowerCase()
concat()连接两个字符串 substring()
substring(begin,end)
字符串转换成其他数据:
Integer.pareseInt(string)Character.pareseChar(string)Double b=Double.pareseDouble(string)
基本数据类型变成字符串:
Static string valueOf(int)valueOf(double)类:类是对对象状态和行为的定义
内部类:在一个类的内部还有另一个类
匿名内部类
包装类:
在包装类的操作中存在 装箱 和拆箱的 在jdk1.5后可以自动
重要的一点,可以将字符串变成基本数据类型
包,实际上就是一个文件夹,在不同的文件中可以存在同名的类
抽象类:包括一个抽象方法的类 抽象方法只是声明而未实现的方法,所有的抽象方法和抽
象类都都必须用abstract关键字声明
抽象类不能直接实例化,可以声明 必须被继承 子类必须直接全部继承抽型方法 继承:
继承的限制:java不支持多重继承,一个子类只能继承一个父类
代码块:包括四种:普通代码块
构造块,静态块 :优先于主方法执行,优先于构造块,而且只执行一次
同步代码块 this super
final 声明的类不能有子类
声明的方法不能被覆写
声明的变量变成了常量
如果final定义一个常量,常量的标识符必须是大写 接口:接口是一个特殊的类,是由抽象方法和全局常量组成: Interface 定义一个接口 Implements 实现接口
一个抽象类可以有多个接口,但一个接口不能继承抽象类 对象的多态:主要指子类对象和父类对象相互转换关系
向上转型:父类 父类对象=子类实例 自动完成
向下转型:子类 子类对象=子类(父类实例)强制完成 用packet来定义包 Import 导入包 系统常用的包
泛型: 异常: 多线程: Java常用类库 StringBuffer String是一个字符串,但是字符串的内容不可以改变,改变的只是内存地址的指向,想改变字符串的内容,必须使用StringBuffer append 将字符串链接
string和stringBuffer没有关系,如果先把StringBuffer对象变成string 必须用toString 常用方法 append()insert(int string)添加 delete(start,end)删除 reverse()反转字符
system类
system.currentTimeMillis()获得当前的时间
gc()方法:
finalize()throws Throwable 此方法将在对象被调用前使用 Date类 java.util.Date Arrays 是一组与数组操作相关的类 正则表达式
集合框架
Collection set list List 内容可以重复,Set内容不可以重复 IO流
File类 删除 delete exists 判断是否春在 separator 分隔符 isFIle 是否是文件 isDirectory判断是否是文件