JDBC DAO设计

转自:http://zpball.iteye.com/blog/1114922

Connection的含义
    Connection表示了一个和数据库的链接,底层需要有操作系统的Socket支持,所以Connection是一种资源,既然是一种资源,就需要按照建立,打开,使用,关闭的顺序合理的使用。
    Connection是Java数据库操作的基础,是进行一系列操作的基础,所有的派生的操作,例如Statement,PreparedStatement,ResultSet等都由Connection直接或者间接的衍生。

    如何获得Connection呢?
    方法一,使用DriverManager类来获取,前提条件是数据库驱动程序需要在classpath下(即使用数据库链接的程序按照Java的方式可以访问到)。
       Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@192.168.0.1:1521:ORCL", user, pwd );
    方法二,使用数据库连接池来获取
       什么是数据库连接池呢,数据库连接池是标准JavaEE容器的一种服务,例如Webspher,Weblogic,Tomcat等,容器预先建立一些数据 库链接,以便应用程序使用的时候从中借取,注意有借有还,当应用程序使用完了之后会将数据库链接还回连接池。(数据源配置请参考其他文档)
       使用连接池的好处是,可以预先建立链接,减小在数据库获取上的相对时间。
       使用连接池获取数据库链接的方式为:
           InitialContext ctx = new InitialContext();
           DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/DataSource");
           Connection conn = ds.getConnection();
       由于在配置数据库连接池的时候已经定义了URL,用户名,密码等信息,所以在程序中使用的时候不需要传入这些信息。

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


Java代码 
package com.jpleasure.jdbc.dao; 
 
import java.sql.Connection; 
import java.sql.DriverManager; 
import java.sql.SQLException; 
 
public class ConnectionManager { 
     
    public static Connection getConnection() throws DaoException { 
        Connection conn = null; 
        try { 
            conn = DriverManager.getConnection("", "", ""); 
        } catch (SQLException e) { 
            throw new DaoException("can not get database connection", e); 
        } 
        return conn; 
    } 



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

Java代码 
package com.jpleasure.jdbc.dao; 
 
import java.sql.Connection; 
import java.sql.DriverManager; 
import java.sql.SQLException; 
 
public class ConnectionManager { 
     
    public static Connection getConnection() throws DaoException { 
        Connection conn = null; 
        try { 
             Context ctx = new InitialContext(); 
             DataSource ds = (DataSource)ctx.lookup("jdbc/dsname"); 
             conn = ds.getConnection(); 
          } catch(NamingException e) { 
             throw new DaoException("can not find datasource", e); 
          }catch (SQLException e) { 
            throw new DaoException("can not get database connection", e); 
        }  
        return conn; 
    } 




如果需要预先设定Connection的一些属性,也可以在上述代码中设定,例如:

Java代码 
package com.jpleasure.jdbc.dao; 
 
import java.sql.Connection; 
import java.sql.DriverManager; 
import java.sql.SQLException; 
 
public class ConnectionManager { 
     
    public static Connection getConnection() throws DaoException { 
        Connection conn = null; 
        try { 
              Context ctx = new InitialContext(); 
              DataSource ds = (DataSource)ctx.lookup("jdbc/dsname"); 
              conn = ds.getConnection(); 
              conn.setAutoCommit(false); 
          } catch(NamingException e) { 
             throw new DaoException("can not find datasource", e); 
          }catch (SQLException e) { 
            throw new DaoException("can not get database connection", e); 
        }  
        return conn; 
    } 



CommonDao定义
    属性和构造方法
       通常情况下,CommonDao要有一个Connection的引用。所有一个CommonDao的实例的所有方法的调用都需要依赖于这个 Connection。需要一个Connection的另外一个原因是如果各个方法需要保证在一个事务环境中(上下文中),必须保证所有的操作都在一个 Connection上。
       构造方法通常需要将类型为Connection的属性实例化,例如:
Java代码 
package com.jpleasure.jdbc.dao; 
 
import java.sql.Connection; 
 
public class CommonDao { 
     
    private Connection conn; 
     
    public CommonDao() throws DaoException { 
        this.conn = ConnectionManager.getConnection(); 
    }     



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

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

        rollback()  
          回滚一个事务,必须在begin方法调用之后调用。且和commit方法互斥。
   
       事务的实现有两种方法,一种是使用基于单一Connection的事务,另外一种方法是使用容器的JTA(Java Transaction API)。需要注意的是第一种方法可以在任何环境下使用,但是只能是针对单一的数据库链接。第二种方法智能在支持JTA的Java EE容器中使用(例如Websphere,Weblogic等,Tomcat默认不支持),但是支持多个Connection实例。
    第一种方法代码为:
Java代码 
package com.jpleasure.jdbc.dao; 
 
import java.sql.Connection; 
import java.sql.SQLException; 
 
public class CommonDao { 
     
    private Connection conn; 
     
    public CommonDao() throws DaoException { 
        this.conn = ConnectionManager.getConnection(); 
    }     
 
    public void begin() throws DaoException{ 
        if(conn != null) { 
            try { 
                conn.setAutoCommit(false); 
            } catch (SQLException e) { 
                throw new DaoException("can not begin transaction", e); 
            } 
        } else { 
            throw new DaoException("connection not opened!"); 
        } 
    } 
     
    public void commit() throws DaoException { 
        try { 
            if (conn != null && !conn.getAutoCommit()) { 
                conn.commit(); 
                conn.setAutoCommit(true); 
            } else { 
                if (conn == null) { 
                    throw new DaoException("connection not opened!"); 
                } else { 
                    throw new DaoException("first begin then commit please!"); 
                } 
            } 
        } catch (SQLException e) { 
            throw new DaoException("can not commit transaction!", e); 
        } 
    } 
     
    public void rollback() throws DaoException { 
        try { 
            if (conn != null && !conn.getAutoCommit()) { 
                conn.rollback(); 
                conn.setAutoCommit(true); 
            } else { 
                if (conn == null) { 
                    throw new DaoException("connection not opened!"); 
                } else { 
                    throw new DaoException("first begin then rollback please!"); 
                } 
            } 
        } catch (SQLException e) { 
            throw new DaoException("can not rollback transaction!", e); 
        } 
    } 
     
     



第二种我们在使用DAO的实例中介绍如何使用(@TODO)
       新建两个DAO,做不同的操作,使用JTA保证事务完整。

   查询方法
       查询方法也许是CommonDao最常用的方法,查询方法需要将数据库的结果返回给画面。返回值我们一般不使用ResultSet,因为 ResultSet依赖于Connection,如果Connection关闭,ResultSet将不再有效,所以我们通常将ResultSet转变为 一个List之后返回。
       在说明查询方法之前,我们先说说如何将数据库中的内容放在List中,我们使用一个List表示一个查询结果集合,使用一个Map表示集合中的一行,Map的key表示数据库表的字段名字,Value表示数据库字段的内容。代码为:
Java代码 
private List convert(ResultSet rs) throws DaoException { 
 
       // record list 
       List retList = new ArrayList(); 
 
       try { 
           ResultSetMetaData meta = rs.getMetaData(); 
 
           // column count 
           int colCount = meta.getColumnCount(); 
 
           // each record 
           while (rs.next()) { 
 
               Map recordMap = new HashMap(); 
 
               // each column 
               for (int i = 1; i <= colCount; i++) { 
                   // column name 
                   String name = meta.getColumnName(i); 
                   // column value 
                   Object value = rs.getObject(i); 
                   // add column to record 
                   recordMap.put(name, value); 
               } 
               // ad record to list 
               retList.add(recordMap); 
           } 
       } catch (SQLException ex) { 
           throw new DaoException("can not convert result set to list of map", ex); 
       } 
       return retList; 
   } 


为了避免Sql注入的安全问题,我们通常使用PreparedStatement,在使用PreparedStatement的时候涉及到如何将传入参数设置到PreparedStatement上面,参看以下的共通方法:
Java代码 
private void apply(PreparedStatement pstmt, List params) throws DaoException { 
    try { 
        // if params exist 
        if (params != null && params.size() > 0) { 
            // parameters iterator 
            Iterator it = params.iterator(); 
             
            // parameter index 
            int index = 1; 
            while(it.hasNext()) { 
                 
                Object obj = it.next(); 
                // if null set "" 
                if (obj == null) { 
                    pstmt.setObject(index, ""); 
                } else { 
                    // else set object 
                    pstmt.setObject(index, obj); 
                } 
                 
                //next index 
                index++; 
            } 
        } 
    } catch (SQLException ex) { 
        throw new DaoException("can not apply parameter", ex); 
    } 



接着我们继续说我们的查询方法,有了上述两个方法,我们的查询方法就非常简单了:
Java代码 
public List query(String sql, List params) throws DaoException { 
        List result = null; 
        PreparedStatement pstmt = null; 
        ResultSet rs = null; 
        try { 
            pstmt = conn.prepareStatement(sql); 
            this.apply(pstmt, params); 
            rs = pstmt.executeQuery(); 
            result = this.convert(rs); 
        } catch (SQLException ex) { 
            throw new DaoException("can not execute query", ex); 
        } finally { 
            if (rs != null) { 
                try { 
                    rs.close(); 
                } catch (SQLException e) { 
                    // nothing 
                } 
            } 
            if (pstmt != null) { 
                try { 
                    pstmt.close(); 
                } catch (SQLException e) { 
                    // nothing 
                } 
            } 
        } 
 
        return result; 
    } 
     


特殊的查询方法(返回单值)
    有时候为了方便使用,我们需要返回单值的产寻方法,例如 select max(id) from table_a, select count(id) from table_b等。以下的代码使用了上述通用的查询方法,代码为:
Java代码 
public Object queryOne(String sql, List params) throws DaoException { 
      List list = this.query(sql, params); 
       
      if(list == null || list.size() == 0) { 
          throw new DaoException("data not exist"); 
      } else { 
          Map record = (Map)list.get(0); 
          if(record == null || record.size() == 0 ) { 
              throw new DaoException("data not exist"); 
          } else { 
              return record.values().toArray()[0]; 
          } 
      } 
  } 
 
   



更新,删除,插入方法
    由于在JDBC中这三个方法都是用了一个execute完成,所以这里我们也使用一个方法来完成这些功能。代码为: 
Java代码 
public int execute(String sql, List params) throws DaoException { 
        int ret = 0; 
        PreparedStatement pstmt = null; 
        try { 
            pstmt = conn.prepareStatement(sql); 
            this.apply(pstmt, params); 
            ret = pstmt.executeUpdate(); 
        }catch(SQLException ex) { 
            throw new DaoException("", ex); 
        } finally { 
            if (pstmt != null) { 
                try { 
                    pstmt.close(); 
                } catch (SQLException e) { 
                    // nothing. 
                } 
            } 
        } 
         
        return ret; 
    } 
     


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

Java代码 
public List[] queryBatch(String[] sqlArray, List[] paramArray) throws DaoException { 
       List rets = new ArrayList(); 
       if(sqlArray.length != paramArray.length) { 
           throw new DaoException("sql size not equal parameter size"); 
       } else { 
           for(int i = 0; i < sqlArray.length; i++) { 
               String sql = sqlArray[i]; 
               List param = paramArray[i]; 
               List ret = this.query(sql, param); 
               rets.add(ret); 
           } 
           return (List[])rets.toArray(); 
       } 
   } 


Java代码 
批处理方法(更新) 

    有些时候需要一次更新多条Sql语句,为了便于操作,添加了批处理更新操作,参看以下代码,为了和更新方法区分,将参数和返回值都改为了数组形式。  
Java代码 
public int[] executeBatch(String[] sqlArray, List[] paramArray) throws DaoException { 
        List rets = new ArrayList(); 
        if(sqlArray.length != paramArray.length) { 
            throw new DaoException("sql size not equal parameter size"); 
        } else { 
            for(int i = 0; i < sqlArray.length; i++) { 
                int ret = this.execute(sqlArray[i], paramArray[i]); 
                rets.add(new Integer(ret)); 
            } 
             
            int[] retArray = new int[rets.size()]; 
            for(int i = 0; i < retArray.length; i++) { 
                retArray[i] = ((Integer)rets.get(i)).intValue(); 
            } 
             
            return retArray; 
        } 
    } 


资源释放
    由于CommonDao有一个Connection的属性,且Connection属于稀缺资源,所以在CommonDao不需要在使用的时候需要显示的关闭Connection。代码如下:
Java代码 
public void close() throws DaoException{ 
       try { 
           if (conn != null && conn.getAutoCommit()) { 
               conn.close(); 
           } else { 
               if(conn == null) { 
                   throw new DaoException("can not close null connection, first new then close"); 
               } else { 
                   throw new DaoException("transaction is running, rollbakc or commit befor close please."); 
               } 
           } 
       } catch (SQLException ex) { 
           throw new DaoException("Can not close common dao"); 
       } 
   } 

JDBC工具类(JDBCUtil Class)
    在上述的代码中我们看到有很多的无用的处理,例如:
Java代码 
if (pstmt != null) { 
    try { 
        pstmt.close(); 
    } catch (SQLException e) { 
        // nothing. 
    } 



  为什么要有这些处理呢?说先这些处理发生的位置都是在正常处理完成之后,这些处理(例如pstmt.close())即使失败也没有影响,这个时候我们需 要做上述的无用处理,这正是JDBC API的一个小小的瑕疵。我们通常使用一个特殊的静态工具来来做补充,例如:
Java代码 
package com.jpleasure.jdbc.dao; 
 
import java.sql.Connection; 
import java.sql.PreparedStatement; 
import java.sql.ResultSet; 
import java.sql.SQLException; 
 
public class JDBCUtil { 
    public void safelyClose(Connection conn) { 
        if(conn != null) { 
            try { 
                conn.close(); 
            } catch (SQLException e) { 
                //  
            } 
        } 
    } 
    public void safelyClose(PreparedStatement pstmt) { 
        if(pstmt != null) { 
            try { 
                pstmt.close(); 
            } catch (SQLException e) { 
                //  
            } 
        } 
    } 
    public void safelyClose(ResultSet rs) { 
        if(rs != null) { 
            try { 
                rs.close(); 
            } catch (SQLException e) { 
                //  
            } 
        } 
    } 

  


Java代码 
异常处理 

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

Java代码 
package com.jpleasure.jdbc.dao; 
 
public class DaoException extends Exception { 
 
    public DaoException() { 
        super(); 
    } 
 
    public DaoException(String message, Throwable cause) { 
        super(message, cause); 
    } 
 
    public DaoException(String message) { 
        super(message); 
    } 
 
    public DaoException(Throwable cause) { 
        super(cause); 
    } 
     

你可能感兴趣的:(jdbc)