DbUtils源码阅读二:DbUtils与QueryLoader

1 DbUtils


1.1 DbUtils类
DbUtils类是一个final类,提供了关于JDBC操作的静态方法,如关闭数据库资源,加载数据库驱动,事务操作和打印信息。工具类是不应该有public或default构造方法,在DbUtils中却提供了:
public DbUtils() {}
说是为了向老版本兼容。

1.2 数据库资源操作
1.2.1 关闭数据库资源的操作分为两种:一种向上抛出异常,另一种是捕获异常后不做处理。以Connection关闭为例: 
public static void close(Connection conn) throws SQLException {
    if (conn != null) { // 先判断是否为null,防止抛出空指针异常。
        conn.close();
    }
}
public static void closeQuietly(Connection conn) {
    try {
        close(conn); // 调用会抛异常的方法
    } catch (SQLException e) {
        // 不对异常进行处理。
    }
}
1.2.2 一起关闭Connection、Statement和ResultSet资源
public static void closeQuietly(Connection conn, Statement stmt, ResultSet rs) {
        // try-finally结构,依次关闭rs, stmt, conn。
    	try {
            closeQuietly(rs);
        } finally {
            try {
                closeQuietly(stmt);
            } finally {
                closeQuietly(conn);
            }
        }
    }
代码中使用到了嵌套式的try-finally语句块,保证了Connection等资源将能够得到关闭。 建议独立使用try-catch和try-finally语句块。
以IO流操作为例,
1) 不独立使用的情况下
InputStream is = ...;
try {
    is.available();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (is != null) {
        try {
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2) 独立使用的情况下
InputStream is = ...;
try {
    try {
        is.available();
    } finally {
        if (is != null) {
            is.close();
        }
    }
} catch (IOException e) {
    e.printStackTrace();
}
前者,在finally语句块中,当调用is.close()需要另外一个try-catch来处理异常。而后者的finally语句块中, 调用is.close()所抛出的异常将被外层的try-catch来处理。并且,外层try语句块确保关闭输入流,而外层try语句块将报告所出现的异常,也包括了finally子句中出现的。

1.3 事务操作
1.3.1 提交事务操作
public static void commitAndClose(Connection conn) throws SQLException {
    if (conn != null) {
        try {
            conn.commit();
        } finally { // 无论commit是否成功,都将关闭连接。
            conn.close();
        }
    }
}
1.3.2 回滚事务操作
public static void rollback(Connection conn) throws SQLException {
    if (conn != null) {
        conn.rollback();
    }
}

public static void rollbackAndClose(Connection conn) throws SQLException {
    if (conn != null) {
        try {
            conn.rollback();
        } finally {
            conn.close();
        }
    }
}
都判断了传入进来的Connection是否为空,然后在调用Connection对象的commit()或rollback()方法,选择使用try-finally语句块关闭Connection资源,并向上抛出异常。关于事务还有一组对捕获的异常不做处理的操作,如:
public static void rollbackAndCloseQuietly(Connection conn) {
    try {
        rollbackAndClose(conn);
    } catch (SQLException e) {
        // 不做处理
    }
}
1.4 加载数据库驱动
public static boolean loadDriver(String driverClassName) {
    // 使用加载DbUtils类的类加载器。
    return loadDriver(DbUtils.class.getClassLoader(), driverClassName);
}
// 提供一个类加载器
public static boolean loadDriver(ClassLoader classLoader, String driverClassName) {
    try {
        classLoader.loadClass(driverClassName).newInstance(); // 创建Class对象所对应的类的一个实例。
        return true;
    } catch (IllegalAccessException e) {
        // 若没有默认的构造方法,还是适用于DriverManager的约定,如:Class.forName(driverClassName);所以返回true。
        return true;
    } catch (Exception e) {
        return false;
    }
}
1.5 打印栈信息
1.5.1 打印异常栈信息
public static void printStackTrace(SQLException e) {
    printStackTrace(e, new PrintWriter(System.err)); // 使用System.err作为目的地,并将其包装成一个PrintWriter。
}

public static void printStackTrace(SQLException e, PrintWriter pw) {
    SQLException next = e; // 为传入进来的异常对象取个别名,使其适用于下面的while循环。
    while (next != null) { // while循环的终止条件为next == null。
        next.printStackTrace(pw); // 将堆栈信息打印到pw。
        next = next.getNextException(); // 获取当前异常对象的下一个异常对象
        if (next != null) {
            pw.println("Next SQLException:"); // 输出Tip
        }
    }
}
1.5.2 打印警告信息
由于SQLWarning是SQLException的子类,所以调用了 printStackTrace方法
public static void printWarnings(Connection conn) {
    printWarnings(conn, new PrintWriter(System.err));
}

public static void printWarnings(Connection conn, PrintWriter pw) {
    if (conn != null) {
        try {
            printStackTrace(conn.getWarnings(), pw);
        } catch (SQLException e) {
            printStackTrace(e, pw); // 打印conn.getWarnings()抛出的异常栈信息
        }
    }
}

2 QueryLoader

该类负责将SQL语句注册到内存中,并保证不会重复注册相同SQL语句。
该类实现从.properties文件中加载SQL语句并映射到Map中。
该类提供了protected的构造方法和protected的loadQueries(String path)方法,所以可以从该类扩展出不同的实现。
protected QueryLoader() {}
...
protected Map<String, String> loadQueries(String path) throws IOException {...}
2.1 饿汉式单例类
private static final QueryLoader instance = new QueryLoader();

public static QueryLoader instance() {
    return instance;
}
2.2 存放SQL语句块的Map类型final成员属性
private final Map<String, Map<String, String>> queries = new HashMap<String, Map<String, String>>();
外层Map的key是存放文件的路径,而value是内层的Map,内层Map存放每一条SQL语句的key和value。Map可以保证key是唯一的。

2.3 默认实现
protected Map<String, String> loadQueries(String path) throws IOException {
    // 将classpath中的资源文件读入到输入流对象中。路径字符串格式应该以"/"打头
    InputStream in = getClass().getResourceAsStream(path);
    if (in == null) {
        throw new IllegalArgumentException(path + " not found.");
    }
    Properties props = new Properties();
    try {
        props.load(in);
    } finally {
        in.close();
    }
    // 将Properties对象直接拷贝给HashMap,为了更好的性能保证。
    @SuppressWarnings({ "rawtypes", "unchecked" })
    HashMap<String, String> hashMap = new HashMap(props); // loadQueries()的每一次调用都会创建出一个新的HashMap对象。
    return hashMap;
}
2.4 加载
public synchronized Map<String, String> load(String path) throws IOException {
    Map<String, String> queryMap = this.queries.get(path); // 试着到成员属性queries中取
    // 判断是否已经存在。
    if (queryMap == null) {
        queryMap = this.loadQueries(path); // 使用默认的实现将SQL语句加载到Map中
        this.queries.put(path, queryMap); // 放入queries中
    }
    return queryMap;
}
2.5 卸载
public synchronized void unload(String path) {
    this.queries.remove(path); // 从成员属性queries中删除即可
}
加载与卸载方法都加上了synchronized关键字,适应了多线程环境,避免被重复加载或卸载。

转载于:https://my.oschina.net/belinwu/blog/110386

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值