简述JDBC(BaseDao源码级)

一、 定义

1. 什么是JDBC?

jdbc是一种由一组用Java语言编写的标准类和标准接口组成,用于执行SQL语句的Java API。JDBC提供了面向多种关系型数据库连接的统一访问,同时统一规范了标准接口和工具,使数据库开发人员能够编写数据库应用程序,实现了所有这些面向标准的目标并且具有简单,严格类型定义且高性能实现的接口。

2. 具体实现

如下图,我们很明显的看出:java应用程序是不能直接访问关系型数据库的,必须要通过相应的数据库驱动程序,通过驱动程序去和数据库打交道。而所谓的数据库驱动,实际上是java中抽象工厂的一个具体实例。其实也就是通过JDBC API接口中的抽象工厂来生产各种关系数据库的抽象接口数据库驱动厂商JDBC DriverManager(对Connection等接口的实现类的jar文件),来分别具体生产实现各种关系型数据库的驱动接口,从而使得程序员可向相应数据库发送SQL调用。同时,将Java语言和JDBC结合起来使程序员不必为不同的平台编写不同的应用程序,只须写一遍程序就可以让它在任何平台上运行,这也是Java语言“编写一次,处处运行”的优势
在这里插入图片描述

二、流程原理四大接口

1. Driver接口

在Java程序中,连接数据库,必须先装载特定厂商的数据库驱动程序,不同的数据库有不同的装载方法。我们直接使用Driver接口(由数据库厂家提供),可以通过 “Class.forName(“指定数据库的驱动程序”)” 方式来加载添加到开发环境中的驱动程序,例如:
装载MySql驱动:Class.forName(“com.mysql.jdbc.Driver”);
装载Oracle驱动:Class.forName(“oracle.jdbc.driver.OracleDriver”);

实际上,JDBC 源码中第一句 的 class.forName(),做了很多的事情, 在jdk 中,只有Driver 的一个接口,没有一个具体实现实例,就是说mysql-connector-java.jar 这种类似的 jar 需要第三方去实现,具体的Driver 实现类是在第三方的jar里面的。那么它是怎么实现的呢?在 class.forName 里面,指定加载 第三方的 Driver接口,那么在类加载的时候,可以完成 jdk 中 Driver 接口, 在 第三方jar 中具体实现了class(第三方 的jar里面,存在一个 配置文件,指向了在 第三方 jar 中具体实现了 jdk 中 sql 包下的 Driver 接口的 类),同时Driver 接口中使用了泛型,这样就可以通过反射的方式获取到 第三方中具体实现类,同时解析SQL语句不同类型的结果集,映射为不同的实体类型。
本文最后,本人写了一个小型配置文件的BaseDao,来封装实现了mysql的SQL语句在java中的实现细节,仅供参考,来理解JDBC具体实现的原理。

2. Connection接口
  • Connection是java与特定关系数据库建立的连接(会话),是连接执行SQL语句返回结果集的前提。DriverManager.getConnection(url, user, password)方法建立在JDBC URL中定义的数据库Connection连接上。
      连接MySql数据库:Connection con = DriverManager.getConnection(“jdbc:mysql://host:port/database”, “user”, “password”);
      连接Oracle数据库:Connection con = DriverManager.getConnection(“jdbc:oracle:thin:@host:port:database”, “user”, “password”);
  • 常用方法:
    createStatement():创建向数据库发送sql的statement对象。
    prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
    prepareCall(sql):创建数据库发送执行存储过程的callableStatement对象。
    setAutoCommit(boolean autoCommit):设置事务是否自动提交。
    commit() :在链接上提交事务。
    rollback() :在此链接上回滚事务。
3. Statement接口
  • Statement接口是用于执行静态SQL语句并返回它所生成结果的对象,默认情况下,每个SQL语句同时只有一个ResultSet对象。它有以下三大接口,这三大接口也是JDBC的核心所在。
    • Statement:由createStatement创建,用于发送简单的SQL语句(不带参数)。存在sql注入的危险(String sql = “delete from table where id=” + id;如果用户传入的id为“5 or 1=1”,那么将删除表中的所有记录),不适用于多条相同的SQL语句。
    • PreparedStatement:继承自Statement接口,由preparedStatement创建,用于发送含有一个或多个参数的SQL语句。PreparedStatement对象比Statement对象的效率更高,并且可以防止SQL注入,是一种预编译SQL语句执行对象的接口,是一种类似于JQuery模板的方式,所以我们一般都使用PreparedStatement
    • CallableStatement:继承自PreparedStatement接口,由方法prepareCall创建,用于调用存储过程和自定义函数。
  • 常用Statement方法:
    execute(String sql):运行语句,返回是否有结果集
    executeQuery(String sql):运行select语句(DQL语句),返回ResultSet结果集。
    executeUpdate(String sql):运行insert/update/delete(DML语句),返回更新的行数(执行成功的话,大于0)。当然也可以用于运行create/drop/alter(DDL语句),返回更新的行数(执行成功的话,结果等于0)。

    addBatch(String sql) :把多条sql语句放到一个批处理中。
    executeBatch():向数据库发送一批sql语句执行。
4. ResultSet接口
  • ResultSet提供操作结果集结构的方法,常用的有
    getMetaData():返回结果集的列(域)的数量、类型和参数的对象
    getRow():返回当前的实体行数
  • ResultSet提供检索不同类型字段的方法,常用的有:
    getString(int index)、getString(String columnName):获得在数据库里是varchar、char等类型的数据对象。
    getObject(int index)、getObject(String columnName):获取在数据库里任意类型的数据。
    mysql以下所有数据类型都有上述两种get方法。mysql数据类型和java数据类型对照如下:
		char/varchar/text				java.lang.String
		tinyint/smallint/int/bigint		java.lang.Byte/Short/Integer/Long
		decimal							java.math.BigDecimal
		bit								java.lang.Boolean
		datetime/timestamp/date			java.sql.Date
  • ResultSet还提供了对结果集进行滚动的方法:
    next():移动到下一行,相当于java集合中的iterator的hasNext()和next()的结合体
    Previous():移动到前一行
    absolute(int row):移动到指定行
    beforeFirst():移动resultSet的最前面。
    afterLast() :移动到resultSet的最后面。
    使用后依次关闭对象及连接:ResultSet → Statement → Connection

三、使用JDBC的步骤

加载JDBC驱动程序 → 建立数据库连接Connection → 创建执行SQL的语句Statement → 处理执行结果ResultSet → 释放资源
下面以java连接mysql数据库为例:

1. 以反射的方式装载驱动
 Class.forName("com.mysql.jdbc.Driver");

原则上必须只装载一次。

2. 建立连接对象Connection
final String URL = "jdbc:mysql://192.168.40.103:3306/school?useUnicode=true&characterEncoding=utf-8&useSSL=true"
                ,USERNAME = "root",
                PASSWORD = "kb08";
        Connection con = DriverManager.getConnection(URL,USERNAME,PASSWORD);

建立连接对象Connection的传入参数必须是常量,事实上,真正的连接对象过程都是写在配置文件里的,所以,当然是常量。
关于URL的结构如下:
协议:子协议://主机IP:端口号/数据库?【参数名:参数值】【其他参数如:统一编码&字符编码格式&加密套接字协议】
备注:【】为可选项,不写,就是默认值。其他为必选项,且顺序必须一致。

3. 创建执行SQL的语句+处理执行结果ResultSet

这里使用的是PreparedStatement预编译SQL语句执行,关于Statement执行SQL语句执行(不实用,且有SQL注入风险)和prepareCall执行自定义函数和存储过程(不常用),这里不做举例了。

		//创建执行对象
        Statement sta = con.createStatement();
        
		 //执行sql命令
        /*非查询操作*/
        final String SQL1 = "insert into deptinfo(deptName) value('市场部')";
        final String SQL2 = "create table deptinfo";
        int rst1 = sta.executeUpdate(SQL1);
        int rst2 = sta.executeUpdate(SQL2);
        System.out.println(rst1);//结果1,DML语句成功执行,返回1
        System.out.println(rst2);//结果0,DDL语句成功执行,返回0
        
        /*查询操作*/
        //返回结果集
        final String SQL3 = "select * from deptinfo";
        ResultSet rst3 = sta.executeQuery(SQL);
        while(rst3.next()){
            System.out.print(rst3.getInt(1)+"\t");
            System.out.println(rst3.getString("deptName"));
        }

		//占位符的使用
		 final String SQL = "select * from empinfo where deptId=?";
        PreparedStatement pst = con.prepareStatement(SQL);
        pst.setObject(1,3);

        ResultSet rst = pst.executeQuery();
        ResultSetMetaData struct = rst.getMetaData();
        String[] arr = new String[struct.getColumnCount()];
        while (rst.next()) {
            for (int i = 1; i <= arr.length; i++) {
                arr[i - 1] = rst.getObject(i).toString();
            }
            System.out.println(MessageFormat.format("{0}\t{1}\t{2}\t{3}", arr));
        }

占位符,其实非常像JQuery中的模板字符串的形式,同时也可以理解为java中String的Message.format()的填坑操作。

4. 释放资源
 		PoolUtil.close(rst);
        PoolUtil.close(pst);
        PoolUtil.close(con);

这里的释放资源我调用了我自己封装的关闭资源的工具类PoolUtil进行释放资源。

四、综合运用(BaseDao搭建)

BaseDao,就是数据库访问对象的工具类,它广泛应用于mysql通过JDBC在java平台中SQL语句的执行,它对java执行SQL语句的过程进行了封装,方便多个数据库连接对象同时多并发地去调用该工具类去执行多条SQL语句(包含基本的增删改查、自定义函数和存储过程的执行),是一款功能强大的工具类。本人搭建了一个简单的BaseDao,供大家参考。

1. 设计难点

1、查询操作:通过自定义类型SelRtn反馈【是否存在异常】和查询结果
2、BaseDao对象的管理缺失(不停的创建):单例工厂的模式
3、连接字符串管理:通过properties小型配置文件
4、并发管理:通过弹性线程池ConPool对多并发和Connection对象进行管理

2. 具体实现
  1. properties小型配置文件(config/sys.properties)
    properties小型配置文件实际存储的是键值对形式的配置内容,下面会详说。mysql和连接池对象poolCon的配置如下:
#mysql配置
#mysql 驱动装载配置
mysql01.driver=com.mysql.jdbc.Driver
#mysql url配置(数据库地址)
mysql01.url=jdbc:mysql://192.168.40.103:3306/school?useUnicode=true&characterEncoding=utf-8&userSSL=true
#mysql 连接对象用户名
mysql01.username=root
#mysql 连接对象用户密码
mysql01.password=kb08

#连接池配置
#核心连接池数
pool.coreCount=3
#最大连接池数
pool.maxCount=10
#最大等待时间(s)
pool.maxWait=5
#连接池最大空闲时间(s)
pool.maxIdle=30
#重新尝试连接间隔时间(ms)
pool.retryInterval=50
#重新尝试连接最大次数
pool.maxRetryCount=8
#连接池异常信息    0:无异常   1:异常
pool.exitOnErr=0
  1. 系统级常量PoolConstant(使用枚举也行)
    PoolConstant类将 properties小型配置文件的每个配置信息进行了封装,此外也封装了POOL和MYSQL两大类的配置信息,方便之后调用。
package cn.kgc.kb08.jdbc.dao3.impl;

public interface PoolConstant {
    String POOL_CORE_COUNT = "coreCount";
    String POOL_MAX_COUNT = "maxCount";
    String POOL_MAX_WAIT = "maxWait";
    String POOL_MAX_IDLE = "maxIdle";
    String POOL_RETRY_INTERVAL = "retryInterval";
    String POOL_MAX_RETRY_COUNT = "maxRetryCount";
    String POOL_EXIT_ON_ERR = "exitOnErr";
    String[] POOL = {
            POOL_CORE_COUNT,
            POOL_MAX_COUNT,
            POOL_MAX_WAIT,
            POOL_MAX_IDLE,
            POOL_RETRY_INTERVAL,
            POOL_MAX_RETRY_COUNT,
            POOL_EXIT_ON_ERR
    };

    String MYSQL_DRI = "driver";
    String MYSQL_URL = "url";
    String MYSQL_USER = "username";
    String MYSQL_PASS = "password";
    String[] MYSQL = {
            MYSQL_DRI,
            MYSQL_URL,
            MYSQL_USER,
            MYSQL_PASS
    };
}

  1. 连接池Pool公共接口
    规定两个方法:
    void destroy():销毁连接池对象
    Dao newDao():创建唯一的Dao对象
package cn.kgc.kb08.jdbc.dao3;

public interface Pool {
    void destroy();
    Dao newDao();
}
  1. 连接池Pool的抽象工厂PoolFactory
    该类规定了连接池的单例模式的核心两点:
    Pool 构造方法私有不可见
    同步锁初始化获得连接池Pool对象(第一次创建,其他获得同一个对象Pool)。
    这两点保证了Pool对象的单例模式,而Pool对象实际上封装了BaseDao对象(下面会讲到)的创建,由于Pool对象是唯一的,自然而然其封装的内部类BaseDao对象也是唯一的,这样就解决了BaseDao对象的管理缺失。
package cn.kgc.kb08.jdbc.dao3;

import cn.kgc.kb08.jdbc.dao3.impl.ConPool;

/**
 * 连接池的单例模式
 */
public abstract class PoolFactory {
    private static Pool pool;

    private static synchronized void init(){
        if (null==pool){
            pool = new ConPool();
        }
    }

    public static Pool get(){
        if (null==pool){
            init();
        }
        return pool;
    }
}

  1. 连接池对象管理类ConPool
    这是BaseDao的核心所在,通过弹性线程池ConPool对多并发和Connection对象进行管理,内部封装了BaseDao的内部类,下面简要分析一下原理:
    在这里插入图片描述
    实际上,连接池对象管理类ConPool就相当于一个分段锁,意思是说,ConPool整体是一个分段锁的外部,是一个读锁(共享锁),每一个连接对象都能同时访问,上面说过连接对象内部封装了BaseDao对象,就意味着,每个连接对象都能通过唯一的BaseDao,去访问数据库mysql。ConPool其内部又有一定数量的读锁(核心连接池数,当然当核心池连接长时间繁忙时,就是说超过配置信息的最大尝试时间*最大尝试次数,系统会额外的建立一个非核心连接池对象,解决临时问题,当然这个非核心连接池对象在其空闲时间超过配置的最大空闲时间时,就会被系统销毁)每一个核心连接池对象,他们之间又是写锁(排它锁),而我们知道读锁是支持并发查询数据的,而写锁又能保证在增删改数据的同时只能保证一个连接池对象能够拿到那个唯一的ConPool对象,进而拿到唯一的BaseDao对象,进行操作,操作完成后释放锁,保证了并发地增删改查操作。具体代码如下:
package cn.kgc.kb08.jdbc.dao3.impl;

import cn.kgc.kb08.jdbc.dao3.Pool;
import cn.kgc.kb08.jdbc.dao3.SelRtn;
import cn.kgc.kb08.jdbc.dao3.Dao;

import java.lang.reflect.Method;
import java.sql.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static cn.kgc.kb08.jdbc.dao3.impl.PoolUtil.close;

/**
 * Base:通用
 * Dao:DataBase Access Object 数据库访问对象
 * @version kb08-3.0
 * @since kb08-1.0
 * 已解决缺陷:
 * 1、查询操作:通过自定义类型SelRtn反馈【是否存在异常】和查询结果
 * 2、BaseDao对象的管理缺失(不停的创建):单例工厂的模式
 * 3、连接字符串管理:通过properties小型配置文件
 * 4、并发管理:通过弹性线程池PoolUtil对多并发和Connection对象进行管理
 */
public final class ConPool implements Pool {
    /**
     * 自定义类型:池中连接
     */
    private class PoolCon{
        //池连接对象是否空闲
        boolean free = true;
        //池连接对象是否为核心连接对象
        boolean core;
        //连接数据库对象
        Connection con;
        //临时池连接对象空闲开始时间
        Long idleBegin;

        public PoolCon(boolean core,Connection con){
            this.core = core;
            this.con = con;
            resetIdle();
        }

        public void resetIdle(){
            if (!core){
                this.idleBegin = System.currentTimeMillis();
            }
        }
    }
    //池连接键值对
    private ConcurrentMap<Integer,PoolCon> pool;
    //池连接配置信息键值对
    private Map<String,Integer> cnfPool;
    //数据库连接配置信息键值对
    private Map<String,String> cnfCon;
    /**
     * 执行定期清理线程池
     * 1、检查核心连接对象的有效性:无效则创建新核心连接对象覆盖之
     * 2、检查临时连接对象是否超时,超时则并关闭并移除
     */
    private ScheduledExecutorService schedule;
    //主线程池(后面定义为配置信息的2倍数量)
    private ExecutorService service;
    private Lock lock;
    private Condition cond;
    //临时池连接对象是否正在清理
    private boolean clearing;
    //数据库访问对象
    private Dao dao;

    public  ConPool(){
        initCnf();
        initPool();
        startClear();
    }

    /**
     * 初始化池和数据配置信息
     */
    private void initCnf(){
        cnfPool = PoolUtil.parse(Integer.class,"pool",
                Arrays.asList(PoolConstant.POOL));
        cnfCon = PoolUtil.parse(String.class,"mysql01",
                Arrays.asList(PoolConstant.MYSQL));
    }

    /**
     * 初始化连接池(核心连接对象)
     */
    private void initPool(){
        final int MAX_COUNT = cnfPool.get(PoolConstant.POOL_MAX_COUNT);
        //主线程池数量为配置信息最大数量的2倍
        service = Executors.newFixedThreadPool(MAX_COUNT*2);
        schedule = Executors.newSingleThreadScheduledExecutor();
        lock = new ReentrantLock(true);
        cond = lock.newCondition();
        //支持并发操作的Map
        pool = new ConcurrentHashMap<>(MAX_COUNT);
        PoolCon pc;
        final int CORE_COUNT = cnfPool.get(PoolConstant.POOL_CORE_COUNT);
        //初始化核心池连接对象
        //变量j的作用是保证核心连接池对象创建成功(数量与配置信息一致),即使线程中途中断,也能保证成功
        for (int i = 1,j = 1; i <= CORE_COUNT; i++) {
            pc = mkPoolCon(true);
            if (null!=pc){
                System.out.println(j+"池连接对象创建成功");
                pool.put(j++,pc);
            }
        }
        if (pool.size()==0){
            System.out.println("连接池初始化失败,系统强制退出");
            System.exit(-1);
        }
        //规则:连接对象不小于最大连接对象的一半,或者出现异常(配置信息的异常为1)
        if (cnfPool.get(PoolConstant.POOL_EXIT_ON_ERR)==1 && pool.size()<=CORE_COUNT/2){
            System.out.println("连接池初始化有效核心连接数为未过半异常,系统强制退出");
            System.exit(-1);
        }
        System.out.println("连接池初始化成功");
    }

    /**
     * 创建一个池对象
     * @param core 池对象类型:true:核心对象  false:临时对象
     * @return PoolCon
     */
    private PoolCon mkPoolCon(boolean core){
        PoolCon pc = null;
        for (int i = 1; i <=cnfPool.get(PoolConstant.POOL_MAX_RETRY_COUNT); i++) {
            try {
                //建立与数据库的连接
                Connection con = DriverManager.getConnection(
                        cnfCon.get(PoolConstant.MYSQL_URL),
                        cnfCon.get(PoolConstant.MYSQL_USER),
                        cnfCon.get(PoolConstant.MYSQL_PASS)
                );
                pc = new PoolCon(core,con);
                break;
            } catch (SQLException e) {
                try {
                    //尝试配置信息的间隔时间进行请求建立池连接对象
                    TimeUnit.SECONDS.sleep(cnfPool.get(PoolConstant.POOL_RETRY_INTERVAL));
                    continue;
                } catch (InterruptedException ex) {
                    System.err.println(ex.getMessage());
                }
            }
        }
        return pc;
    }

    /**
     * 验证连接对象是否有效
     * @param pc 池连接对象
     * @return true :有效 false:无效
     */
    private boolean isPCValid(PoolCon pc){
        try {
            //执行一条无意义的SQL语句
            pc.con.createStatement().executeQuery("select 1");
            return true;
        } catch (SQLException e) {
            return false;
        }
    }

    /**
     * 验证临时连接对象是否过期
     * @param pc 池连接对象
     * @return true: 过期     false:未过期
     */
    private boolean isExpired(PoolCon pc){
        //临时连接对象空闲时间超过配置信息的最大空闲时间
        return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()-pc.idleBegin)
                >=cnfPool.get(PoolConstant.POOL_MAX_IDLE);
    }

    /**
     * 验证用户等待是否超时(配置最大时限)
     * @param waitBegin 计算参考起点时间
     * @return true:等待已超时   false:等待未超时
     */
    private boolean isWaitExpried(long waitBegin){
        //连接对象请求未获得批准时间超过配置信息的最大请求等待时间
        return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()-waitBegin)>=
                cnfPool.get(PoolConstant.POOL_MAX_WAIT);
    }

    /**
     * 开启定期清理任务
     */
    private void startClear(){
        System.out.println("池清理线程启动成功。。。。");
        //定时时间为配置信息的最大空闲时间
        int delay = cnfPool.get(PoolConstant.POOL_MAX_IDLE);
        schedule.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                //定时清理时获得锁,正在清理
                lock.lock();
                clearing = true;

                //非空闲的连接池对象不清理
                for (Integer key : pool.keySet()) {
                    PoolCon pc = pool.get(key);
                    if (!pc.free){
                        continue;
                    }
                    //核心连接池对象无效,重新建立覆盖之
                    if (pc.core){
                        if (!isPCValid(pc)){
                            pool.put(key,mkPoolCon(true));
                        }
                    }else{
                        //临时连接池对象无效或者已超时,进行清理
                        if (isExpired(pc) || !isPCValid(pc)){
                            pool.remove(key);
                        }
                    }
                }

                //定时清理后释放锁,通知其他线程,已完成清理
                clearing = false;
                cond.signalAll();
                lock.unlock();
            }
        },delay,delay,TimeUnit.SECONDS);
    }

    /**
     * 连接池销毁
     */
    @Override
    public void destroy(){
        //只要有连接池对象就有间隔时间的判断是否销毁
        while (pool.size()>0){
            for (Integer key : pool.keySet()) {
                PoolCon pc = pool.get(key);
                //连接池对象空闲方能清理,释放资源,并移除
                if (pc.free){
                    pc.free = false;
                    close(pc.con);
                    pool.remove(key);
                    System.out.println("释放池连接"+key+"对象成功");
                }
            }
            try {
                TimeUnit.MILLISECONDS.sleep(cnfPool.get(PoolConstant.POOL_RETRY_INTERVAL));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 从池中获取一个有效连接对象
     * @return PoolCon
     */
    private PoolCon fetch(){
        long waitBegin = System.currentTimeMillis();
        for (int i = 1; i <=cnfPool.get(PoolConstant.POOL_MAX_RETRY_COUNT) ; i++) {
            try {
                //上锁,正在清理就等待
                lock.lock();
                if (clearing) {
                    cond.await();
                }
                for (Integer key : pool.keySet()) {
                    PoolCon pc = pool.get(key);
                    //连接池对象空闲且有效方能取出
                    if (pc.free && isPCValid(pc)){
                        pc.free = false;
                        System.out.println("成功取出池连接对象");
                        return pc;
                    }
                }
                //没有超时无法获得,一定时间间隔后再次请求
                if (isWaitExpried(waitBegin)){
                    return null;
                }
                TimeUnit.MILLISECONDS.sleep(cnfPool.get(PoolConstant.POOL_RETRY_INTERVAL));
            }catch (Exception ex){
                ex.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
        //取出临时连接对象(并非是第一个临时连接池对象)
        if (pool.size()<cnfPool.get(PoolConstant.POOL_MAX_COUNT)){
            PoolCon pc = mkPoolCon(false);
            if (null!=pc){
                pc.free = false;
                pool.put(pool.size()+1,pc);
                System.out.println("成功取出池连接对象");
                return pc;
            }
        }
        return null;
    }

    /**
     * 归还一个有效连接对象给池中
     * @param pc
     */
    private void giveBack(PoolCon pc){
        //没有连接池对象,返回
        if (null==pc){
            return;
        }
        //非核心连接池对象重置空闲时间
        if (!pc.core){
            pc.resetIdle();
        }
        pc.free = true;
    }

    /**
     * 初始化Dao对象
     */
    private synchronized void init(){
        if (null==dao){
            dao = new Dao() {
                /**
                 * 自定义方法:获取预编译执行对象
                 * @param con 连接对象
                 * @param SQL 数据库操作命令
                 * @param params 动态参数:预编译参数
                 * @return 预编译执行对象
                 * @throws SQLException
                 */
                private PreparedStatement getPst(Connection con,final String SQL,Object...params) throws SQLException {
                    PreparedStatement pst = con.prepareStatement(SQL);
                    if (null!=params && params.length>0){
                        for (int i = 0; i < params.length; i++) {
                            pst.setObject(i+1,params[i]);
                        }
                    }
                    return pst;
                }

                /**
                 * 自定义方法:执行增删改操作
                 * @param pst 执行对象
                 * @return int
                 * @throws SQLException
                 */
                private int update(PreparedStatement pst) throws SQLException {
                    return pst.executeUpdate();
                }

                /**
                 * 自定义方法:执行查询操作
                 * @param pst 执行对象
                 * @return 结果集
                 * @throws SQLException
                 */
                private ResultSet query(PreparedStatement pst) throws SQLException {
                    return pst.executeQuery();
                }

                /**
                 * 自定义方法:反射解析实体参数类型的set方法,并以属性名为键,以方法对象为值进行存储
                 * @param c 实体类的实际类型
                 * @return HashMap<String, Method>
                 */
                private HashMap<String, Method> parseMethod(Class c) throws Exception{
                    HashMap<String, Method> mapMethod = new HashMap<>();
                    final String PREFIX = "set";
                    for (Method method : c.getDeclaredMethods()) {
                        String name = method.getName();
                        if (!name.startsWith(PREFIX)){
                            continue;
                        }
                        name = name.substring(3);
                        name = name.substring(0,1).toLowerCase()+name.substring(1);
                        mapMethod.put(name,method);
                    }
                    return mapMethod;
                }

                /**
                 * 自定义方法:解析结果集携带的表结构(表字段名称)
                 * @param md 结构对象
                 * @return Stirng[]
                 * @throws SQLException
                 */
                private String[] parseStruct(ResultSetMetaData md) throws SQLException {
                    String[] names = new String[md.getColumnCount()];
                    for (int i = 0; i < names.length; i++) {
                        names[i] = md.getColumnLabel(i+1);
                    }
                    return names;
                }

                /**
                 * 自定义方法:封装数据库的增删改操作
                 * @param SQL 数据库命令
                 * @param params 动态参数: 预编译命令参数
                 * @return int: -1:异常   0:失败    >0表示成功(多条操作)
                 */
                @Override
                public int exeUpd(final String SQL,final Object...params){
                    try {
                        return service.submit(new Callable<Integer>() {
                            @Override
                            public Integer call() throws Exception {
                                int rst = 0;
                                PoolCon pc = null;
                                PreparedStatement pst = null;
                                try {
                                    pc = fetch();
                                    if (null!=pc){
                                        pst = getPst(pc.con,SQL,params);
                                        rst = update(pst);
                                    }
                                } catch (SQLException e) {
                                    rst = -1;
                                }finally {
                                    close(pst);
                                    giveBack(pc);
                                }
                                return rst;
                            }
                        }).get();
                    } catch (Exception e) {
                        return -1;
                    }
                }

                /**
                 * 自定义方法:封装查询单个值
                 * @param <T> 泛型
                 * @param c 需要单个值的实际类型:基本类型的包装类型
                 * @param SQL 数据库命令
                 * @param params 动态参数:预编译参数值
                 * @return SelRtn:  isErr=true:异常   isErr=false(rtn=null:失败,否则:成功)
                 */
                @Override
                public <T> SelRtn exeSingle(final Class<T> c, final String SQL, final Object...params){
                    try {
                        return service.submit(new Callable<SelRtn>() {
                            @Override
                            public SelRtn call() throws Exception {
                                PoolCon pc = null;
                                PreparedStatement pst = null;
                                ResultSet rst = null;
                                try {
                                    pc = fetch();
                                    pst = getPst(pc.con,SQL,params);
                                    rst = query(pst);
                                    if (null!=rst && rst.next()){
                                        return SelRtn.succeed(c.getConstructor(String.class).newInstance(rst.getObject(1).toString()));
                                    }else{
                                        return SelRtn.succeed(null);
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }finally {
                                    PoolUtil.close(rst,pst);
                                    giveBack(pc);
                                }
                                return SelRtn.fail();
                            }
                        }).get();
                    } catch (Exception e) {
                        return SelRtn.fail();
                    }
                }

                /**
                 * 自定义方法:封装查询多行多列
                 * @param <T> 泛型
                 * @param c 需要实际类型(实体自定义类型)
                 * @param SQL 数据库命令
                 * @param params 动态参数:预编译参数值
                 * @return SelRtn:  isErr=true:异常   isErr=false(rtn=null:失败,否则:成功)
                 */
                @Override
                public <T> cn.kgc.kb08.jdbc.dao3.SelRtn exeQuery(final Class<T> c, final String SQL, final Object...params){
                    try {
                        return service.submit(new Callable<SelRtn>() {
                            @Override
                            public SelRtn call() throws Exception {
                                PoolCon pc = null;
                                PreparedStatement pst = null;
                                ResultSet rst = null;
                                try {
                                    pc = fetch();
                                    pst = getPst(pc.con,SQL,params);
                                    rst = query(pst);
                                    if (null!=rst && rst.next()){
                                        List<T> list = new ArrayList<>();
                                        Map<String, Method> map = parseMethod(c);
                                        String[] names = parseStruct(rst.getMetaData());
                                        do{
                                            T t = c.newInstance();
                                            for (String name : names) {
                                                map.get(name).invoke(t,rst.getObject(name));
                                            }
                                            list.add(t);
                                        }while (rst.next());
                                        return SelRtn.succeed(list);
                                    }else{
                                        return SelRtn.succeed(null);
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }finally {
                                    PoolUtil.close(rst,pst);
                                    giveBack(pc);
                                }
                                return SelRtn.fail();
                            }
                        }).get();
                    } catch (Exception e) {
                        return SelRtn.fail();
                    }
                }
            };
        }
    }

    /**
     * 实例化Dao对象
     * @return Dao
     */
    @Override
    public Dao newDao(){
        if (null==dao){
            init();
        }
        return dao;
    }
}

  1. PoolUtil连接池对象工具类
    封装了解析数据源配置信息和释放系统资源这两个行为
package cn.kgc.kb08.jdbc.dao3.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

public class PoolUtil {
    /**
     * 自定义方法:解析数据源配置信息
     * @param dataSource 数据源名称
     * @return Map<String,String>
     */
    protected static <T> Map<String,T> parse(Class<T> c,String dataSource, List<String> items){
        File config = new File("config/sys.properties");
        Properties pro = new Properties();
        try {
            pro.load(new FileInputStream(config));
            Map<String,T> map = new HashMap(items.size());
            for (String item : items) {
                String key = dataSource+"."+item;
                if (!pro.containsKey(key)){
                    throw new IOException("缺失配置项:"+item);
                }
                map.put(item,c.getConstructor(String.class).newInstance(pro.getProperty(key)));
            }
            return map;
        } catch (Exception e) {
            System.out.println(dataSource+"数据源配置信息异常,系统强制退出:"+e.getMessage());
            System.exit(-1);
        }finally {
            if (null!=pro){
                //清空键值对,并清空引用
                pro.clear();
                pro = null;
            }
        }
        return null;
    }

    /**
     * 数据库资源释放
     * @param acs 动态参数:待释放资源
     */
    protected static void close(AutoCloseable...acs){
        for (AutoCloseable ac : acs) {
            if (null!=ac){
                try {
                    ac.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  1. BaseDao的公共方法Dao接口
ackage cn.kgc.kb08.jdbc.dao3;

public interface Dao {
    int exeUpd(final String SQL, Object... params);

    <T> SelRtn exeSingle(final Class<T> c, final String SQL, final Object... params);

    <T> cn.kgc.kb08.jdbc.dao3.SelRtn exeQuery(final Class<T> c, final String SQL, final Object... params);

}
  1. 自定义类型SelRtn
    通过自定义类型SelRtn反馈【是否存在异常】和查询结果:
    异常:返回SelRtn fail(),其err属性为true
    结果空:返回SelRtn succeed(),其rtn属性为null
    结果非空:返回SelRtn succeed(),其rtn属性为Object对象
package cn.kgc.kb08.jdbc.dao3;

/**
 * 完善查询操作返回类型,对于异常的缺失
 */
public final class SelRtn {
    private boolean err = false;
    private Object rtn;

    public static SelRtn succeed(Object rtn){
        return new SelRtn(rtn);
    }

    public static SelRtn fail(){
        return new SelRtn();
    }

    private SelRtn(Object rtn){
        this.rtn = rtn;
    }

    private SelRtn(){
        this.err = true;
    }

    public boolean isErr(){
        return this.err;
    }

    public <T> T getRtn(){
        return (T)rtn;
    }
}

  1. 实体类
    与数据库中表字段一一对应
package cn.kgc.kb08.jdbc.entity;

public class Student {
    private Integer id;
    private Integer classId;
    private String stuName;
    private String stuGender;
    private String province;
    private String city;
    private String district;


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getClassId() {
        return classId;
    }

    public void setClassId(Integer classId) {
        this.classId = classId;
    }

    public String getStuName() {
        return stuName;
    }

    public void setStuName(String stuName) {
        this.stuName = stuName;
    }

    public String getStuGender() {
        return stuGender;
    }

    public void setStuGender(Boolean stuGender) {
        this.stuGender = stuGender?"男":"女";
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getDistrict() {
        return district;
    }

    public void setDistrict(String district) {
        this.district = district;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", classId=" + classId +
                ", stuName='" + stuName + '\'' +
                ", stuGender='" + stuGender + '\'' +
                ", province='" + province + '\'' +
                ", city='" + city + '\'' +
                ", district='" + district + '\'' +
                '}';
    }
}

  1. 测试类
package cn.kgc.kb08.jdbc.daotest;

import cn.kgc.kb08.jdbc.dao3.Pool;
import cn.kgc.kb08.jdbc.dao3.SelRtn;
import cn.kgc.kb08.jdbc.dao3.Dao;
import cn.kgc.kb08.jdbc.dao3.PoolFactory;
import cn.kgc.kb08.jdbc.entity.Student;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
    public static void main(String[] args) {
        Pool pool = PoolFactory.get();
        final Dao dao = pool.newDao();
        final int PAGE_SIZE = 10;
        String sql = "select ceil(count(1)/?) from studentinfo";
        final int TOTAL = dao.exeSingle(Integer.class,sql,PAGE_SIZE).getRtn();
        ExecutorService service = Executors.newFixedThreadPool(TOTAL);

        final String SQL = "select * from studentinfo limit ?,?";

        for (int i = 1; i <= TOTAL; i++) {
            final int I = i;
            service.submit(new Runnable() {
                @Override
                public void run() {
                    SelRtn sr = dao.exeQuery(Student.class, SQL,(I-1)*PAGE_SIZE,PAGE_SIZE);

                    System.out.println("\n第"+I+"页*******************************");
                    if (!sr.isErr()){
                        List<Student> list = sr.getRtn();
                        if (null!=list&&list.size()>0){
                            for (Student student : list) {
                                System.out.println(student);
                            }
                        }else{
                            System.out.println("查无记录");
                        }
                    }else{
                        System.out.println("出错");
                    }
                }
            });
        }
        pool.destroy();
    }
}

结果:

1池连接对象创建成功
2池连接对象创建成功
3池连接对象创建成功
连接池初始化成功
池清理线程启动成功。。。。
成功取出池连接对象
释放池连接1对象成功
释放池连接2对象成功
释放池连接3对象成功
成功取出池连接对象

第7*******************************
Student{id=61, classId=5, stuName='葛优', stuGender='男', province='安徽省', city='蚌埠市', district='五河县'}
Student{id=62, classId=5, stuName='赵伟伟', stuGender='男', province='江苏省', city='南通市', district='海安市'}
Student{id=63, classId=5, stuName='汤伟杰', stuGender='男', province='山东省', city='潍坊市', district='诸城市'}
Student{id=64, classId=5, stuName='江玉', stuGender='男', province='江苏省', city='淮安市', district='盱眙县'}
Student{id=65, classId=5, stuName='戴媛媛', stuGender='女', province='安徽省', city='安庆市', district='桐城市'}
Student{id=66, classId=5, stuName='赵杰', stuGender='男', province='安徽省', city='六安市', district='霍山县'}
Student{id=67, classId=5, stuName='吴悠', stuGender='男', province='天津市', city='市辖区', district='滨海新'}
Student{id=68, classId=5, stuName='孙亚洲', stuGender='男', province='安徽省', city='安庆市', district='望江县'}
Student{id=69, classId=5, stuName='李佛光', stuGender='男', province='江苏省', city='南通市', district='如东县'}
Student{id=70, classId=5, stuName='王家能', stuGender='男', province='云南省', city='楚雄彝', district='族自治'}
成功取出池连接对象

第5*******************************
Student{id=41, classId=3, stuName='何健', stuGender='男', province='云南省', city='楚雄彝', district='族自治'}
Student{id=42, classId=3, stuName='唐明汉', stuGender='男', province='江西省', city='抚州市', district='乐安县'}
Student{id=43, classId=3, stuName='郑朋', stuGender='男', province='江苏省', city='泰州市', district='泰兴市'}
Student{id=44, classId=3, stuName='陈立志', stuGender='男', province='安徽省', city='亳州市', district='涡阳县'}
Student{id=45, classId=3, stuName='庄俊伟', stuGender='男', province='江苏省', city='南京市', district='浦口区'}
Student{id=46, classId=3, stuName='戴彬', stuGender='男', province='上海市', city='市辖区', district='浦东新'}
Student{id=47, classId=4, stuName='陈元生', stuGender='男', province='江苏省', city='泰州市', district='泰兴市'}
Student{id=48, classId=4, stuName='张刘留', stuGender='男', province='江苏省', city='南京市', district='高淳区'}
Student{id=49, classId=4, stuName='刘青青', stuGender='女', province='江苏省', city='扬州市', district='宝应县'}
Student{id=50, classId=4, stuName='周璟', stuGender='男', province='江苏省', city='扬州市', district='宝应县'}
成功取出池连接对象

第1*******************************
Student{id=1, classId=1, stuName='陈涛', stuGender='男', province='江苏省', city='南京市', district='浦口区'}
Student{id=2, classId=1, stuName='陈玉莹', stuGender='女', province='江苏省', city='盐城市', district='阜宁县'}
Student{id=3, classId=1, stuName='陈真', stuGender='男', province='安徽省', city='蚌埠市', district='五河县'}
Student{id=4, classId=1, stuName='丁聪', stuGender='男', province='江苏省', city='南通市', district='海安市'}
Student{id=5, classId=1, stuName='管若涵', stuGender='男', province='山东省', city='潍坊市', district='诸城市'}
Student{id=6, classId=1, stuName='胡正双', stuGender='男', province='江苏省', city='淮安市', district='盱眙县'}
Student{id=7, classId=1, stuName='江文涛', stuGender='男', province='安徽省', city='安庆市', district='桐城市'}
Student{id=8, classId=1, stuName='雷兵', stuGender='男', province='安徽省', city='六安市', district='霍山县'}
Student{id=9, classId=1, stuName='李楚鸿', stuGender='男', province='天津市', city='市辖区', district='滨海新'}
Student{id=10, classId=1, stuName='刘用兵', stuGender='男', province='安徽省', city='安庆市', district='望江县'}
成功取出池连接对象
成功取出池连接对象

第9*******************************
Student{id=81, classId=6, stuName='吴涛', stuGender='男', province='安徽省', city='宣城市', district='宁国市'}
Student{id=82, classId=6, stuName='徐磊', stuGender='男', province='安徽省', city='蚌埠市', district='怀远县'}
Student{id=83, classId=6, stuName='黄雄', stuGender='男', province='江苏省', city='盐城市', district='亭湖区'}
Student{id=84, classId=6, stuName='蔡志远', stuGender='男', province='江苏省', city='南通市', district='如皋市'}
Student{id=85, classId=6, stuName='陈稼轩', stuGender='男', province='安徽省', city='马鞍山', district='市和县'}
Student{id=86, classId=6, stuName='周继伟', stuGender='男', province='安徽省', city='安庆市', district='望江县'}3*******************************
Student{id=21, classId=2, stuName='徐凯强', stuGender='男', province='江苏省', city='扬州市', district='宝应县'}
Student{id=22, classId=2, stuName='姚成耀', stuGender='男', province='安徽省', city='安庆市', district='望江县'}
Student{id=87, classId=6, stuName='陈军华', stuGender='男', province='江苏省', city='南京市', district='江宁区'}
Student{id=88, classId=6, stuName='秦超', stuGender='男', province='江苏省', city='南京市', district='浦口区'}
Student{id=23, classId=2, stuName='应春雨', stuGender='女', province='安徽省', city='宣城市', district='宁国市'}
Student{id=24, classId=2, stuName='余杰', stuGender='男', province='安徽省', city='蚌埠市', district='怀远县'}
Student{id=25, classId=2, stuName='张超', stuGender='男', province='江苏省', city='盐城市', district='亭湖区'}
Student{id=89, classId=6, stuName='邵娅', stuGender='女', province='江苏省', city='盐城市', district='阜宁县'}
Student{id=26, classId=2, stuName='张志强', stuGender='男', province='江苏省', city='南通市', district='如皋市'}
Student{id=27, classId=2, stuName='张梓尧', stuGender='男', province='安徽省', city='马鞍山', district='市和县'}
Student{id=28, classId=2, stuName='黄超杰', stuGender='男', province='安徽省', city='安庆市', district='望江县'}
Student{id=29, classId=2, stuName='李刚', stuGender='男', province='江苏省', city='南京市', district='江宁区'}
Student{id=30, classId=2, stuName='吴凡', stuGender='男', province='江苏省', city='南京市', district='浦口区'}
成功取出池连接对象

第6*******************************
Student{id=51, classId=4, stuName='苏金峰', stuGender='男', province='安徽省', city='安庆市', district='望江县'}
Student{id=52, classId=4, stuName='吴凯', stuGender='男', province='安徽省', city='宣城市', district='宁国市'}
Student{id=53, classId=4, stuName='王纪文', stuGender='男', province='安徽省', city='蚌埠市', district='怀远县'}
Student{id=54, classId=4, stuName='孟涛涛', stuGender='男', province='江苏省', city='盐城市', district='亭湖区'}
Student{id=55, classId=4, stuName='李志杰', stuGender='男', province='江苏省', city='南通市', district='如皋市'}
Student{id=56, classId=4, stuName='丰笛', stuGender='男', province='安徽省', city='马鞍山', district='市和县'}
Student{id=57, classId=4, stuName='周炜', stuGender='男', province='安徽省', city='安庆市', district='望江县'}
Student{id=58, classId=4, stuName='张小港', stuGender='男', province='江苏省', city='南京市', district='江宁区'}
Student{id=59, classId=4, stuName='汪小天', stuGender='男', province='江苏省', city='南京市', district='浦口区'}
Student{id=60, classId=4, stuName='花磊', stuGender='男', province='江苏省', city='盐城市', district='阜宁县'}
成功取出池连接对象
成功取出池连接对象

第8*******************************
Student{id=71, classId=5, stuName='关敬元', stuGender='男', province='江西省', city='抚州市', district='乐安县'}
Student{id=72, classId=5, stuName='陈明秋', stuGender='男', province='江苏省', city='泰州市', district='泰兴市'}
Student{id=73, classId=5, stuName='李国旗', stuGender='男', province='安徽省', city='亳州市', district='涡阳县'}
Student{id=74, classId=5, stuName='韩睿刘洋', stuGender='男', province='江苏省', city='南京市', district='浦口区'}
Student{id=75, classId=5, stuName='张全根', stuGender='男', province='上海市', city='市辖区', district='浦东新'}
Student{id=76, classId=6, stuName='王跃', stuGender='男', province='江苏省', city='泰州市', district='泰兴市'}
Student{id=77, classId=6, stuName='王伟', stuGender='男', province='江苏省', city='南京市', district='高淳区'}
Student{id=78, classId=6, stuName='谢武阳', stuGender='男', province='江苏省', city='扬州市', district='宝应县'}
Student{id=79, classId=6, stuName='袁潇凯', stuGender='男', province='江苏省', city='扬州市', district='宝应县'}
Student{id=80, classId=6, stuName='邢慧斌', stuGender='男', province='安徽省', city='安庆市', district='望江县'}4*******************************
Student{id=31, classId=3, stuName='殷狄', stuGender='男', province='江苏省', city='盐城市', district='阜宁县'}
Student{id=32, classId=3, stuName='吴衍文', stuGender='男', province='安徽省', city='蚌埠市', district='五河县'}
Student{id=33, classId=3, stuName='范新宇', stuGender='男', province='江苏省', city='南通市', district='海安市'}
Student{id=34, classId=3, stuName='张琪', stuGender='男', province='山东省', city='潍坊市', district='诸城市'}
Student{id=35, classId=3, stuName='凌宇翔', stuGender='男', province='江苏省', city='淮安市', district='盱眙县'}
Student{id=36, classId=3, stuName='贾仲羽', stuGender='男', province='安徽省', city='安庆市', district='桐城市'}
Student{id=37, classId=3, stuName='黄鑫', stuGender='男', province='安徽省', city='六安市', district='霍山县'}
Student{id=38, classId=3, stuName='胡妙妙', stuGender='女', province='天津市', city='市辖区', district='滨海新'}
Student{id=39, classId=3, stuName='张啸尘', stuGender='男', province='安徽省', city='安庆市', district='望江县'}
Student{id=40, classId=3, stuName='许梦娟', stuGender='女', province='江苏省', city='南通市', district='如东县'}
成功取出池连接对象

第2*******************************
Student{id=11, classId=1, stuName='钱一鸣', stuGender='男', province='江苏省', city='南通市', district='如东县'}
Student{id=12, classId=1, stuName='苏郑梓梵', stuGender='男', province='云南省', city='楚雄彝', district='族自治'}
Student{id=13, classId=1, stuName='唐宇飞', stuGender='男', province='江西省', city='抚州市', district='乐安县'}
Student{id=14, classId=1, stuName='王波', stuGender='男', province='江苏省', city='泰州市', district='泰兴市'}
Student{id=15, classId=1, stuName='王春雷', stuGender='男', province='安徽省', city='亳州市', district='涡阳县'}
Student{id=16, classId=1, stuName='王亮', stuGender='男', province='江苏省', city='南京市', district='浦口区'}
Student{id=17, classId=2, stuName='王志远', stuGender='男', province='上海市', city='市辖区', district='浦东新'}
Student{id=18, classId=2, stuName='吴彦祥', stuGender='男', province='江苏省', city='泰州市', district='泰兴市'}
Student{id=19, classId=2, stuName='吴志远', stuGender='男', province='江苏省', city='南京市', district='高淳区'}
Student{id=20, classId=2, stuName='相羽', stuGender='男', province='江苏省', city='扬州市', district='宝应县'}

Process finished with exit code -1

猜你喜欢

转载自blog.csdn.net/BigData_Hobert/article/details/107301740