`

[zt]JDBC DAO设计

阅读更多

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的代码示例:

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;
    }
}

    如果需要从开发模式变为运用模式,只需要将上述代码修改为:
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的一些属性,也可以在上述代码中设定,例如:
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的属性实例化,例如:

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实例。
    第一种方法代码为:
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表示数据库字段的内容。代码为:
        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上面,参看以下的共通方法:
    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);
        }
    }

    接着我们继续说我们的查询方法,有了上述两个方法,我们的查询方法就非常简单了:
    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等。以下的代码使用了上述通用的查询方法,代码为:
    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完成,所以这里我们也使用一个方法来完成这些功能。代码为:
    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方法做区分,将参数和返回值都改为了数组形式。

    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();
        }
    }


    批处理方法(更新)
    有些时候需要一次更新多条Sql语句,为了便于操作,添加了批处理更新操作,参看以下代码,为了和更新方法区分,将参数和返回值都改为了数组形式。
    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。代码如下:
    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)
    在上述的代码中我们看到有很多的无用的处理,例如:
            if (pstmt != null) {
                try {
                    pstmt.close();
                } catch (SQLException e) {
                    // nothing.
                }
            }
    为什么要有这些处理呢?说先这些处理发生的位置都是在正常处理完成之后,这些处理(例如pstmt.close())即使失败也没有影响,这个时候我们需 要做上述的无用处理,这正是JDBC API的一个小小的瑕疵。我们通常使用一个特殊的静态工具来来做补充,例如:

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

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);
    }
   
}

 

分享到:
评论

相关推荐

    RS485通信芯片zt13085e的原理图库和PCB库

    ZT13085E的设计目标是提供一种高效、可靠的数据传输方式,尤其是在长距离和噪声环境下的通信。 首先,我们来了解一下RS485标准。RS485是EIA(Electronic Industries Association)制定的一种电气接口标准,它允许在...

    zt411-zt421-ug-zhcn_ZT411/ZT421_斑马打印机手册_

    斑马打印机ZT411和ZT421是工业级条码和标签打印机,广泛应用于制造业、物流、零售业等对打印质量和效率有高要求的领域。这两款打印机以其出色的性能、耐用性和易用性赢得了用户的信赖。这份中文手册详细地介绍了ZT...

    ZT213/ZT213LEEA规格书V2.10-低功耗RS232多通道USB收发器/驱动器芯片手册

    ZT213/ZT213LEEA是一款专为低功耗应用设计的RS232多通道USB收发器和驱动器芯片,广泛应用于数据通信、工业控制、物联网设备等领域。本文将深入探讨其规格参数、功能特点、选型指南及应用实例,帮助读者全面了解这款...

    hive jdbc 连接所需要的jar包

    Hive JDBC连接是Java应用程序与Hive数据仓库进行交互的一种方式。它允许程序通过标准的JDBC接口查询和操作Hive中的数据。在使用Hive JDBC进行连接时,需要依赖一系列的jar包来确保所有必要的功能得以实现。下面将...

    ZT7548 Datasheet Rev.1.0.3.pdf

    ZT7548的设计目标是实现混合触摸功能,具备自我和互电容感应技术,从而提供增强的防水、悬停、高电压感应、手套触摸和增强的触控笔检测能力。此外,该触控IC支持CDMS(SF)增强SF技术和高信噪比(SNR)。 ZT7548...

    ZT单片机课程设计出租车计价器.doc

    该文档是关于ZT单片机课程设计的一个项目——出租车计价器的详细说明。这个设计旨在模拟实际的出租车计价过程,通过嵌入式系统实现里程计算、价格计算和LED显示等功能。以下是对各部分的详细解释: 1. **设计任务及...

    Zebra ZT230 条码打印机驱动

    本驱动程序专为Zebra的ZT210、ZT220及ZT230系列打印机设计,提供了全面的功能支持,以实现高质量的条形码、二维码以及其他标签打印。 1. **驱动程序安装** - `zd51166465-certified.exe` 是Zebra ZT230系列打印机...

    斑马zt410中文库

    "斑马zt410中文库"指的是为ZT410打印机特别设计的中文字符集和字体库,确保在打印中文标签时能够清晰、准确地输出汉字。 1. **斑马ZT410打印机特性** - 高性能:ZT410采用强大的32位处理器,提供快速打印速度,...

    ZT410打印机IP地址设置网络打印机

    ZT410打印机IP地址设置网络打印机ZT410打印机IP地址设置网络打印机ZT410打印机IP地址设置网络打印机ZT410打印机IP地址设置网络打印机ZT410打印机IP地址设置网络打印机

    斑马打印机ZT210用户指南

    "斑马打印机ZT210用户指南" 本用户指南旨在为Zebra ZT210/ZT220/ZT230打印机的用户提供操作和维护指南,该设备是一种工业级的条码打印机,具有高速打印、高速处理和高质量打印输出等特点。 版权信息 本手册的版权...

    斑马zt210打印机驱动 v5.1.07.5146 官方版

    斑马zt210是一款专为中国市场设计的工业条码打印机,非常适合不需要频繁更换标签的条码标签应用。这里给大家提供斑马zt210驱动下载,推荐有需要的用户下载安装。斑马zt210打印机优势:◆ 节省空间* 小巧紧凑和流线型...

    斑马ZT510打印机驱动文件

    斑马ZT510打印机驱动文件

    斑马打印机(ZT210).docx

    斑马打印机ZT210是一款专业的工业级条形码和标签打印机,广泛应用于物流、零售、医疗等行业的标签制作。以下是对如何设置和使用斑马ZT210打印机的详细步骤: 首先,我们需要安装电脑驱动。双击下载好的驱动程序文件...

    证通ZT598金属键盘开发资料.rar

    证通ZT598金属键盘设计坚固耐用,通常用于银行自助服务终端、ATM机、POS机以及各类安全认证设备。其采用金属材质,增加了物理防护性,能够有效防止恶意破坏和物理攻击。此外,键盘内部可能集成了加密芯片,确保数据...

    zt-exec-1.9-API文档-中文版.zip

    赠送jar包:zt-exec-1.9.jar; 赠送原API文档:zt-exec-1.9-javadoc.jar; 赠送源代码:zt-exec-1.9-sources.jar; 赠送Maven依赖信息文件:zt-exec-1.9.pom; 包含翻译后的API文档:zt-exec-1.9-javadoc-API文档-...

    zebra ZT400系列打印机技术手册

    ### zebra ZT400系列打印机技术手册 #### 知识点概述: 1. **版权及法律声明**:Zebra ZT400系列打印机技术手册的版权及相关软件固件的所有权归属ZIH Corp.及其许可证持有者,未经授权复制会受到法律制裁。 2. **...

    zt云流量HTML响应式模板

    "zt云流量HTML响应式模板"是一个专为网站设计的专业工具,它采用了现代网页设计的重要技术——HTML响应式布局。这种模板旨在为不同设备提供优化的用户体验,无论用户是在桌面电脑、平板还是智能手机上访问,都能呈现...

    众仪ZT-X万用表30分钟关机数据

    众仪ZT-X万用表30分钟关机数据,这是一款反人类设计的万用表.

Global site tag (gtag.js) - Google Analytics