javaGrowing

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  92 随笔 :: 33 文章 :: 49 评论 :: 0 Trackbacks

#

     摘要: Java 基础方面 : ...  阅读全文
posted @ 2006-02-28 10:57 javaGrowing 阅读(3598) | 评论 (15)编辑 收藏

o_left-1.jpg o_right-1.jpg
posted @ 2006-02-08 11:45 javaGrowing 阅读(228) | 评论 (0)编辑 收藏

JDBC高级应用三

再谈JDBC连结

为什么要反复谈JDBC连结,因为所以JDBC的性能,最主要的就是JDBC连结,而SQL语句的
优化,和JAVA编程无关,你的一个查询语句的效率,是你对于SQL语法的使用技巧,这一方
面你就可请教DBA,而不是来看我这种程序设计者的文章.

我们已经知道,取得数据库连结,有几种层次的实现方法,一是直接得到物理连结,而是通
过传统的连结池(没有多大区别),三是通过java的扩展包javax.sql.DataSource得到连结
句柄,对于上面两种,没有什么可以多说的,对于DataSource,我们再深入一些.
一般来说,DataSource是容器本身作为一个JDNI查找的对象返回出来的,也就是说要依赖
容器进行配置,而如果一个100%的应用程序(Application),比如基于swing的App,我根本
不需要运行容器,那我如何取得DataSource对象?这时可能要你自己写基于DataSource的
连结池了(是不是有些太深入了?要做就做高手,SUN能做我们就能做).
如果自己要实现DataSource,要清楚几个关系,即DataSource中返回的Connection是一个连
结的句柄,它要和实际的物理连结关连,这些实际的物理连结就是PooledConnection,我们
叫它池中的连结,可以通过实现ConnectionPoolDataSource,从中得到PooledConnection,
这部分本来是厂商实现的,但这部份实现和传统的连结池没有什么大的区别,主要是从
ConnectionPoolDataSource中得到PooledConnection的物理连结,但如何从PooledConnection
中getConnection(),返回给用户.这部分实现就是DataSource实现的性能高低的关键,一般
来说,我们可以先把一个物理连结PooledConnection和多个客户连结相关连来增加性能,也
就是一个PooledConnection本身再作为一个工场的种子,通过一个PooledConnection再返回
多个Connection,说白了就是多个Connection的请求通过一个PooledConnection传递给数据库.
只要用户调用Connection的close()方法,就打断这个Connetion与PooledConnection的关联而
让PooledConnection可以和新的Connection进行关联.

JDBC事务

JDBC1开始,就支持本地事务,所谓要地事务,就是在一个连结中的多个操作可以作为一个事务
过程来提交.注意,只要你使用conn.setAutoCommit(false);方法就隐式地打开了一个事务.当
事务被commit或abort时,隐含的是打开了一个新的事务.
另外,当一次事务被commit或abort,PreparedSattement和CallableStatement绑定的结果集全
部被关闭,而普通的Statement绑一的结果集将被维持.
在处理多个操作时:
conn.setAutoCommit(false);
Statement st1 = conn.createSatatement(sql1);
Statement st2 = conn.createSatatement(sql2);
Statement st3 = conn.createSatatement(sql3);
Statement st4 = conn.createSatatement(sql4);
st1.executeXXXXX();
st2.executeXXXXX();
st3.executeXXXXX();
st4.executeXXXXX();
在这里,我们要么把四个操作一起回滚,或一起提交,但如果我们认为,1-3的操作是要求一致完
成,而4的操作在一至三完成时再完成.在经前的版本中我们要把它们分开在两次事务中,但在
JDBC3.0以后,我们可以利用conn.setSavepoint() 来得到一个SavePoint来对同一事务中不同阶
段进行断点保存.然后可以在任何断点上进行提交和回滚.同时我们还可以利用
conn.setTransactionIsolation()来设置隔离级别.
要注意的是,对事务的支持要看数据库的具体实现.如果数据库本身不支持事务,那么以上的操作
都是无效的,可以从 DatabaseMetaData中查询数据库对事务的支持情况.

毕竟,本地事务功能并不是很强,而如果不是编程人员对SQL语句传入错误,那么在一次连结中
多个操作只完成部份的机率并不容易发生(当然有时还会发生的,要不本地事务就不会产生了).
其实,JDBC事务最重要的是分布式事务,即同时操作不同的连结,可能是同物理库的不同空间,也
可能是同一主机的不同数据库或不同主机的多个数据库.这就很难保证每个操作都是成功的,发
生操作不一致的机会太多了,可以说如果不在事务中测试你就无法相信操作的一致性.所以分布
式事务是JDBC的重要技术.
在下一节我们重点介绍JDBC分布式事务.
posted @ 2006-01-09 15:01 javaGrowing 阅读(296) | 评论 (0)编辑 收藏

JDBC高级应用(二)
本来想继续谈JDBC的高级连结方式,事务模式.但发现关于大对象存储有很多人在问,所以
先来插入一节关于大对象存储的内容,然后再接着原来的思路写下去.

JDBC的大对象存储听起来复杂,其实如果你明白了原理以后,就非常简单,网上有关这方面的
教材很少,而SUN的文档中,我从1.2开始看到一在仍然是错误的,不知道写文档的人长脑子没
有,就那几行代码你试试不就知道了,这么多次重抄下来还是错误的.


大对象分类:一般来说,大对象分为:大的文本对象,比如一个很长的文本(请你要注意什么是
文本文件,什么是二进制文件)文件,或者是你定义的一个长字符串,比如你定义了:
String s = "我们要去吃饭了......................然后睡觉!";
从吃饭到睡觉中间省略了实际的10000000000000字,虽然你不会真的定义这么称的String,但
有时会从什么地方得到这样的String,要写到数据库中.
另一种就是大的二进制对象,象执行文件,图象文件等,注意,word,excel,ppt这些"带格式"的文
档都应该以二进制对象存储.

一般来说,数据库如果支持大对象存储,会有这几种类型的SQL数据类型:
BLOB,CLOCB,NLOB,也有的数据数只有一种BLOB,基本上是这样的:BLOB用来存放二进制文件,而
CLOB用来存放文本文件,NLOB是对多字节文本文件支持.假如你的文本文件是纯英文的,放在
BLOB中当然可以,也就是说它是以byte格式存储的,而多字节是以CHAR格式存储的.

同样对于这几种类型的文档,有几种相对应的存取方式:
setter:
利用PreparedStatement的setXXX方法,
setAsciiStream()方法用于写入一般的文本流.setBinaryStream()方法用于写入二进制流
而setUnicodeStream()用于写好UNICODE编码的文本,与此相对应的ResultSet中三个getter方法
用于取回:getAsciiStream(),getBinaryStream(),getBinaryStream().
对于文件本身,要把它作为一个流,只要new InputStream(new FileInputStream("文件路径"))
就可以了,但对于大的String对象,你不会写入文件再转换成输入流吧?
new StringBufferInputStream(String s),记住了.
JDBC2以后提供了java.sql.BLOB对象,我不建议大家使用它,一是很麻类,二是容易出错,要先插
入一个空的BLOB对象,然后再填充它,实在没有必要,直接setXXX就行了,我试过,至少mysql,
oracle,sql server是可以直接set的.
好了,我们先看一个例子如何写入文件到数据库:
数据结构:
create table test(
  name varchar(200),
  content BLOB
);
File f = new File("a.exe");//先生成File对象是为了取得流的长度.FileInputStram可以直接
                           //传入文件路径
InputStream in = new InputStream(new FileInputStream(f));
PreparedStatement ps = conn.prepareStatement("insert into test (?,?)");
ps.setString(1,"a.exe");
ps.setBinaryStream(2,in,(int)f.length());
ps.executeUpdate();
f的长度一定要做从long到int的转换,SUN的文档中好几版都没有改过来.就这么简单,当然,不同的
数据库存本身要设置它允许的最大长度,MYSQL默认只能传1M的文件,要修改参数原能存更大的文件.
如果要从数库中取得文件:
PreparedStatement ps = conn.prepareStatement("select * from test where name=?");
ps.setString(1,"a.exe");
ResultSet rs = ps.executeQuery();
if(rs.next()){
 InputStream in = rs.getBinaryStream("content");
}
得到in对象后,你可以进行任何处理,写向文件和写向页面只是out对象不同而已:
写向文件:
DateOutputStream out = new DateOutputStream(new FileOutputStream("b.exe"));
写向页面:
response.reset();
response.setContType("类型");
ServletOutputSreamt out = response.getOutputSream();
得到out对象后,就可以输出了:
byte[] buf = new byte[1024];
int len = 0;
while((len = in.read(buf)) >0)
  out.write(buf,0,len);
in.close();
out.close();
对于向页面输入,要设置什么样的ContType,要看你想如何输出,如果你想让对方下载,就设为
"application/octet-stream",这样即使是文本,图象都会下载而不会在浏览器中打开.如果你要想
在浏览器中打开,就要设置相应的类型,还要在容器的配置文件中设置支持这种文档类型的输出,但
对于很多格式的文件,到底要输出什么类型,其实就是HTTP的MIME集,比如图片:image/gif,当然你如
果你的文件扩展名(ext)不确定,你也不要用if(ext.equals("gif"))......这样来判断,我教你一个
技巧,我之所以说是技巧,是我没有在别的地方发现有人用这种方法,对我来说我是绝对不会把别人的
方法拿来说是我的技巧的:
构造一个file类型的URL,我们知道URL目前JAVA可以支持HTTP,FTP,MAILTO,FILE,LDAP等,从FILE类型
的URL就可以得到它的MIME:

URL u = new URL("file://a.exe");
String mime = u.openConnection().getContentType();
这样你就可以直接response.setContType(mime);而不用一个一个类型判断了.
好了,大对象存储就说到这儿,不同的数据仍然和些特殊的规定,不在此一一列举了.
posted @ 2006-01-09 10:08 javaGrowing 阅读(305) | 评论 (0)编辑 收藏

JDBC高级应用 一

关于数据库连结

我们所说有JDBC高级应用,并不是说它的技术含量很高(也许JAVA平台上不存在什么"技术含量"的说
法,因为JAVA是给大家用的而不是给某些人用的).说它是高级应用,是因为它是对于JDBC基础应用来
说的扩展,也就是可以优化你的应用性能,或方便于应用的实现.所以说它是一种高级应用而不叫高级
技术.

    JDBC中,java.sql包是基础的,也是核心的功能,javax.sql包则是高级的,扩展的功能.所以
为了交流的方便,我们把它们区分为core API和optional API.

    但是仍然然有一些core API中的功能,我把它归纳到高级应用中.象存储过程的调用,多结果集
的处理,我之所以要把这些东西拿出来说明,是目前你在网上找不到任何一份详细的文档和例程,可以
说有95%以上的开发人员都不知道真正如何处理这些工作.所以我会在回上海后详细写这一段的内容.
现在我们还是来看看optional API给我们带来的好处:

    我们已经了解,执行一个SQL语句,要经过如下几步:
    1.Connction
    2.Statement
    3.Statement.executeXXXXX();
    4.可选的对结果集的处理
    5.必要的Connction的关闭.(再次提醒如果你想成为中级水平以上的程序员,请你把关闭语
    句写在finally块中,在通过下面的介绍后我介绍一个方法可以用来验证你的程序是否有连结
    泄漏)
    
    这其中,生成Connction对象是最最重要的工作,也是最消耗资源的,因为连结对象要驱动底层
的SOCKET,调用物理连结和数据库进行通信,所以生成,关闭,再生成这种连结对象就相当于我们在二十
世纪八十年代(1980年以后出身的不了解吧?)喝易拉罐饮料一样.你买一瓶饮料是一块二角钱,你可知道
那罐子(Connection)值一块零八分.而你喝下去的东西只值一角二分钱,这是我们那儿一个饮料厂的真实
数据.
    在javax.sql包出来以前,我们只能买这样的饮料来喝,除非你不喝.也有一些人不服气自己生
产饮料(poolman),可是消费者很快发现,它只是把原来单卖的易拉罐现在打包卖给了我们,因为它还是
用原来的包装原料来生产的,poolman这种类型的连结池,其根本是从DriverManager中getConnection
出来的.真正的效率如果不是你心理作用的话,也许比单个连结还要低!!!以及一些江湖好汉的"杰作",
都无法跳出这个框框.我自己在那一段时间也曾醉心于研究这些"连结池",因为谁都可以把别人的原码
读过后,再加上其他人的优点写出一个更好的来,可是,大家可以看到,我写出了好用的Upload,DownLoad,
HtmlUtil,Encoder,Decoder等一系列工具.可是我没有写出成功的连结池.......

    我们再来深入一步,为什么DriverManager生成的连结和基于它的连结池不能真正提高性能.
DriverManager对象中,绝大多数的JDBC是封装了一个物理连结,也就是它抓住了一个和数据库通信的
Socket,当你使用DriverManager.getConnection()时也就是有一个和数据库连结的Socket让你占用了.
而且这个方法是同步的.大家知道这样的物理连结对于任何系统是有限制的,比如一个WEB服务器一般
最大并发是150到250之间,数据库服务器也是这样的道理,你不仅要考虑你的程序不能用光连结,还要
考虑不同Runtime中或其它应用程序也在同时和你一起使用这些物理连结,如果一台服务器上有JAVA 
WEB SERVER,还有一个C的应用程序也访问数据库,你不能那么无礼地要求人家C程序员他的程序必须
等你的JAVA调用空闲才能访问数据库吧.所以物理连结是极其宝贵的.
    基于DriverManager.getConnection()的连结池只不过是预先生成这样的物理连结放在一个
pool中,然后编号等你调用,它省略的是生成这样的连结的时间.注意你得到的连结在你没有释放之前,
它无法处理别的工作,因为连结句柄在用户手中,另外这种连结池调用时是由程序调用者初始化的,
每一次调用都必须有初始化工作,而调用者是否以优化的方法去运行它,完成还要看每个人的编程水
平.另一方面,如果这种连结池是如果用于WEB容器管理,那简单是垃圾,因为它强迫使用静态变量来
保持连结,容器根本无法做到访问控制.而且它不能在不同Runtime中被调用.

    而javax.sql的实现采用了在用户连结和物理连结中间加一个缓冲的中间层,它虽然也只生
成30个物理连结,但用户本身不能访问它,DataSource返回给用户的是一个JAVA抽象对象,客户程序把
连结请求放回缓冲中由DataSource统一调度物理连结来处理,这样可以最大程序利用宝贵的物理连结
也许现在的30个物理连结仍然不够负载,你仍然需要修改实际连结数,但我们知道,我们现在的这种连
结方式已经不是DriverManager.getConnection()能比的了.也就是说,在同样多的物理连结下,
DataSource可以给我们更多(是多得多的)的调用机会,其实,正常情况下一个从DriverManager中
getConnection()出来物理连结的负载量只有百分之几,就是因为你的调用抓住了它的句柄而不能让
它很好地工作.另外程序员只能返回它而不能关闭它,因为传统连结池中连结对象一旦由用户关闭,
就要再次重新生成物理新的连结,所以用户只能释放,对于非连结池和连结池得到的连结对象,
要用不同的代码编程,简单是一种痛苦.一旦没有注意,在处理完数据后不是释放而是关闭,这个错误
到底是谁的过错?????????????

    我上面说java.sql实现绝大多数是得到物理连结,也有例外,但不是那些以前的连结池,而是
OSE,就是oracle的servlet环境,它是把servlet服务器实现在数据库的地址空间上,servlet服务去调
用数据库根本没有通过传统的连结,因为数据是"敞开"的,这就象通过网络访问其它计算机文件和访问
本地文件的区别. 虽然它也提供标准的JDBC接口让你调用,但它底层根本不是用JDBC封装的.

    DataSource的另外一个优点就是它完全实现数据库和应用程序的分离,如何配置服务器生成
DataSource的引用和程序开发无关,你在程序开发中,只要通过JDNI查找DataSource的逻辑名称就行.
而DriverManager.getConnection()中,你不得不把数据库驱动程序名,访问地址,用户,密码写在你的
应用程序中,即使可以从配置文件中读取这些属性串,而不同的服务器配置文件的路径你都要修改.而
DataSource提供了标准的配置.

    说来说去,如何用DataSource来连结数据库?

    非常简单:

    DataSource ds = (DataSource)new InitialContext().lookup("jdbc/mydb");
    Connection conn = ds.getConnection();

    当然我是为了说明它简单故意把它的异常给忽略了.实际应用中.你应该捕获它的异常.

    如果你还不明白什么是JDNI,我劝你先找一些这方面的资料看看,这可以是网编程方面的
基础协议啊.

    关于DataSource ds = (DataSource)new InitialContext().lookup("jdbc/mydb");有几点
需要说明的是,你只要配置好你的服务器(tomcat,resin,weblogic)这些服务器中都有一个例子,如果
你不是很懂,你先把那个例子改动几个字就行了.用一段时间你就会慢慢理解它们代表什么了.
然后你在容器环境下调用new InitialContext().lookup("jdbc/mydb")容器就会自动找到那个
DataSource对象给你调用,这个过程对用户来说是透明的.
    那边那个聪明的朋友已经问了,因为DataSource的属性是已经配置好的放在容器中的,那我
不在容器环境下,比如一个独立的application,我如何能取到DataSource呢?
    其实,如果你能知道new InitialContext()时,容器调用了哪些默认的配置,你就可以把
这些配置参数手工加进去而不依赖容器环境了.好在InitialContext可以getEnvironment() ,在生
成这个对象后你可以get一下看看,把这些参数记下来,以后在没有这些参数的环境下put进去.
    这里多几句话,谈一下学习方法,我在国内主持几个论坛(不多,两三个),从没有问过别人什
么问题,java技术又不是我发明的,不可能我什么都懂,一是问了好象有损于"高手"风范(哈哈,其实
真正的高手还是要问别人的,只有我这种假高手才不会问别人).另一方面是我根本不必问别人,比如
象application下,连结不同厂家的DataSource要put什么东西进去呢?一是去他们的网站看资料,虽然
我的英语水平只有大家的10%,但我上英文网站的次数可能比你们多.二是看有没有什么共用的API能
得到,好在可以getEnvironment(),但假如没有这个方法呢?这就要看你的学习态度了,有人会这论坛
叫"高手球命",还有什么"急用,在线等待"什么的.而我,会把new InitialContext()反编译出来看看
它调用了什么(孔子说,为了学习的目的,反编译是允许的,甚至说是伟大的,光明的,正确的思想,是有
道德的,脱离了低级趣味的,有益于人民的行为!!!----孔子语录补集第123页第4行,1989年10月版)
如果一个对象在构造时要求有参数,而它又有一个没有参数的重载的构造方法,你想想它肯定在没有
参数的构造方法中调用了默认参数,你要做的就是把它们打印出来.有多少人是这样做的?

    jdbc optional API的其它扩展功能:
    javax.sql不仅仅是在性能上的提高,而且它还支持分布式事务,在传统的连结过程中,我们
可以在一个连结过程中,setAutoCommit()为fasle,然后通过rollback()或commit()那回滚和提交事
务,这种在一个连结过程中的事务称为本地事务,但假如在一个事务中要对多个数据库操作,或多过
Servlet参与操作,那就必须使用分布式事务.

    关于JDBC的事务我会放在下面来介绍,一个值得庆贺的功能出来了,就是事务保存点已经
JDBC3.0中实现,以前,如果我们把事务原子A,B,C放在一个事务中,如果A,B执行了,C失败,我们只能
把A,B都回滚了,但现在我们可以先把A,B保存为一个点,然后以这个点为回滚或提交,这就象在用WORD
编写文章时我们可以在不同的时候保存一个副本,而不会要么一字没有了,要么就是当前编辑的状态.

    现在我们来优化我们在基础知识中实现的Bean,今天在家,没法上论坛,上次写的连结部分
叫什么名字忘记了,现在我们就叫它PooledDB吧.
    当时我们已经把那个Bean分为三个部分,把生成连结部分独立出来了,而业务方法部份和扩
展部分根本不要动它,这就是继承的好处:)


package com.inmsg.beans;

import javax.naming.*;
import javax.sql.*;

public class PooledDB {
 
      Connection con = null;
      private String source = "";
      public PooledDB() throws Exception {//默认构造方法,如果构造时不加参数,连结jdbc/office
            source = "java:comp/env/jdbc/office";
            Context ct = new InitialContext();
            DataSource ds = (DataSource) ct.lookup(source);
          con = ds.getConnection();
    }
    
    //然后增加重载方法,用来连结其它的数据源    
    public PooledDB(String source) throws Exception {
            this.source = source;
            Context ct = new InitialContext();
            DataSource ds = (DataSource) ct.lookup(source);
          con = ds.getConnection();
    }
    //注意一定要先把source赋给成员变量this.source,因为下面还有一个makeConnection()
    //辅助方法,如果不把source赋给this.source,则makeConnection()调用默认的source字符串

    private void makeConnection() throws Exception {
            Context ct = new InitialContext();
            DataSource ds = (DataSource) ct.lookup(source);
            con = ds.getConnection();
    }
    
    //现在我们把close()方法拿到父类来实现,这是经过综合考虑的,它是一个业务方法,无论是什么
    //方式取得连结,它本身不会修改,但为什么还封装到父类中呢?因为这样可以用一个独立的父类来
    //做连结测试,如果我只想试一下数据库能不能连结,我就不必再引用子类,直接用这个父类就行了
    public void close() throws Exception{
        if(con != null && !con.isClosed()) con.close();
    }
}

    一般来说,构造方法尽量捕获异常处理而不要抛出异常,但作为Bean的实现,捕获异常调用者不容易
看到异常信息,所以抛给调用者处理,另外这个类又要在应用程序中调用,又要考虑作为Bean调用,所以一定
要有一个无参数的构造方法,否则不能作为javaBean调用.把异常抛出给调用者的另一目的,我在设计时是这
样考虑的,就是强迫你一定在使用try{}catch(){}块,这样你就会想到再加一个finally{},再次提醒,一定要
以下面的形式来调用你数据库连结的Bean或封装类:
    PooledDB pd = null;
    try{
        pd = new PooledDB();
        ....................
    }
    catch(Exception e){}
    finally{try{pd.close();}catch(Exception ex){}}
    
    如果要测试你的数据库连结是否有泄漏,请你把DataSource中最大连结数设为1,只用一个连结的情
况下,如果你的程序中哪一处没有关闭连结,则下面的程序就不能再访问,然后从头到尾测试你的程序吧,一旦
发现不能访问数据库了,就查看刚才访问的代码,这样所有程序测试后,就可以放心了,一般来说我是不用这么
测试的,因为我在写数据库连结时是没有生成对象就写好close:

    PooledDB pd = null;
    try{}
    catch(Exception e){}
    finally{try{pd.close();}catch(Exception ex){}}
    然后原在try{}的花括号中回车加上pd = PooledDB();和业务代码的 :)当然,你们都比我聪明用不
着这样死套也不会忘记close()的.
    不要太高兴,到目前为止你仍然还没得到一个最好的解决方案,以下我们还会对这个数据库连结的
Bean(类)不断优化的..................................

    好了,javax.sql的连结先说到这儿吧,今天是周六,出去玩一会了(广州街头上没有什么美女!)
posted @ 2006-01-09 10:07 javaGrowing 阅读(311) | 评论 (0)编辑 收藏

仅列出标题
共19页: First 上一页 10 11 12 13 14 15 16 17 18 下一页 Last