浅谈JDBC DAO的设计理念

开发 后端
本文将简单谈谈JDBC DAO的设计理念,DAO是Data Access Object数据访问接口,数据访问:顾名思义就是与数据库打交道。

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等,容器预先建立一些数据库链接,以便应用程序使用的时候从中借取,注意有借有还,当应用程序使用完了之后会将数据库链接还回连接池。(数据源配置请参考其他文档)

使用连接池的好处是,可以预先建立链接,减小在数据库获取上的相对时间。

使用连接池获取数据库链接的方式为:

  1. InitialContextctx=newInitialContext();
  2. DataSourceds=(DataSource)ctx.lookup("java:comp/env/jdbc/DataSource");
  3. Connectionconn=ds.getConnection();

由于在配置数据库连接池的时候已经定义了URL,用户名,密码等信息,所以在程序中使用的时候不需要传入这些信息。

JDBC DAO中ConnectionManager定义

Connection用来专门管理数据库链接,通常情况下ConnectionManager只有一个方法,调用这个方法将返回一个Connection的实例。通过ConnectionManager可以封装Connection的获取方式(例如开发的时候使用DriverManager,运用的时候使用DataSource的方式,但是不需要修改ConnectionManager之外的其他代码)和追加Connection获取之前之后的操作(例如针对Connection的属性的设置)。

下面的代码是一个ConnectionManager的代码示例:

  1. packagecom.jpleasure.jdbc.dao;
  2. importjava.sql.Connection;
  3. importjava.sql.DriverManager;
  4. importjava.sql.SQLException;
  5. publicclassConnectionManager{
  6. publicstaticConnectiongetConnection()throwsDaoException{
  7. Connectionconn=null;
  8. try{
  9. conn=DriverManager.getConnection("","","");
  10. }catch(SQLExceptione){
  11. thrownewDaoException("cannotgetdatabaseconnection",e);
  12. }
  13. returnconn;
  14. }
  15. }

如果需要从开发模式变为运用模式,只需要将上述代码修改为:

  1. packagecom.jpleasure.jdbc.dao;
  2. importjava.sql.Connection;
  3. importjava.sql.DriverManager;
  4. importjava.sql.SQLException;
  5. publicclassConnectionManager{
  6. publicstaticConnectiongetConnection()throwsDaoException{
  7. Connectionconn=null;
  8. try{
  9. Contextctx=newInitialContext();
  10. DataSourceds=(DataSource)ctx.lookup("jdbc/dsname");
  11. conn=ds.getConnection();
  12. }catch(NamingExceptione){
  13. thrownewDaoException("cannotfinddatasource",e);
  14. }catch(SQLExceptione){
  15. thrownewDaoException("cannotgetdatabaseconnection",e);
  16. }
  17. returnconn;
  18. }
  19. }
  20. 如果需要预先设定Connection的一些属性,也可以在上述代码中设定,例如:
  21. packagecom.jpleasure.jdbc.dao;
  22. importjava.sql.Connection;
  23. importjava.sql.DriverManager;
  24. importjava.sql.SQLException;
  25. publicclassConnectionManager{
  26. publicstaticConnectiongetConnection()throwsDaoException{
  27. Connectionconn=null;
  28. try{
  29. Contextctx=newInitialContext();
  30. DataSourceds=(DataSource)ctx.lookup("jdbc/dsname");
  31. conn=ds.getConnection();
  32. conn.setAutoCommit(false);
  33. }catch(NamingExceptione){
  34. thrownewDaoException("cannotfinddatasource",e);
  35. }catch(SQLExceptione){
  36. thrownewDaoException("cannotgetdatabaseconnection",e);
  37. }
  38. returnconn;
  39. }
  40. }
  41. CommonDao定义
  42. 属性和构造方法
  43. 通常情况下,CommonDao要有一个Connection的引用。所有一个CommonDao的实例的所有方法的调用都需要依赖于这个Connection。需要一个Connection的另外一个原因是如果各个方法需要保证在一个事务环境中(上下文中),必须保证所有的操作都在一个Connection上。
  44. 构造方法通常需要将类型为Connection的属性实例化,例如:
  45. packagecom.jpleasure.jdbc.dao;
  46. importjava.sql.Connection;
  47. publicclassCommonDao{
  48. privateConnectionconn;
  49. publicCommonDao()throwsDaoException{
  50. this.conn=ConnectionManager.getConnection();
  51. }
  52. }

事务方法

begin()

开始一个事务,调用CommonDao的begin方法之后,所以的后续操作将会在一个事务环境内,要么全部提交,要么全部回滚。

commit()

提交一个事务,必须在begin调用之后调用。且和rollback方法互斥。

rollback()

回滚一个事务,必须在begin方法调用之后调用。且和commit方法互斥。

事务的实现有两种方法,一种是使用基于单一Connection的事务,另外一种方法是使用容器的JTA(JavaTransactionAPI)。需要注意的是***种方法可以在任何环境下使用,但是只能是针对单一的数据库链接。第二种方法智能在支持JTA的JavaEE容器中使用(例如Websphere,Weblogic等,Tomcat默认不支持),但是支持多个Connection实例。

***种方法代码为:

  1. packagecom.jpleasure.jdbc.dao;
  2. importjava.sql.Connection;
  3. importjava.sql.SQLException;
  4. publicclassCommonDao{
  5. privateConnectionconn;
  6. publicCommonDao()throwsDaoException{
  7. this.conn=ConnectionManager.getConnection();
  8. }
  9. publicvoidbegin()throwsDaoException{
  10. if(conn!=null){
  11. try{
  12. conn.setAutoCommit(false);
  13. }catch(SQLExceptione){
  14. thrownewDaoException("cannotbegintransaction",e);
  15. }
  16. }else{
  17. thrownewDaoException("connectionnotopened!");
  18. }
  19. }
  20. publicvoidcommit()throwsDaoException{
  21. try{
  22. if(conn!=null&&!conn.getAutoCommit()){
  23. conn.commit();
  24. conn.setAutoCommit(true);
  25. }else{
  26. if(conn==null){
  27. thrownewDaoException("connectionnotopened!");
  28. }else{
  29. thrownewDaoException("firstbeginthencommitplease!");
  30. }
  31. }
  32. }catch(SQLExceptione){
  33. thrownewDaoException("cannotcommittransaction!",e);
  34. }
  35. }
  36. publicvoidrollback()throwsDaoException{
  37. try{
  38. if(conn!=null&&!conn.getAutoCommit()){
  39. conn.rollback();
  40. conn.setAutoCommit(true);
  41. }else{
  42. if(conn==null){
  43. thrownewDaoException("connectionnotopened!");
  44. }else{
  45. thrownewDaoException("firstbeginthenrollbackplease!");
  46. }
  47. }
  48. }catch(SQLExceptione){
  49. thrownewDaoException("cannotrollbacktransaction!",e);
  50. }
  51. }
  52. }

第二种我们在使用DAO的实例中介绍如何使用(@TODO)

新建两个DAO,做不同的操作,使用JTA保证事务完整。

查询方法

查询方法也许是CommonDao最常用的方法,查询方法需要将数据库的结果返回给画面。返回值我们一般不使用ResultSet,因为ResultSet依赖于Connection,如果Connection关闭,ResultSet将不再有效,所以我们通常将ResultSet转变为一个List之后返回。

在说明查询方法之前,我们先说说如何将数据库中的内容放在List中,我们使用一个List表示一个查询结果集合,使用一个Map表示集合中的一行,Map的key表示数据库表的字段名字,Value表示数据库字段的内容。代码为:

  1. privateListconvert(ResultSetrs)throwsDaoException{
  2. //recordlist
  3. ListretList=newArrayList();
  4. try{
  5. ResultSetMetaDatameta=rs.getMetaData();
  6. //columncount
  7. intcolCount=meta.getColumnCount();
  8. //eachrecord
  9. while(rs.next()){
  10. MaprecordMap=newHashMap();
  11. //eachcolumn
  12. for(inti=1;i<=colCount;i++){
  13. //columnname
  14. Stringname=meta.getColumnName(i);
  15. //columnvalue
  16. Objectvalue=rs.getObject(i);
  17. //addcolumntorecord
  18. recordMap.put(name,value);
  19. }
  20. //adrecordtolist
  21. retList.add(recordMap);
  22. }
  23. }catch(SQLExceptionex){
  24. thrownewDaoException("cannotconvertresultsettolistofmap",ex);
  25. }
  26. returnretList;
  27. }

为了避免Sql注入的安全问题,我们通常使用PreparedStatement,在使用PreparedStatement的时候涉及到如何将传入参数设置到PreparedStatement上面,参看以下的共通方法:

  1. privatevoidapply(PreparedStatementpstmt,Listparams)throwsDaoException{
  2. try{
  3. //ifparamsexist
  4. if(params!=null&¶ms.size()>0){
  5. //parametersiterator
  6. Iteratorit=params.iterator();
  7. //parameterindex
  8. intindex=1;
  9. while(it.hasNext()){
  10. Objectobj=it.next();
  11. //ifnullset""
  12. if(obj==null){
  13. pstmt.setObject(index,"");
  14. }else{
  15. //elsesetobject
  16. pstmt.setObject(index,obj);
  17. }
  18. //nextindex
  19. index++;
  20. }
  21. }
  22. }catch(SQLExceptionex){
  23. thrownewDaoException("cannotapplyparameter",ex);
  24. }
  25. }
  26. 接着我们继续说我们的查询方法,有了上述两个方法,我们的查询方法就非常简单了:
  27. publicListquery(Stringsql,Listparams)throwsDaoException{
  28. Listresult=null;
  29. PreparedStatementpstmt=null;
  30. ResultSetrs=null;
  31. try{
  32. pstmt=conn.prepareStatement(sql);
  33. this.apply(pstmt,params);
  34. rs=pstmt.executeQuery();
  35. result=this.convert(rs);
  36. }catch(SQLExceptionex){
  37. thrownewDaoException("cannotexecutequery",ex);
  38. }finally{
  39. if(rs!=null){
  40. try{
  41. rs.close();
  42. }catch(SQLExceptione){
  43. //nothing
  44. }
  45. }
  46. if(pstmt!=null){
  47. try{
  48. pstmt.close();
  49. }catch(SQLExceptione){
  50. //nothing
  51. }
  52. }
  53. }
  54. returnresult;
  55. }

特殊的查询方法(返回单值)

有时候为了方便使用,我们需要返回单值的产寻方法,例如selectmax(id)fromtable_a,selectcount(id)fromtable_b等。以下的代码使用了上述通用的查询方法,代码为:

  1. publicObjectqueryOne(Stringsql,Listparams)throwsDaoException{
  2. Listlist=this.query(sql,params);
  3. if(list==null||list.size()==0){
  4. thrownewDaoException("datanotexist");
  5. }else{
  6. Maprecord=(Map)list.get(0);
  7. if(record==null||record.size()==0){
  8. thrownewDaoException("datanotexist");
  9. }else{
  10. returnrecord.values().toArray()[0];
  11. }
  12. }
  13. }

更新,删除,插入方法

由于在JDBC中这三个方法都是用了一个execute完成,所以这里我们也使用一个方法来完成这些功能。代码为:

  1. publicintexecute(Stringsql,Listparams)throwsDaoException{
  2. intret=0;
  3. PreparedStatementpstmt=null;
  4. try{
  5. pstmt=conn.prepareStatement(sql);
  6. this.apply(pstmt,params);
  7. ret=pstmt.executeUpdate();
  8. }catch(SQLExceptionex){
  9. thrownewDaoException("",ex);
  10. }finally{
  11. if(pstmt!=null){
  12. try{
  13. pstmt.close();
  14. }catch(SQLExceptione){
  15. //nothing.
  16. }
  17. }
  18. }
  19. returnret;
  20. }

批处理方法(查询)

有些时候为了便于操作,需要一次查询多条SQL语句,我们称之为批处理,实现参看以下方法,其中为了和query方法做区分,将参数和返回值都改为了数组形式。

  1. publicList[]queryBatch(String[]sqlArray,List[]paramArray)throwsDaoException{
  2. Listrets=newArrayList();
  3. if(sqlArray.length!=paramArray.length){
  4. thrownewDaoException("sqlsizenotequalparametersize");
  5. }else{
  6. for(inti=0;iStringsql=sqlArray[i];
  7. Listparam=paramArray[i];
  8. Listret=this.query(sql,param);
  9. rets.add(ret);
  10. }
  11. return(List[])rets.toArray();
  12. }
  13. }

批处理方法(更新)

有些时候需要一次更新多条Sql语句,为了便于操作,添加了批处理更新操作,参看以下代码,为了和更新方法区分,将参数和返回值都改为了数组形式。

  1. publicint[]executeBatch(String[]sqlArray,List[]paramArray)throwsDaoException{
  2. Listrets=newArrayList();
  3. if(sqlArray.length!=paramArray.length){
  4. thrownewDaoException("sqlsizenotequalparametersize");
  5. }else{
  6. for(inti=0;iintret=this.execute(sqlArray[i],paramArray[i]);
  7. rets.add(newInteger(ret));
  8. }
  9. int[]retArray=newint[rets.size()];
  10. for(inti=0;iretArray[i]=((Integer)rets.get(i)).intValue();
  11. }
  12. returnretArray;
  13. }
  14. }

资源释放

由于CommonDao有一个Connection的属性,且Connection属于稀缺资源,所以在CommonDao不需要在使用的时候需要显示的关闭Connection。代码如下:

  1. publicvoidclose()throwsDaoException{
  2. try{
  3. if(conn!=null&&conn.getAutoCommit()){
  4. conn.close();
  5. }else{
  6. if(conn==null){
  7. thrownewDaoException("cannotclosenullconnection,firstnewthenclose");
  8. }else{
  9. thrownewDaoException("transactionisrunning,rollbakcorcommitbeforcloseplease.");
  10. }
  11. }
  12. }catch(SQLExceptionex){
  13. thrownewDaoException("Cannotclosecommondao");
  14. }
  15. }

JDBC工具类(JDBCUtilClass)

在上述的代码中我们看到有很多的无用的处理,例如:

  1. if(pstmt!=null){
  2. try{
  3. pstmt.close();
  4. }catch(SQLExceptione){
  5. //nothing.
  6. }
  7. }

为什么要有这些处理呢?说先这些处理发生的位置都是在正常处理完成之后,这些处理(例如pstmt.close())即使失败也没有影响,这个时候我们需要做上述的无用处理,这正是JDBCAPI的一个小小的瑕疵。我们通常使用一个特殊的静态工具来来做补充,例如:

  1. packagecom.jpleasure.jdbc.dao;
  2. importjava.sql.Connection;
  3. importjava.sql.PreparedStatement;
  4. importjava.sql.ResultSet;
  5. importjava.sql.SQLException;
  6. publicclassJDBCUtil{
  7. publicvoidsafelyClose(Connectionconn){
  8. if(conn!=null){
  9. try{
  10. conn.close();
  11. }catch(SQLExceptione){
  12. //
  13. }
  14. }
  15. }
  16. publicvoidsafelyClose(PreparedStatementpstmt){
  17. if(pstmt!=null){
  18. try{
  19. pstmt.close();
  20. }catch(SQLExceptione){
  21. //
  22. }
  23. }
  24. }
  25. publicvoidsafelyClose(ResultSetrs){
  26. if(rs!=null){
  27. try{
  28. rs.close();
  29. }catch(SQLExceptione){
  30. //
  31. }
  32. }
  33. }
  34. }

JDBC DAO中异常处理

也许细心的你已经发现了一个问题,为什么所有抛出异常的地方我们都是将SQLException包装在了DaoException之内抛出呢,为什么不直接抛出SQLException呢?有两个原因,***,可以细化,分类Exception抛出合适的异常,添加合适的消息,第二,隔离和Dao和业务逻辑的耦合,可以方便的修改Dao层而不会影响到业务逻辑层。另外需要注意,DaoExcetion中可以包含SQLException,这个时候可以为客户提供更详细的错误信息,例如ORA-12524等内容,但是很少见到。

  1. packagecom.jpleasure.jdbc.dao;
  2. publicclassDaoExceptionextendsException{
  3. publicDaoException(){
  4. super();
  5. }
  6. publicDaoException(Stringmessage,Throwablecause){
  7. super(message,cause);
  8. }
  9. publicDaoException(Stringmessage){
  10. super(message);
  11. }
  12. publicDaoException(Throwablecause){
  13. super(cause);
  14. }
  15. }

【编辑推荐】

  1. 使用JDBC的五个精华功能
  2. Tomcat5+MySQL JDBC连接池配置
  3. 在Weblogic中实现JDBC的功能
  4. 详解JDBC与Hibernate区别
  5. JDBC连接MySQL数据库关键四步
  6. 详解JDBC驱动的四种类型
责任编辑:彭凡 来源: javaeye
相关推荐

2009-07-15 17:11:31

JDBC的概念

2009-06-29 17:17:57

Spring

2009-07-20 17:41:59

Java JDBC

2009-07-01 17:58:20

JSP

2009-07-21 17:41:58

JDBC数据源

2010-03-02 16:34:06

Android平台

2010-03-16 17:07:51

云计算

2009-07-20 09:27:42

IBATIS.netDAO

2009-07-15 18:07:47

JDBC代码

2010-09-28 11:05:49

jQuery

2010-11-09 09:43:22

UI设计Windows Pho

2010-06-11 14:55:20

2022-10-09 14:15:42

短链设计

2009-07-23 13:37:45

JDBC连接SQL S

2009-07-16 14:46:48

jdbc statem

2009-07-22 13:49:40

JSP JDBC

2009-07-15 15:30:12

MyEclipse J

2009-07-16 16:23:20

JDBC result

2009-07-17 17:41:25

JDBC连接SQL S

2009-07-14 17:18:23

JDBC怎么连接数据库
点赞
收藏

51CTO技术栈公众号