JDBC学习(十)完善jdbc工具类

前面九节JDBC的学习,知识点已经很丰富了,现在将这些应用起来,完善JDBC工具类。

之前几节有对JDBCUtils工具类介绍:

1.0版:JDBC学习(八)数据库连接池

2.0版:JDBC学习(九)dbUtils原理

这次是3.0版

首先导jar包:

c3p0-0.9.2-pre1.jar

mchange-commons-0.2.jar

mysql-connector-java-5.1.46.jar

commons-dbutils-1.4.jar

c3p0配置文件(src目录下)

<?xml version="1.0" encoding="UTF-8"?>

<c3p0-config>
    <!-- 这是默认配置信息 -->
    <default-config>
        <!-- 连接四大参数配置 -->
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/sql_test</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="user">root</property>
        <property name="password">123456</property>
        <!-- 池参数配置 -->
        <property name="acquireIncrement">3</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">2</property>
        <property name="maxPoolSize">10</property>
    </default-config>

</c3p0-config>

这里以转账案例来展开:

在2.0的基础上,我们需要实现,在业务层(service)不能出现Connection连接对象:

  • jdbcUtils.java中添加事务操作
  • Dao层使用事务时,保证是jdbcUtils里面创建的。

JDBCUtils.java

/**
 * 使用c3p0连接池
 */
public class JDBCUtils {

    //创建一个连接池对象,这里在配置文件中设置参数 c3p0-config.xml
    private static ComboPooledDataSource cpds=new ComboPooledDataSource();

    //事务专用连接
    private static Connection cn=null;



    /**
     * 返回连接对象
     * @return
     * @throws SQLException
     */
    public static Connection getConnction() throws SQLException {
        
        //如果cn不为null,表示已经开启了一个事务,直接返回这个连接就行
        if (cn != null) {
            return cn;
        }
        return  cpds.getConnection();
    }

    /**
     * 返回连接池对象
     * @return
     */
    public static ComboPooledDataSource getDataSource(){
        return cpds;
    }

    /**
     * 开启事务
     * 1、创建一个连接,并开启事务:cn.setAutoCommit(false);
     * 2、这个连接需要给dao层用
     * 3、这个连接还要给commitTransaction或rollbackTransaction用
     */
    public static void beginTransaction() throws SQLException {

        //如果事务已经存在,不能重复开启
        if (cn != null) {
            throw new SQLException("事务已经开启,不需要重复开启!");
        }
        //创建连接对象
        cn=getConnction();
        //开启事务
        cn.setAutoCommit(false);
        
    }

    /**
     * 提交事务
     */
    public static void commitTransaction() throws SQLException {

        if (cn == null) {
            throw new SQLException("未开启事务,不可以提交!");
        }
        //提交事务
        cn.commit();
        //关闭连接
        cn.close();
        //将cn置为null,防止其他事务使用
        cn=null;

       
    }

    /**
     * 回滚事务
     */
    public static void rollbackTransaction() throws SQLException {
       
        if (cn == null) {
            throw new SQLException("未开启事务,不可以回滚!");
        }
        //回滚事务
        cn.rollback();
        //关闭连接
        cn.close();
        //置空
        cn=null;

    }
}

Dao层:AccountDaoQuery .java

/**
 * 转账数据库操作
 */
public class AccountDaoQuery {

    public void updateAccount(String name,int money) throws SQLException {

        //使用dbutils工具
        QueryRunner qr=new QueryRunner();
        //创建sql模板,通过名字查询记录,并修改与余额
        String sql="update account set balance=balance+? where name=?";
        //参数
        Object [] params={money,name};

        Connection cn=JDBCUtils.getConnction();
        //执行
        qr.update(cn,sql,params);

    }
}

测试:Demo1.java

ublic class Demo1 {

    //依赖dao层
    AccountDaoQuery accountDAOQuery =new AccountDaoQuery();

    @Test
    public void func1() throws SQLException {

        try{
            //开启事务
            JDBCUtils.beginTransaction();

            //两部完成转账
            accountDAOQuery.updateAccount("lisi",-100);
            accountDAOQuery.updateAccount("zhangsan",100);
            //结束事务
            JDBCUtils.commitTransaction();
        }catch (Exception e){
            //回滚事务
            JDBCUtils.rollbackTransaction();
        }

    }
}

完成上面的功能后,我们思考:如果是事务的连接,提交或是回滚后就会关闭。但是如果是普通的连接,上面的代码没有关闭(归还给数据库连接池),这样会造成资源浪费。所以我们需要在dao层操作完毕后判断如果是普通连接就关闭

对于连接是否是事务的,JDBCUtils来操作更合理:

改进:JDBCUtils.java

/**
 * 使用c3p0连接池
 */
public class JDBCUtils {

    //创建一个连接池对象,这里在配置文件中设置参数 c3p0-config.xml
    private static ComboPooledDataSource cpds=new ComboPooledDataSource();

    //事务专用连接
    private static Connection cn=null;



    /**
     * 返回连接对象
     * @return
     * @throws SQLException
     */
    public static Connection getConnction() throws SQLException {
        
        //如果cn不为null,表示已经开启了一个事务,直接返回这个连接就行
        if (cn != null) {
            return cn;
        }
        return  cpds.getConnection();
    }

    /**
     * 返回连接池对象
     * @return
     */
    public static ComboPooledDataSource getDataSource(){
        return cpds;
    }

    /**
     * 开启事务
     * 1、创建一个连接,并开启事务:cn.setAutoCommit(false);
     * 2、这个连接需要给dao层用
     * 3、这个连接还要给commitTransaction或rollbackTransaction用
     */
    public static void beginTransaction() throws SQLException {

        //如果事务已经存在,不能重复开启
        if (cn != null) {
            throw new SQLException("事务已经开启,不需要重复开启!");
        }
        //创建连接对象
        cn=getConnction();
        //开启事务
        cn.setAutoCommit(false);
        
    }

    /**
     * 提交事务
     */
    public static void commitTransaction() throws SQLException {

        if (cn == null) {
            throw new SQLException("未开启事务,不可以提交!");
        }
        //提交事务
        cn.commit();
        //关闭连接
        cn.close();
        //将cn置为null,防止其他事务使用
        cn=null;

       
    }

    /**
     * 回滚事务
     */
    public static void rollbackTransaction() throws SQLException {
       
        if (cn == null) {
            throw new SQLException("未开启事务,不可以回滚!");
        }
        //回滚事务
        cn.rollback();
        //关闭连接
        cn.close();
        //置空
        cn=null;

    }

    /**
     * 释放非事务连接对象
     * @param connection
     */
    public static void releaseConnection(Connection connection) throws SQLException {
        /**
         * 判断传入的连接是否是事务连接
         * 1、如果连接为null,则关闭连接:因为null一定不是事务
         * 2、如果传入的连接不等于cn,关闭连接
         */
        if (connection == null) {
            connection.close();
        }
        if(connection!=cn) connection.close();
    }
}

这样在dao层,的updateAccount方法最后添加:

//释放连接
JDBCUtils.releaseConnection(cn);

现在我们进一步思考:在dao层中每次操作都需要连接,并且完了都要判断释放连接,代码重复太多。如果我们能重写apache的DBUtils工具类里面没有连接对象作为参数的方法,把创建连接和释放连接放进去:

继承QueryRunner:TrQueryRunner.java

/**
 * 自定义TrQueryRunner,继承QueryRunner,实现其中没有连接对象参数的函数
 * 实现自动获取连接和释放连接
 */
public class TrQueryRunner extends QueryRunner {

    @Override
    public int[] batch(String sql, Object[][] params) throws SQLException {
        //得到连接对象
        Connection cn=JDBCUtils.getConnction();
        //调用含有连接对象参数的函数
        int []result=batch(cn,sql,params);
        //释放连接
        JDBCUtils.releaseConnection(cn);
        return result;
    }

    @Override
    public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
        //得到连接对象
        Connection cn=JDBCUtils.getConnction();
        //调用含有连接对象参数的函数
        T result=query(cn,sql,rsh,params);
        //释放连接
        JDBCUtils.releaseConnection(cn);
        return result;
    }

    @Override
    public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException {
        //得到连接对象
        Connection cn=JDBCUtils.getConnction();
        //调用含有连接对象参数的函数
        T result=query(cn,sql,rsh);
        //释放连接
        JDBCUtils.releaseConnection(cn);
        return result;
    }

    @Override
    public int update(String sql) throws SQLException {
        //得到连接对象
        Connection cn=JDBCUtils.getConnction();
        //调用含有连接对象参数的函数
        int result=update(cn,sql);
        //释放连接
        JDBCUtils.releaseConnection(cn);
        return result;
    }

    @Override
    public int update(String sql, Object param) throws SQLException {
        //得到连接对象
        Connection cn=JDBCUtils.getConnction();
        //调用含有连接对象参数的函数
        int result=update(cn,sql,param);
        //释放连接
        JDBCUtils.releaseConnection(cn);
        return result;
    }

    @Override
    public int update(String sql, Object... params) throws SQLException {
        //得到连接对象
        Connection cn=JDBCUtils.getConnction();
        //调用含有连接对象参数的函数
        int result=update(cn,sql,params);
        //释放连接
        JDBCUtils.releaseConnection(cn);
        return result;
    }
}

应用改进后的TrQueryRunner

转账dao层:AccountDaoTr.java

public class AccountDaoTr {
    public void updateAccount(String name,int money) throws SQLException {

        //使用自定义的TrQueryRunner类
        QueryRunner qr=new TrQueryRunner();
        String sql="update account set balance=balance+? where name=?";
        //参数
        Object [] params={money,name};
        //执行重写的update方法,内部已经创建了连接对象
        qr.update(sql,params);
    }
}

测试类:Demo2.java

public class Demo2 {

    AccountDaoTr accountDAOTr =new AccountDaoTr();

    @Test
    public void func1() throws SQLException {

        try{
            JDBCUtils.beginTransaction();
            accountDAOTr.updateAccount("lisi",-100);
            accountDAOTr.updateAccount("zhangsan",100);
            JDBCUtils.commitTransaction();
        }catch (Exception e){
            JDBCUtils.rollbackTransaction();
        }

    }
}

上面的JDBCUtils工具不能满足多线程的任务,我们应用ThreadLocal来改进

改进:JDBCUtils.java

/**
 * 使用c3p0连接池
 */
public class JDBCUtils {

    //创建一个连接池对象,这里在配置文件中设置参数 c3p0-config.xml
    private static ComboPooledDataSource cpds=new ComboPooledDataSource();

    //通过将Connection放在ThreadLocal中,来满足多线程操作
    private static ThreadLocal<Connection> tl=new ThreadLocal<Connection>();



    /**
     * 返回连接对象
     * @return
     * @throws SQLException
     */
    public static Connection getConnction() throws SQLException {
        //首先从ThreadLocal中获取连接对象
        Connection cn=tl.get();
        //如果cn不为null,表示已经开启了一个事务,直接返回这个连接就行
        if (cn != null) {
            return cn;
        }
        return  cpds.getConnection();
    }

    /**
     * 返回连接池对象
     * @return
     */
    public static ComboPooledDataSource getDataSource(){
        return cpds;
    }

    /**
     * 开启事务
     * 1、创建一个连接,并开启事务:cn.setAutoCommit(false);
     * 2、这个连接需要给dao层用
     * 3、这个连接还要给commitTransaction或rollbackTransaction用
     */
    public static void beginTransaction() throws SQLException {

        Connection cn=tl.get();
        //如果事务已经存在,不能重复开启
        if (cn != null) {
            throw new SQLException("事务已经开启,不需要重复开启!");
        }
        //创建连接对象
        cn=getConnction();
        //开启事务
        cn.setAutoCommit(false);
        //将创建的事务连接放入threadLocal中
        tl.set(cn);
    }

    /**
     * 提交事务
     */
    public static void commitTransaction() throws SQLException {

        Connection cn=tl.get();
        if (cn == null) {
            throw new SQLException("未开启事务,不可以提交!");
        }
        //提交事务
        cn.commit();
        //关闭连接
        cn.close();
        //将cn置为null,防止其他事务使用
        //cn=null;

        //现在只需要从threadLocal中移除即可
        tl.remove();
    }

    /**
     * 回滚事务
     */
    public static void rollbackTransaction() throws SQLException {
        Connection cn=tl.get();
        if (cn == null) {
            throw new SQLException("未开启事务,不可以回滚!");
        }
        //回滚事务
        cn.rollback();
        //关闭连接
        cn.close();
        //置空
        //cn=null;

        tl.remove();
    }

    /**
     * 释放非事务连接对象
     * @param connection
     */
    public static void releaseConnection(Connection connection) throws SQLException {
        Connection cn=tl.get();
        /**
         * 判断传入的连接是否是事务连接
         * 1、如果连接为null,则关闭连接:因为null一定不是事务
         * 2、如果传入的连接不等于cn,关闭连接
         */
        if (connection == null) {
            connection.close();
        }
        if(connection!=cn) connection.close();
    }
}

猜你喜欢

转载自blog.csdn.net/SICAUliuy/article/details/88919878