JDBC DAO中Connection的含义
Connection表示了一个和数据库的链接,底层需要有操作系统的Socket支持,所以Connection是一种资源,既然是一种资源,就需要按照建立,打开,使用,关闭的顺序合理的使用。
Connection是Java数据库操作的基础,是进行一系列操作的基础,所有的派生的操作,例如Statement,PreparedStatement,ResultSet等都由Connection直接或者间接的衍生。
如何获得Connection呢?
方法一,使用DriverManager类来获取,前提条件是数据库驱动程序需要在classpath下(即使用数据库链接的程序按照Java的方式可以访问到)。
Connectionconn=DriverManager.getConnection("jdbc:oracle:thin:@192.168.0.1:1521:ORCL",user,pwd);
方法二,使用数据库连接池来获取
什么是数据库连接池呢,数据库连接池是标准JavaEE容器的一种服务,例如Webspher,Weblogic,Tomcat等,容器预先建立一些数据库链接,以便应用程序使用的时候从中借取,注意有借有还,当应用程序使用完了之后会将数据库链接还回连接池。(数据源配置请参考其他文档)
使用连接池的好处是,可以预先建立链接,减小在数据库获取上的相对时间。
使用连接池获取数据库链接的方式为:
- InitialContextctx=newInitialContext();
- DataSourceds=(DataSource)ctx.lookup("java:comp/env/jdbc/DataSource");
- Connectionconn=ds.getConnection();
由于在配置数据库连接池的时候已经定义了URL,用户名,密码等信息,所以在程序中使用的时候不需要传入这些信息。
JDBC DAO中ConnectionManager定义
Connection用来专门管理数据库链接,通常情况下ConnectionManager只有一个方法,调用这个方法将返回一个Connection的实例。通过ConnectionManager可以封装Connection的获取方式(例如开发的时候使用DriverManager,运用的时候使用DataSource的方式,但是不需要修改ConnectionManager之外的其他代码)和追加Connection获取之前之后的操作(例如针对Connection的属性的设置)。
下面的代码是一个ConnectionManager的代码示例:
- packagecom.jpleasure.jdbc.dao;
- importjava.sql.Connection;
- importjava.sql.DriverManager;
- importjava.sql.SQLException;
- publicclassConnectionManager{
- publicstaticConnectiongetConnection()throwsDaoException{
- Connectionconn=null;
- try{
- conn=DriverManager.getConnection("","","");
- }catch(SQLExceptione){
- thrownewDaoException("cannotgetdatabaseconnection",e);
- }
- returnconn;
- }
- }
如果需要从开发模式变为运用模式,只需要将上述代码修改为:
- packagecom.jpleasure.jdbc.dao;
- importjava.sql.Connection;
- importjava.sql.DriverManager;
- importjava.sql.SQLException;
- publicclassConnectionManager{
- publicstaticConnectiongetConnection()throwsDaoException{
- Connectionconn=null;
- try{
- Contextctx=newInitialContext();
- DataSourceds=(DataSource)ctx.lookup("jdbc/dsname");
- conn=ds.getConnection();
- }catch(NamingExceptione){
- thrownewDaoException("cannotfinddatasource",e);
- }catch(SQLExceptione){
- thrownewDaoException("cannotgetdatabaseconnection",e);
- }
- returnconn;
- }
- }
- 如果需要预先设定Connection的一些属性,也可以在上述代码中设定,例如:
- packagecom.jpleasure.jdbc.dao;
- importjava.sql.Connection;
- importjava.sql.DriverManager;
- importjava.sql.SQLException;
- publicclassConnectionManager{
- publicstaticConnectiongetConnection()throwsDaoException{
- Connectionconn=null;
- try{
- Contextctx=newInitialContext();
- DataSourceds=(DataSource)ctx.lookup("jdbc/dsname");
- conn=ds.getConnection();
- conn.setAutoCommit(false);
- }catch(NamingExceptione){
- thrownewDaoException("cannotfinddatasource",e);
- }catch(SQLExceptione){
- thrownewDaoException("cannotgetdatabaseconnection",e);
- }
- returnconn;
- }
- }
- CommonDao定义
- 属性和构造方法
- 通常情况下,CommonDao要有一个Connection的引用。所有一个CommonDao的实例的所有方法的调用都需要依赖于这个Connection。需要一个Connection的另外一个原因是如果各个方法需要保证在一个事务环境中(上下文中),必须保证所有的操作都在一个Connection上。
- 构造方法通常需要将类型为Connection的属性实例化,例如:
- packagecom.jpleasure.jdbc.dao;
- importjava.sql.Connection;
- publicclassCommonDao{
- privateConnectionconn;
- publicCommonDao()throwsDaoException{
- this.conn=ConnectionManager.getConnection();
- }
- }
事务方法
begin()
开始一个事务,调用CommonDao的begin方法之后,所以的后续操作将会在一个事务环境内,要么全部提交,要么全部回滚。
commit()
提交一个事务,必须在begin调用之后调用。且和rollback方法互斥。
rollback()
回滚一个事务,必须在begin方法调用之后调用。且和commit方法互斥。
事务的实现有两种方法,一种是使用基于单一Connection的事务,另外一种方法是使用容器的JTA(JavaTransactionAPI)。需要注意的是***种方法可以在任何环境下使用,但是只能是针对单一的数据库链接。第二种方法智能在支持JTA的JavaEE容器中使用(例如Websphere,Weblogic等,Tomcat默认不支持),但是支持多个Connection实例。
***种方法代码为:
- packagecom.jpleasure.jdbc.dao;
- importjava.sql.Connection;
- importjava.sql.SQLException;
- publicclassCommonDao{
- privateConnectionconn;
- publicCommonDao()throwsDaoException{
- this.conn=ConnectionManager.getConnection();
- }
- publicvoidbegin()throwsDaoException{
- if(conn!=null){
- try{
- conn.setAutoCommit(false);
- }catch(SQLExceptione){
- thrownewDaoException("cannotbegintransaction",e);
- }
- }else{
- thrownewDaoException("connectionnotopened!");
- }
- }
- publicvoidcommit()throwsDaoException{
- try{
- if(conn!=null&&!conn.getAutoCommit()){
- conn.commit();
- conn.setAutoCommit(true);
- }else{
- if(conn==null){
- thrownewDaoException("connectionnotopened!");
- }else{
- thrownewDaoException("firstbeginthencommitplease!");
- }
- }
- }catch(SQLExceptione){
- thrownewDaoException("cannotcommittransaction!",e);
- }
- }
- publicvoidrollback()throwsDaoException{
- try{
- if(conn!=null&&!conn.getAutoCommit()){
- conn.rollback();
- conn.setAutoCommit(true);
- }else{
- if(conn==null){
- thrownewDaoException("connectionnotopened!");
- }else{
- thrownewDaoException("firstbeginthenrollbackplease!");
- }
- }
- }catch(SQLExceptione){
- thrownewDaoException("cannotrollbacktransaction!",e);
- }
- }
- }
第二种我们在使用DAO的实例中介绍如何使用(@TODO)
新建两个DAO,做不同的操作,使用JTA保证事务完整。
查询方法
查询方法也许是CommonDao最常用的方法,查询方法需要将数据库的结果返回给画面。返回值我们一般不使用ResultSet,因为ResultSet依赖于Connection,如果Connection关闭,ResultSet将不再有效,所以我们通常将ResultSet转变为一个List之后返回。
在说明查询方法之前,我们先说说如何将数据库中的内容放在List中,我们使用一个List表示一个查询结果集合,使用一个Map表示集合中的一行,Map的key表示数据库表的字段名字,Value表示数据库字段的内容。代码为:
- privateListconvert(ResultSetrs)throwsDaoException{
- //recordlist
- ListretList=newArrayList();
- try{
- ResultSetMetaDatameta=rs.getMetaData();
- //columncount
- intcolCount=meta.getColumnCount();
- //eachrecord
- while(rs.next()){
- MaprecordMap=newHashMap();
- //eachcolumn
- for(inti=1;i<=colCount;i++){
- //columnname
- Stringname=meta.getColumnName(i);
- //columnvalue
- Objectvalue=rs.getObject(i);
- //addcolumntorecord
- recordMap.put(name,value);
- }
- //adrecordtolist
- retList.add(recordMap);
- }
- }catch(SQLExceptionex){
- thrownewDaoException("cannotconvertresultsettolistofmap",ex);
- }
- returnretList;
- }
为了避免Sql注入的安全问题,我们通常使用PreparedStatement,在使用PreparedStatement的时候涉及到如何将传入参数设置到PreparedStatement上面,参看以下的共通方法:
- privatevoidapply(PreparedStatementpstmt,Listparams)throwsDaoException{
- try{
- //ifparamsexist
- if(params!=null&¶ms.size()>0){
- //parametersiterator
- Iteratorit=params.iterator();
- //parameterindex
- intindex=1;
- while(it.hasNext()){
- Objectobj=it.next();
- //ifnullset""
- if(obj==null){
- pstmt.setObject(index,"");
- }else{
- //elsesetobject
- pstmt.setObject(index,obj);
- }
- //nextindex
- index++;
- }
- }
- }catch(SQLExceptionex){
- thrownewDaoException("cannotapplyparameter",ex);
- }
- }
- 接着我们继续说我们的查询方法,有了上述两个方法,我们的查询方法就非常简单了:
- publicListquery(Stringsql,Listparams)throwsDaoException{
- Listresult=null;
- PreparedStatementpstmt=null;
- ResultSetrs=null;
- try{
- pstmt=conn.prepareStatement(sql);
- this.apply(pstmt,params);
- rs=pstmt.executeQuery();
- result=this.convert(rs);
- }catch(SQLExceptionex){
- thrownewDaoException("cannotexecutequery",ex);
- }finally{
- if(rs!=null){
- try{
- rs.close();
- }catch(SQLExceptione){
- //nothing
- }
- }
- if(pstmt!=null){
- try{
- pstmt.close();
- }catch(SQLExceptione){
- //nothing
- }
- }
- }
- returnresult;
- }
特殊的查询方法(返回单值)
有时候为了方便使用,我们需要返回单值的产寻方法,例如selectmax(id)fromtable_a,selectcount(id)fromtable_b等。以下的代码使用了上述通用的查询方法,代码为:
- publicObjectqueryOne(Stringsql,Listparams)throwsDaoException{
- Listlist=this.query(sql,params);
- if(list==null||list.size()==0){
- thrownewDaoException("datanotexist");
- }else{
- Maprecord=(Map)list.get(0);
- if(record==null||record.size()==0){
- thrownewDaoException("datanotexist");
- }else{
- returnrecord.values().toArray()[0];
- }
- }
- }
更新,删除,插入方法
由于在JDBC中这三个方法都是用了一个execute完成,所以这里我们也使用一个方法来完成这些功能。代码为:
- publicintexecute(Stringsql,Listparams)throwsDaoException{
- intret=0;
- PreparedStatementpstmt=null;
- try{
- pstmt=conn.prepareStatement(sql);
- this.apply(pstmt,params);
- ret=pstmt.executeUpdate();
- }catch(SQLExceptionex){
- thrownewDaoException("",ex);
- }finally{
- if(pstmt!=null){
- try{
- pstmt.close();
- }catch(SQLExceptione){
- //nothing.
- }
- }
- }
- returnret;
- }
批处理方法(查询)
有些时候为了便于操作,需要一次查询多条SQL语句,我们称之为批处理,实现参看以下方法,其中为了和query方法做区分,将参数和返回值都改为了数组形式。
- publicList[]queryBatch(String[]sqlArray,List[]paramArray)throwsDaoException{
- Listrets=newArrayList();
- if(sqlArray.length!=paramArray.length){
- thrownewDaoException("sqlsizenotequalparametersize");
- }else{
- for(inti=0;iStringsql=sqlArray[i];
- Listparam=paramArray[i];
- Listret=this.query(sql,param);
- rets.add(ret);
- }
- return(List[])rets.toArray();
- }
- }
批处理方法(更新)
有些时候需要一次更新多条Sql语句,为了便于操作,添加了批处理更新操作,参看以下代码,为了和更新方法区分,将参数和返回值都改为了数组形式。
- publicint[]executeBatch(String[]sqlArray,List[]paramArray)throwsDaoException{
- Listrets=newArrayList();
- if(sqlArray.length!=paramArray.length){
- thrownewDaoException("sqlsizenotequalparametersize");
- }else{
- for(inti=0;iintret=this.execute(sqlArray[i],paramArray[i]);
- rets.add(newInteger(ret));
- }
- int[]retArray=newint[rets.size()];
- for(inti=0;iretArray[i]=((Integer)rets.get(i)).intValue();
- }
- returnretArray;
- }
- }
资源释放
由于CommonDao有一个Connection的属性,且Connection属于稀缺资源,所以在CommonDao不需要在使用的时候需要显示的关闭Connection。代码如下:
- publicvoidclose()throwsDaoException{
- try{
- if(conn!=null&&conn.getAutoCommit()){
- conn.close();
- }else{
- if(conn==null){
- thrownewDaoException("cannotclosenullconnection,firstnewthenclose");
- }else{
- thrownewDaoException("transactionisrunning,rollbakcorcommitbeforcloseplease.");
- }
- }
- }catch(SQLExceptionex){
- thrownewDaoException("Cannotclosecommondao");
- }
- }
JDBC工具类(JDBCUtilClass)
在上述的代码中我们看到有很多的无用的处理,例如:
- if(pstmt!=null){
- try{
- pstmt.close();
- }catch(SQLExceptione){
- //nothing.
- }
- }
为什么要有这些处理呢?说先这些处理发生的位置都是在正常处理完成之后,这些处理(例如pstmt.close())即使失败也没有影响,这个时候我们需要做上述的无用处理,这正是JDBCAPI的一个小小的瑕疵。我们通常使用一个特殊的静态工具来来做补充,例如:
- packagecom.jpleasure.jdbc.dao;
- importjava.sql.Connection;
- importjava.sql.PreparedStatement;
- importjava.sql.ResultSet;
- importjava.sql.SQLException;
- publicclassJDBCUtil{
- publicvoidsafelyClose(Connectionconn){
- if(conn!=null){
- try{
- conn.close();
- }catch(SQLExceptione){
- //
- }
- }
- }
- publicvoidsafelyClose(PreparedStatementpstmt){
- if(pstmt!=null){
- try{
- pstmt.close();
- }catch(SQLExceptione){
- //
- }
- }
- }
- publicvoidsafelyClose(ResultSetrs){
- if(rs!=null){
- try{
- rs.close();
- }catch(SQLExceptione){
- //
- }
- }
- }
- }
JDBC DAO中异常处理
也许细心的你已经发现了一个问题,为什么所有抛出异常的地方我们都是将SQLException包装在了DaoException之内抛出呢,为什么不直接抛出SQLException呢?有两个原因,***,可以细化,分类Exception抛出合适的异常,添加合适的消息,第二,隔离和Dao和业务逻辑的耦合,可以方便的修改Dao层而不会影响到业务逻辑层。另外需要注意,DaoExcetion中可以包含SQLException,这个时候可以为客户提供更详细的错误信息,例如ORA-12524等内容,但是很少见到。
- packagecom.jpleasure.jdbc.dao;
- publicclassDaoExceptionextendsException{
- publicDaoException(){
- super();
- }
- publicDaoException(Stringmessage,Throwablecause){
- super(message,cause);
- }
- publicDaoException(Stringmessage){
- super(message);
- }
- publicDaoException(Throwablecause){
- super(cause);
- }
- }
【编辑推荐】