精华内容
下载资源
问答
  • java多线程处理数据库数据,使用并发包,无框架,可批量处数据库数据,进行增删改。。等等操作。
  • python使用多线程查询数据库

    万次阅读 热门讨论 2019-02-01 17:04:39
     当数据量过大时,一个程序的执行时间就会主要花费在等待单次查询返回结果,在这个过程中cpu无疑是处于等待io的空闲状态的,这样既浪费了cpu资源,又花费了大量时间(当然这里主要说多线程,批量查询不在考虑范围,...

    一.背景:

             当数据量过大时,一个程序的执行时间就会主要花费在等待单次查询返回结果,在这个过程中cpu无疑是处于等待io的空闲状态的,这样既浪费了cpu资源,又花费了大量时间(当然这里主要说多线程,批量查询不在考虑范围,总会存在不能批量查询的情况),在这种非密集型运算(及大量占用cpu资源)的情况下在python中无疑运用多线程是一个非常棒的选择。

    二.知识点:

            数据库连接池的运用及优势,python中多线程的运用,队列的运用

            数据库连接池:限制了数据库的连接最大个数,每次连接都是可以重复使用的,当然也可以限制每个连接的重复使用次数(这个在这里是没必要的),需要注意的是设置的数据库的最大连接个数最好要大于我们自己开的最大线程个数,一般逻辑是每个线程占用一个数据库连接可以使程序达到最大速度,如果小于则可能存在同时连接个数大于数据库允许的最大连接个数的风险。使用数据库连接池的优势在于,python多线程并发操作数据库,会存在链接数据库超时、数据库连接丢失、数据库操作超时等问题,而数据库连接池提供线程间可共享的数据库连接,并自动管理连接。

           python多线程:在程序等待io的时间里调用多线程去数据库执行查询操作。

           队列:这个就是数据结构里面的知识了,一般队列的常用模式先进先出队列。(这里主要用的是队列取一个数就少一个数的原理,其实用列表也可以实现,他的先进先出主要强调的是一个顺序关系,这一点到没用上,就当是练练手了)

    三.两段代码作比较:

    数据库的截图:

    第一段代码:正常循环查询并打印出执行时间

    #!/usr/bin/python
    # -*- coding=utf-8 -*-
    import time
    import threading
    import MySQLdb
    import Queue
    from MySQLdb.cursors import DictCursor
    from DBUtils.PooledDB import PooledDB
    
    def mysql_connection():
        host = 'localhost'
        user = 'root'
        port = 3306
        password = '123456'
        db = 'test'
        charset = 'utf8'
        limit_count = 3  # 最低预启动数据库连接数量
        pool = PooledDB(MySQLdb, limit_count, maxconnections=15, host=host, user=user, port=port, passwd=password, db=db, charset=charset,
                use_unicode=True, cursorclass=DictCursor)
        return pool
    
    
    start = time.time()
    pool = mysql_connection()
    
    for id in range(50):
        con = pool.connection()
        cur = con.cursor()
        sql = '''select id,name,age,weight from test where id = %s '''%id
        cur.execute(sql)
        time.sleep(0.5)
        result = cur.fetchall()
        if result:
            print('this is tread %s (%s,%s,%s,%s)'%(id,result[0]['id'],result[0]['name'],result[0]['age'],result[0]['weight']))
        else:
            print('this tread %s result is none'%id)
    
    end = time.time() - start
    print(end)

     

     

    执行结果:

     

     

    第二段代码:限制数据库连接池最大15个连接,用队列限制最大线程个数为10个

    #!/usr/bin/python
    # -*- coding=utf-8 -*-
    import time
    import threading
    import MySQLdb
    import Queue
    from MySQLdb.cursors import DictCursor
    from DBUtils.PooledDB import PooledDB
    
    def mysql_connection():
        host = 'localhost'
        user = 'root'
        port = 3306
        password = '123456'
        db = 'test'
        charset = 'utf8'
        limit_count = 3  # 最低预启动数据库连接数量
        pool = PooledDB(MySQLdb, limit_count, maxconnections=15, host=host, user=user, port=port, passwd=password, db=db, charset=charset,
                use_unicode=True, cursorclass=DictCursor)
        return pool
    
    def tread_connection_db(id):
        con = pool.connection()
        cur = con.cursor()
        sql = '''select id,name,age,weight from test where id = %s '''%id
        cur.execute(sql)
        time.sleep(0.5)
        result = cur.fetchall()
        if result:
            print('this is tread %s (%s,%s,%s,%s)'%(id,result[0]['id'],result[0]['name'],result[0]['age'],result[0]['weight']))
        else:
            print('this tread %s result is none'%id)
        con.close()
    
    
    if __name__=='__main__':
        start = time.time()
        #创建线程连接池,最大限制15个连接
        pool = mysql_connection()
        #创建队列,队列的最大个数及限制线程个数
        q=Queue.Queue(maxsize=10)
        #测试数据,多线程查询数据库
        for id in range(50):
            #创建线程并放入队列中
            t = threading.Thread(target=tread_connection_db, args=(id,))
            q.put(t)
            #队列队满
            if q.qsize()==10:
                #用于记录线程,便于终止线程
                join_thread = []
                #从对列取出线程并开始线程,直到队列为空
                while q.empty()!=True:
                    t = q.get()
                    join_thread.append(t)
                    t.start()
                #终止上一次队满时里面的所有线程
                for t in join_thread:
                    t.join()
        end = time.time() - start
        print(end)

     

     

    程序备注应该还算比较清晰的哈,程序执行结果:

    四.结论:

    看结果说话

     

    展开全文
  • Qt数据库连接池-多线程连接数据库

    热门讨论 2021-01-05 17:21:56
    之前在Qt上使用数据库,而Qt上一个连接只能在一个线程上使用,所以就写了一个数据库连接池,现在上传一下自己的源码,还是比较简单的使用。注释比较清晰,就不单独说了,直接上源码。 一、头文件 #ifndef ...

    前言

    之前在Qt上使用数据库,而Qt上一个连接只能在一个线程上使用,所以就写了一个数据库连接池,现在上传一下自己的源码,还是比较简单的使用。注释比较清晰,就不单独说了,直接上源码。

    |版本声明:山河君,未经博主允许,禁止转载

    原理

    数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。

    而其实质上连接池就是通过单例,在进行工具类调用连接后通过一个集合来建立几个连接对象。在进行连接的时候来通过集合分配对象,采用对象锁来限制多线程的资源抢夺情况。

    一、头文件

    #ifndef CSQLDATABASE_H
    #define CSQLDATABASE_H
    
    #include <QtSql>
    #include <QString>
    #include <QMutex>
    #include <QMutexLocker>
    #include <QWaitCondition>
    #include <QQueue>
    /*
     * qt时间样式
     * 年月:yyyy-mm-dd
     * 时间:hh24:mi:ss
     * 年月时间:yyyy-mm-dd hh24:mi:ss
     */
    
    
    class CSQLDatabase
    {
    public:
        /*
         * 单例模式获取
         * 参数:ip,用户名,用户密码,数据库名,数据库类型,端口号,最大连接数,最长等待时间,等待间隔
         */
        static CSQLDatabase& GetInstance(QString qsHostName, QString qsUser, QString qsPassWd,
                                         QString qsDatabase, QString qsDatabaseType, int nPort,
                                         int nMaxConn, int nMaxWaitTime, int nWaitInter);
    
        bool                ExecuteStmt(QString qsSql); //用于增删改
        QSqlQuery           ExecuteQuery(QString qsSql);//用于查询
    
        ~CSQLDatabase();
    
    private:
        CSQLDatabase(QString qsHostName, QString qsUser, QString qsPassWd,
                     QString qsDatabase, QString qsDatabaseType, int nPort,
                     int nMaxConn, int nMaxWaitTime, int nWaitInter);
    
        QSqlDatabase    OpenConnection();
        void            CloseConnection(QSqlDatabase connection);
        QSqlDatabase    CreateConnect(const QString& qsConnName);
    
    private:
        int m_nPort;
        int m_nMaxConn;
        int m_nMaxWaitTime; //最长等待时间
        int m_nWaitInter;   //等待间隔
    
        QString m_qsHostName;
        QString m_qsUser;
        QString m_qsPassWd;
        QString m_qsDatabase;
        QString m_qsDatabaseType;
    
    
        QQueue<QString> m_queUseConnNames;
        QQueue<QString> m_queUnuserConnNames;
    
        static QMutex m_Mutex;
        static QWaitCondition m_WaitConn;
        static CSQLDatabase* m_pInstance;
    };
    
    static CSQLDatabase glSqlDatabse = CSQLDatabase::GetInstance("127.0.0.1", "aaron", "0", "db", "QOCI", 1521, 5, 10, 1);
    
    #endif // CSQLDATABASE_H
    
    

    二、源文件

    #include "CSQLDatabase.h"
    
    QMutex CSQLDatabase::m_Mutex;
    CSQLDatabase* CSQLDatabase::m_pInstance = nullptr;
    QWaitCondition CSQLDatabase::m_WaitConn;
    
    CSQLDatabase& CSQLDatabase::GetInstance(QString qsHostName, QString qsUser, QString qsPassWd,
                                     QString qsDatabase, QString qsDatabaseType, int nPort,
                                     int nMaxConn, int nMaxWaitTime, int nWaitInter)
    {
        if(m_pInstance == nullptr)
        {
            QMutexLocker locker(&m_Mutex);
            m_pInstance = new CSQLDatabase(qsHostName, qsUser, qsPassWd, qsDatabase,
                                           qsDatabaseType, nPort, nMaxConn, nMaxWaitTime, nWaitInter);
        }
    
        return *m_pInstance;
    }
    
    CSQLDatabase::CSQLDatabase(QString qsHostName, QString qsUser, QString qsPassWd,
                               QString qsDatabase, QString qsDatabaseType, int nPort,
                               int nMaxConn, int nMaxWaitTime, int nWaitInter)
        :m_qsHostName(qsHostName), m_qsUser(qsUser), m_qsPassWd(qsPassWd), m_qsDatabase(qsDatabase),
          m_qsDatabaseType(qsDatabaseType), m_nPort(nPort), m_nMaxConn(nMaxConn),
          m_nMaxWaitTime(nMaxWaitTime), m_nWaitInter(nWaitInter)
    {
    }
    
    CSQLDatabase::~CSQLDatabase()
    {
        QMutexLocker locker(&m_Mutex);
        foreach(QString connName, m_pInstance->m_queUseConnNames)   //移除所有使用中的连接
            QSqlDatabase::removeDatabase(connName);
    
        foreach(QString unConnName, m_pInstance->m_queUnuserConnNames)  //移除所有未使用的连接
            QSqlDatabase::removeDatabase(unConnName);
    
        m_pInstance = nullptr;
    }
    
    QSqlDatabase CSQLDatabase::OpenConnection()
    {
        QMutexLocker locker(&m_Mutex);
        QString qsConnName;
    
        int nCountT = m_queUseConnNames.size() + m_queUnuserConnNames.size();
        //如果未到等待时间,并且所有的连接都在使用中,根据等待间隔进入到等待
        for(int i = 0; i < m_nMaxWaitTime && m_queUnuserConnNames.size() == 0 && nCountT == m_queUseConnNames.size(); i += m_nWaitInter)
        {
            //进行等待
            m_WaitConn.wait(&m_Mutex, m_nWaitInter);
            //重新计数总连接
            nCountT = m_queUseConnNames.size() + m_queUnuserConnNames.size();
        }
    
        //判断是否需要创建新连接,使用未使用连接,或者在连接全在使用时返回空的连接(可自己修改)
        if(m_queUnuserConnNames.size() > 0)
            qsConnName = m_queUnuserConnNames.dequeue();
        else if(nCountT < m_nMaxConn)
            qsConnName = QString("Connection-%1").arg(nCountT + 1);
        else {
            qDebug() << "All use sql connect";
            return QSqlDatabase();
        }
    
        //创建新连接,并放入到正在使用的连接容器中
        QSqlDatabase db = CreateConnect(qsConnName);
        if(db.isOpen())
            m_queUseConnNames.enqueue(qsConnName);
    
        return db;
    }
    
    void CSQLDatabase::CloseConnection(QSqlDatabase connection)
    {
        //关闭连接,并将连接名放回未连接容器中
        QMutexLocker locker(&m_Mutex);
        QString qsConnName = connection.connectionName();
        if(m_queUseConnNames.contains(qsConnName))
        {
            m_queUseConnNames.removeOne(qsConnName);
            m_queUnuserConnNames.enqueue(qsConnName);
            //如果某一线程已使用完连接,另一线程正在申请一个数据库连接,进行唤醒操作
            m_WaitConn.wakeOne();
        }
    }
    
    QSqlDatabase CSQLDatabase::CreateConnect(const QString &qsConnName)
    {
        //查看该连接名是否已经创建过连接
        if(QSqlDatabase::contains(qsConnName))
        {
            QSqlDatabase db = QSqlDatabase::database(qsConnName);
            return db;
        }
    
        QSqlDatabase db = QSqlDatabase::addDatabase(m_qsDatabaseType, qsConnName);
        db.setHostName(m_qsHostName);
        db.setDatabaseName(m_qsDatabase);
        db.setUserName(m_qsUser);
        db.setPassword(m_qsPassWd);
        db.setPort(m_nPort);
    
        if(!db.open())
        {
            qDebug() << "Open sql error" << db.lastError().text();
            return QSqlDatabase();
        }
    
        return db;
    }
    
    bool CSQLDatabase::ExecuteStmt(QString qsSql)
    {
        QSqlDatabase db = OpenConnection();
        if(!db.isOpen())
            return false;
    
        QSqlQuery query = db.exec(qsSql);
        CloseConnection(db);
    
        if(query.lastError().isValid())
        {
            qDebug() << "Sql error:" << query.lastError();
            return false;
        }
    
        return true;
    }
    
    QSqlQuery CSQLDatabase::ExecuteQuery(QString qsSql)
    {
        QSqlDatabase db = OpenConnection();
        if(!db.isOpen())
            return QSqlQuery();
    
        QSqlQuery query = db.exec(qsSql);
        CloseConnection(db);
    
        if(query.lastError().isValid())
        {
            qDebug() << "Sql error:" << query.lastError();
            return QSqlQuery();
        }
    
        return query;
    }
    
    
    

    总结

    未经允许,禁止转载!如果感觉对您有点用,请点个赞吧👍!

    展开全文
  • 存在多线程的情况下,如何使用数据库,并在关闭程序的时候完美移除数据库。 目录 所需要的头文件 线程A 连接数据库: 创建表: 数据插入: 线程B: 连接数据库: 查询(检测A线程中插入的数据是否存在): ...

    存在多线程的情况下,如何使用数据库,并在关闭程序的时候完美移除数据库。


    目录

    所需要的头文件

    线程A 

    连接数据库:

    创建表:

    数据插入:

    线程B:

    连接数据库 :

    查询(检测A线程中插入的数据是否存在): 

    移除数据库: 

    附录(完整代码): 


    MySQL环境变量配置(不进行配置无法进行连接):

    修改path

    增加环境变量:

    之后需要将MySQL的libmysql的.dll文件和.lib文件拷贝到Qt的相关文件夹下:

    MySQL需要的文件路径如下:

    并将其拷贝到你需要的编译器下的bin和lib文件夹下即可。


    在多线程环境下,例如存在线程A与线程B,且A,B线程都需要调用数据库,线程A为主线程,表的创建以及数据库的移除均出现在线程A中。

    所需要的头文件

    //数据库
    #include<QSqlDatabase>
    #include<QSqlError>
    #include<QSqlQuery>
    //线程锁
    #include<QMutex>

    所有线程中,所有关于数据库变量的设置,都在cpp源文件的初始化中进行 。假设线程A的初始化先于线程B


    线程A 

    连接数据库:

    在线程A,线程A.cpp的构造函数中,进行数据库相关内容的设置:

        QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL","a");
        db.setHostName("localhost");//数据库服务器IP
        db.setUserName("root");
        db.setPassword("123456789");   //这里输入你的密码
        db.setDatabaseName("test");//这里输入你的数据库名
        bool flagdb=db.open();
        if (flagdb==false)
        {
          QMessageBox::critical(NULL, QObject::tr("无法打开数据库"),"无法创建数据库连接! ", QMessageBox::Cancel);
          return;
        }

    之后

    创建表:

    需要在线程A.h头文件中,定义QSQLQuery的对象query,因为线程A.cpp中多个自定义函数需要调用数据库,保证query的作用域为整个A.cpp。

    线程A的.h中

    QSqlQuery query;
    QString insert="insert into acmmdatabase "
                        "(id,enfrist,ensecond,enthree,enfour,enfive,ensix) "
                        "values(:id,:enfrist,:ensecond,:enthree,:enfour,:enfive,:ensix);";

    之后紧接着线程A.cpp的初始化程序,创建表

    query = QSqlQuery ("",db);
        bool flagquery=query.exec("create table acmmdatabase(id int primary key,enfrist double,ensecond double,enthree double,enfour double,enfive double,ensix double)");
        if(flagquery==false)// 一旦路径错误,就会出错,提示工作人员去修改路径
        {
    //        qDebug()<<"数据库的表有问题!";
            bool clearfalg = query.exec("DROP table acmmdatabase");//删除是使用DROP
            if(clearfalg==false)
            {
                qDebug()<< query.lastError();
            }
            else
            {
                qDebug() << "delete table!";
            }
            bool flagqueryagain=query.exec("create table acmmdatabase(id int primary key,enfrist double,ensecond double,enthree double,enfour double,enfive double,ensix double)");
            if(flagqueryagain==false)
            {
                int chose = QMessageBox::warning(NULL,"warning","数据库创建表失败,请联系工作人员!",QMessageBox::Yes);
                if(chose==QMessageBox::Yes)
                    return;
            }
        }

    之后在线程A.cpp的其他自定义函数中,调用数据库,并进行操作即可,比如插入,清空,删除一项等。

    因为此前query是全局变量,所以在整个线程A.cpp均可调用query进行数据库的相关操作

    数据插入:

    • 数据插入(示例,最后会放上整个程序段)
    void Widget::tableoperation()
    {
     
        /*************数据库调用*************/
        mutex.lock();//保持原子性
    
        int oi=0;
        bool insertfalg =query.prepare(insert);//插入操作
        if(insertfalg == false)//判断操作是否成功
        {
            qDebug()<<query.lastError();
        }
        else
        {
            qDebug()<<"insert succeed!";
        }
        query.bindValue(":id",num+1);
        query.bindValue(":enfrist",dcom->oriangle[oi]);
        query.bindValue(":ensecond",dcom->oriangle[oi+1]);
        query.bindValue(":enthree",dcom->oriangle[oi+2]);
        query.bindValue(":enfour",dcom->oriangle[oi+3]);
        query.bindValue(":enfive",dcom->oriangle[oi+4]);
        query.bindValue(":ensix",dcom->oriangle[oi+5]);
    
        if( query.exec()==false)//判断赋值是否成功
        {
            qDebug()<<query.lastError();
        }
        else
        {
            qDebug()<<"insert data succeed!";
        }
        num++;
    
        mutex.unlock();//保持原子性
    
    
    }

    线程B:

    连接数据库 :

    在线程B.cpp中,连接数据库

            QSqlDatabase db2 = QSqlDatabase::addDatabase("QMYSQL","b");
            db2.setHostName("localhost");//数据库服务器IP
            db2.setUserName("root");
            db2.setPassword("123456789");   //这里输入你的密码
            db2.setDatabaseName("test");//这里输入你的数据库名
            bool flagdb=db2.open();
            if (flagdb==false)
            {
              QMessageBox::critical(NULL, QObject::tr("无法打开数据库"),"无法创建数据库连接! ", QMessageBox::Cancel);
              return;
            }
            imquery =QSqlQuery("",db2);

    同理,需要在线程B.h中,定义QSQLQuery的对象imquery,因为线程B.cpp中多个自定义函数需要调用数据库,保证query的作用域为整个B.cpp。

     QSqlQuery imquery;

    查询(检测A线程中插入的数据是否存在): 

    之后在线程B.cpp进行调用数据库的操作,如查询。

    bool fflag = imquery.exec("select enfrist from acmmdatabase");//只进行enfrist的查询
        if(fflag==false)
        {
            qDebug() <<"Error: Fail to enfrist create table." << imquery.lastError();
    
        }
        else
        {
            while(imquery.next())
            {
                //数据处理的示例,仅供参考
                x=52*sin( imquery.value("enfrist").toDouble()*pi )+65;
                y=52*cos(imquery.value("enfrist").toDouble()*pi )+85;
                p.drawLine(65,85,x,y);
            }
        }

    注意:使用select查询语句时,切记要注意,imquery.next必须在imquery.value之前,否则没有数据。


    移除数据库: 

    最后,当我们需要关闭程序时,要对数据库的表进行删除,并对数据库进行removeDatabase

    如前文所说,线程A.cpp才是主线程,所以表的删除及removeDatabase需要在线程A中完成。

    void Widget::closeEvent(QCloseEvent *event)
    {
        qDebug()<<"进入退出处理程序";
    
        int ret = QMessageBox::question(this,"question","是否需要关闭窗口",QMessageBox::Yes,QMessageBox::No);
    
        if(ret == QMessageBox::Yes)
        {
            event->accept();
    
            stopimage();//关闭线程B
    
            QSqlDatabase::removeDatabase("b");//移除数据库b,及线程B中使用的数据库
    
            QSqlDatabase::removeDatabase("a");//移除数据库a,及线程B中使用的数据库
            /*MySQL*/
    
            qDebug()<<"数据库已经完成关闭和移除";
            /*MySQL*/
        }
        else if(ret == QMessageBox::No)
        {
            event->ignore();
        }
    }

    附录(完整代码): 

    • 补充:整个程序段的展示(部分数据处理的内容未展示)
    • 线程A.h
    //数据库
    #include<QSqlDatabase>
    #include<QSqlError>
    #include<QSqlQuery>
    //线程锁
    #include<QMutex>
    
    namespace Ui {
    class Widget;
    }
    
    class Widget : public QWidget
    {
        Q_OBJECT
    
    protected:
        void closeEvent(QCloseEvent *event);
       
    public:
        explicit Widget(QWidget *parent = 0);
        ~Widget();
    
        void progress();
    
         int prnumb;//进度条读书
    
         //数据库
         QSqlQuery query;
         QString insert="insert into acmmdatabase "
                        "(id,enfrist,ensecond,enthree,enfour,enfive,ensix) "
                        "values(:id,:enfrist,:ensecond,:enthree,:enfour,:enfive,:ensix);";
       
    
    private slots:
    
    
        void on_save_clicked();
    
        void on_deletedata_clicked();
    
        void on_clear_clicked();
    
    private:
        Ui::Widget *ui;
    
       imagethread *imT;
       QThread *imgthread;
    
       QProgressDialog *progressindex;
    
    
    };
    
    #endif // WIDGET_H
    • 线程A.cpp
    • 注意:使用moveToThread的对象的父对象必须是QObejct类型 子线程的类必须是QObject
    #include "widget.h"
    #include "ui_widget.h"
    
    Widget::Widget(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::Widget)
    {
        ui->setupUi(this);
       
        /*********************************线程B*********************************************/
       
        imT = new imagethread;//分配空间
        imgthread = new QThread(this);//指定父对象,子线程
        imT->moveToThread(imgthread);//自定义线程加入子线程
    
        //启动image子线程
        imgthread->start();
        //启动线程
        
        /*********************************线程B*********************************************/
        /***********************************数据库Mysql*****************************************************/
    
        mutex.lock();
        QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL","a");
        db.setHostName("localhost");//数据库服务器IP
        db.setUserName("root");
        db.setPassword("2112414cjm");   //这里输入你的密码
        db.setDatabaseName("test");//这里输入你的数据库名
        bool flagdb=db.open();
        if (flagdb==false)
        {
          QMessageBox::critical(NULL, QObject::tr("无法打开数据库"),"无法创建数据库连接! ", QMessageBox::Cancel);
          return;
        }
        mutex.unlock();
        query = QSqlQuery ("",db);//如果是全局变量,需要制定
        bool flagquery=query.exec("create table acmmdatabase(id int primary key,enfrist double,ensecond double,enthree double,enfour double,enfive double,ensix double)");
        if(flagquery==false)// 一旦路径错误,就会出错,提示工作人员去修改路径
        {
    //        qDebug()<<"数据库的表有问题!";
            bool clearfalg = query.exec("DROP table acmmdatabase");//删除是使用DROP
            if(clearfalg==false)
            {
                qDebug()<< query.lastError();
            }
            else
            {
                qDebug() << "delete table!";
            }
            bool flagqueryagain=query.exec("create table acmmdatabase(id int primary key,enfrist double,ensecond double,enthree double,enfour double,enfive double,ensix double)");
            if(flagqueryagain==false)
            {
                int chose = QMessageBox::warning(NULL,"warning","数据库创建表失败,请联系工作人员!",QMessageBox::Yes);
                if(chose==QMessageBox::Yes)
                    return;
            }
        }
        
        /***********************************数据库Mysql*****************************************************/
    
     
    }
    Widget::~Widget()
    {
        delete ui;
    }
    
    //前界面表格的具体内容
    void Widget::tableoperation()
    {
      /*******
       数据处理....
        ********/
        /*************数据库调用*************/
        mutex.lock();
    
        //摁下之后会进行操作
        int oi=0;
        bool insertfalg =query.prepare(insert);//插入操作
        if(insertfalg == false)
        {
            qDebug()<<query.lastError();
        }
        else
        {
            qDebug()<<"insert succeed!";
        }
        query.bindValue(":id",num+1);
        query.bindValue(":enfrist",dcom->oriangle[oi]);
        query.bindValue(":ensecond",dcom->oriangle[oi+1]);
        query.bindValue(":enthree",dcom->oriangle[oi+2]);
        query.bindValue(":enfour",dcom->oriangle[oi+3]);
        query.bindValue(":enfive",dcom->oriangle[oi+4]);
        query.bindValue(":ensix",dcom->oriangle[oi+5]);
    //    qDebug()<<num+1;
       //更改之后,可以连续赋值
        if( query.exec()==false)//判断赋值是否成功
        {
            qDebug()<<query.lastError();
        }
        else
        {
            qDebug()<<"insert data succeed!";
        }
    
        mutex.unlock();
     /*******
       数据处理....
        ********/
        /**************数据库调用************/
      }
       /*******
       数据处理....
        ********/
    }
    
    //删除某行
    void Widget::on_deletedata_clicked()
    {
        /*******
       数据处理....
        ********/
    
        bool onefalg = query.exec("DELETE FROM acmmdatabase WHERE id = ?");//ok
        if(onefalg==false)
        {
            qDebug()<<"delete one`s 指令错误"<<query.lastError();
        }
        else
        {
            qDebug()<<"deleted,one!指令正确";
        }
        query.addBindValue(numcow+1);
    
        if(query.exec()==false)
        {
            qDebug()<<"delete one`s error"<<query.lastError();
        }
        else
        {
            qDebug()<<"deleted,one!";
        }
     /*******
       数据处理....
        ********/
    }
    //表格全部清空
    void Widget::on_clear_clicked()
    {
        
       /*******
       数据处理....
        ********/
        qDebug()<<"准备清理数据库";
         mutex.lock();
        bool clearfalg = query.exec("DELETE FROM acmmdatabase");//
        if(clearfalg==false)
        {
            qDebug()<< query.lastError();
        }
        else
        {
            qDebug() << "delete,all!";
        }
         mutex.unlock();
        /*******
       数据处理....
        ********/
    }
    
    void Widget::progress()//进度条
    {
        prnumb=0;//进度条初始化
    //    qDebug()<<"进度条";
        progressindex = new QProgressDialog(this);
        progressindex->setMinimum(0);//最小值
        progressindex->setMaximum(table->rowCount());//最大值
        progressindex->reset();//让进度条重新回到开始
        progressindex->setWindowModality(Qt::WindowModal);
        progressindex->setMinimumDuration(5);
        progressindex->setWindowTitle(tr("plese waiting"));
        progressindex->setLabelText("Copying...");
        progressindex->setCancelButtonText(tr("Cancel"));
        progressindex->setRange(0,table->rowCount());
        progressindex->autoReset();//自动关闭
    
    }
    void Widget::progressValue()//进度条赋值
    {
        prnumb++;
        progressindex->setValue(prnumb);
    }
    
    //关闭窗口
    void Widget::closeEvent(QCloseEvent *event)
    {
        qDebug()<<"进入退出处理程序";
    
        int ret = QMessageBox::question(this,"question","是否需要关闭窗口",QMessageBox::Yes,QMessageBox::No);
        //摁键一定要设置,否则会报错
        if(ret == QMessageBox::Yes)
        {
            event->accept();
            stopimage();//关闭线程image
    
            QSqlDatabase::removeDatabase("b");
    
            QSqlDatabase::removeDatabase("a");
            /*MySQL*/
    
            qDebug()<<"数据库已经完成关闭和移除";
            /*MySQL*/
        }
        else if(ret == QMessageBox::No)
        {
            event->ignore();
        }
    }
     //关闭线程B
     void Widget::stopimage()//退出崩溃就是你
     {
    //     qDebug()<<"进入线程关闭函数";
         if(imgthread->isRunning() == true)
         {
             imgthread->quit();
             imgthread->wait();
         }
         if(imT!=NULL)//imT不为空,删除,让他变成空的
         {
             delete imT;
         }
     }
     void Widget::on_save_clicked()
     {
         
         filepath= QFileDialog::getSaveFileName(this, "保存","../","Excel 文件(*.xls)");
         for(int i=0;i<7;i++)
         {
             hHeaderItem[i]=table->horizontalHeaderItem(i)->text(); //读取表头
         }
         throwcount = table->rowCount();//行数
         thcolcount = table->columnCount();
    
         //
             if (filepath!="")
             {
                 QAxObject *excel ;
                 excel = new QAxObject;//建立excel操作对象
                 if (excel->setControl("Excel.Application")) //连接Excel控件
                 {
                     qDebug()<<"进入程序";
                     excel->dynamicCall("SetVisible (bool Visible)","false");//不显示窗体
                     excel->setProperty("DisplayAlerts", false);
                     //不显示任何警告信息。如果为true那么在关闭是会出现类似“文件已修改,是否保存”的提示
                     QAxObject *workbooks = excel->querySubObject("WorkBooks");//获取工作簿集合
                     workbooks->dynamicCall("Add");//新建一个工作簿
                     QAxObject *workbook = excel->querySubObject("ActiveWorkBook");//获取当前工作簿
                     QAxObject *worksheet = workbook->querySubObject("Worksheets(int)", 1);
                     //访问excel中的工作表中第一个单元格
                     //保存到execl,总的来说就是操作单元格,赋值的赋值,改格式大的改格式,遵循一点,修改颜色,行高,字体这些属性,务必是要设定一个修改范围再动手
                     int i,j,colcount,rowcount;//表格的列数
                     colcount=thcolcount,rowcount=throwcount;
    
                     QAxObject *cell;
    
                     QAxObject *range;//= worksheet->querySubObject("Range(const QString&)", cellTitle);
    
                     for(i=0;i<colcount;i++)
                     {
                         //读取
                         QString columnName;
                         //修改内容
                         cell=worksheet->querySubObject("Cells(int,int)", 1, i+1);//确定操作单元格,将数据保存到哪儿,行的表头,所以要从第二列开始
                         columnName=hHeaderItem[i];//获取此处的文本内容,i是列号,就是第几列中的文本内容
                         cell->dynamicCall("SetValue(const QString&)", columnName);//打印到excel
                         //前半句是获取单元格字体,后半句是字体加粗
                         cell->querySubObject("Font")->setProperty("Bold", true);
                         cell->querySubObject("Interior")->setProperty("Color",QColor(191, 191, 191));
                         cell->setProperty("HorizontalAlignment", -4108);//xlCenter
                         cell->setProperty("VerticalAlignment", -4108);//xlCenter
                     }
                     //数据区
                     //qDebug()<<"行数"<<rowcount<<"列数"<<colcount;//列数不对
                     progress();
                     QString rowdata[7];
                     if(rowcount!=0)
                     {
    //                     mutex.lock();
                         for(int datai=0;datai<rowcount;datai++)
                        {
                             mutex.lock();
                             bool chaxun = query.exec(tr("select * from acmmdatabase where id = %1 ").arg(datai+1));//是从1开始的
                             if(chaxun==false)
                             {
                                 qDebug()<<query.lastError();
                             }
                             else
                                 qDebug()<<"succeed";
                             if(query.next())//必须要有next,输出,必须是string
                             {
    //                             (dcom->oriangle[numj]/180)*3.14159265359
                             double test[7];
                             test[0]=query.value("id").toDouble();
                             test[1]=((query.value("enfrist").toDouble())/anglepi)*piw;
                             test[2]=((query.value("ensecond").toDouble())/anglepi)*piw;
                             test[3]=((query.value("enthree").toDouble())/anglepi)*piw;
                             test[4]=((query.value("enfour").toDouble())/anglepi)*piw;
                             test[5]=((query.value("enfive").toDouble())/anglepi)*piw;
                             test[6]=((query.value("ensix").toDouble())/anglepi)*piw;
    
                             rowdata[0]=QString::number(test[0],10,6);
                             rowdata[1]=QString::number(test[1],10,6);
                             rowdata[2]=QString::number(test[2],10,6);
                             rowdata[3]=QString::number(test[3],10,6);
                             rowdata[4]=QString::number(test[4],10,6);
                             rowdata[5]=QString::number(test[5],10,6);
                             rowdata[6]=QString::number(test[6],10,6);
                             }
                            for (int dataj=0;dataj<colcount;dataj++)
                            {
                             //在此处,直接从数据库里面读取数据,不要从前界面读取内容了,实在是太麻烦了,而且容易崩溃,毕竟你的链表没有分陪空间
     //                        rowdata[dataj]=printdata[datai][dataj];
                             worksheet->querySubObject("Cells(int,int)", datai+2, dataj+1)->dynamicCall("SetValue(const QString&)",rowdata[dataj]);
                             }
                            mutex.unlock();
    
                         //进度条
                            progressValue();
    
                         }
    
                     //画框线
                     QString lrange;
                     lrange.append("A2:");//起始行列位置(原点),修改后,开始的位置会变化,A2的从A2那一行开始,A3就是第三行
                     lrange.append(colcount-1 + 'A');//终止列,-1,就是正常状态,改变colcount后面的
                     lrange.append(QString::number(rowcount +1));//终止行
                     //这个起始的位置,相当于原点,最终给一个行列的终止位置就行
                     range = worksheet->querySubObject("Range(const QString&)", lrange);
                     //querySubObject("Borders")是对边框的设置,必须有
                     range->querySubObject("Borders")->setProperty("LineStyle", QString::number(1));
                     range->querySubObject("Borders")->setProperty("Color", QColor(0, 0, 0));//颜色的设置
    
                     //调整数据区行高
                     QString rowsName;
                     rowsName.append("A2:");//起始行列位置
                     rowsName.append(colcount-1 + 'A');//终止列
                     //上面两句也可以变成
                     //rowsName.append("2:");//起始列
                     rowsName.append(QString::number(rowcount + 1));//终止行
    
                     range = worksheet->querySubObject("Range(const QString&)", rowsName);
                     range->setProperty("RowHeight", 20);//设置行高
                     range->setProperty("ColumnWidth", 20);  //设置单元格列宽
                     }
    
                     workbook->dynamicCall("SaveAs(const QString&)",QDir::toNativeSeparators(filepath));
                     //qDebug()<<"chenggong";
                     workbook->dynamicCall("Close()");//关闭工作簿
                     excel->dynamicCall("Quit()");//关闭excel
                     delete excel;
                     QMessageBox::warning(this,tr("save"),tr("导出成功"),QMessageBox::Yes);
                 }
                 else
                 {
                      QMessageBox::warning(this,"错误","未能创建 Excel 对象,请安装 Microsoft Excel。",QMessageBox::Apply);
                 }
         }
             qDebug()<<"线程一次完成";
    
    
     }
    
    • 线程B.h
    #ifndef IMAGETHREAD_H
    #define IMAGETHREAD_H
    
    
    //数据库
    #include<QSqlDatabase>
    #include<QSqlError>
    #include<QSqlQuery>
    #include<QMessageBox>
    //线程锁
    #include<QMutex>
    
    class imagethread : public QObject
    {
        Q_OBJECT
    public:
        explicit imagethread(QObject *parent = nullptr);
       
        QSqlDatabase db2;//此处为全局变量,也可为局部变量,根据工程需求而定
        QSqlQuery imquery;
        
    
    };
    
    #endif // IMAGETHREAD_H
    
    • 线程B
     #include "imagethread.h"
    
    imagethread::imagethread(QObject *parent) : QObject(parent)
    {
        /*********
         数据处理
         *********/
        
    }
    void imagethread::drawImage()
    {
    
        if(newflag==true)
        {
            db2 = QSqlDatabase::addDatabase("QMYSQL","b");
            db2.setHostName("localhost");//数据库服务器IP
            db2.setUserName("root");
            db2.setPassword("2112414cjm");   //这里输入你的密码
            db2.setDatabaseName("test");//这里输入你的数据库名
            bool flagdb=db2.open();
            if (flagdb==false)
            {
              QMessageBox::critical(NULL, QObject::tr("无法打开数据库"),"无法创建数据库连接! ", QMessageBox::Cancel);
              return;
            }
    
            imquery =QSqlQuery("",db2);
            newflag=false;
        }
        /*********************************MySql****************************************/
        if(competeleflag==true)
    {
       competeleflag=false;
       mutex.lock();
    
    
        bool fflag = imquery.exec("select enfrist from acmmdatabase");//只进行enfrist的查询
        if(fflag==false)
        {
            qDebug() <<"Error: Fail to enfrist create table." << imquery.lastError();
    
        }
        else
        {
            while(imquery.next())
            {
    
                x=52*sin( imquery.value("enfrist").toDouble()*pi )+65;
                y=52*cos(imquery.value("enfrist").toDouble()*pi )+85;
                p.drawLine(65,85,x,y);
            }
        }
        /*********
         数据处理
         *********/
    
        mutex.unlock();
     }
    
    
    }
    

    写程序时务必不要忘记,保证程序段的原子性。

    展开全文
  • 众所周知,创建数据库连接需要消耗较的资源,且创建时间也较长。如果网站一天100万PV(假设每个页面都有DB读取或修改操作),程序就需要创建100万次连接,极大的浪费资源。 事实上,同一时间需要创建数据库连接的...

    本例采用mysql数据库,因此请先下载mysql-connection.jar

    在我们的实际开发中,离不开和数据库打交道。而和数据库的通信,离不开数据库连接。
    通常用JDBC连接数据库时,需要加载数据驱动,然后再通过接口返回数据库连接。
    一般分为两步:
    1、加载驱动至内存
    Class.forName(“com.mysql.jdbc.Driver”);

    2、创建并获取连接,返回的是JDBC中的Connection
    DriverManager.getConnection(url, user, password)

    示例:

    //要连接的数据库URL
    String url = "jdbc:mysql://localhost:3306/tangwmdb";
    //连接的数据库时使用的用户名
    String username = "root";
    //连接的数据库时使用的密码
    String password = "root";
    
    //1.加载驱动
    //DriverManager.registerDriver(new com.mysql.jdbc.Driver());不推荐使用这种方式来加载驱动
    Class.forName("com.mysql.jdbc.Driver");//推荐使用这种方式来加载驱动
    
    //2.获取与数据库的链接
    Connection conn = DriverManager.getConnection(url, username, password);
    
    //3.获取用于向数据库发送sql语句的statement
    Statement st = conn.createStatement();
    
    String sql = "select id,name,password from members";
    //4.向数据库发sql,并获取代表结果集的resultset
    ResultSet rs = st.executeQuery(sql);
    
    //5.取出结果集的数据
    while(rs.next()){
        System.out.println("id=" + rs.getObject("id"));
        System.out.println("name=" + rs.getObject("name"));
        System.out.println("password=" + rs.getObject("password"));
    }
    
    //6.关闭链接,释放资源
    rs.close();
    st.close();
    conn.close();

    众所周知,创建数据库连接需要消耗较多的资源,且创建时间也较长。如果网站一天100万PV(假设每个页面都有DB读取或修改操作),程序就需要创建100万次连接,极大的浪费资源。
    事实上,同一时间需要创建数据库连接的请求数量并不多,一般几百个足够了。那么我们可以根据需要创建一个连接池,它负责分配、管理和释放数据库连接,它允许应用程序重复使用同一个现有的数据库连接,而不是重新建立一个。这里用到了设计模式中的一个模式:享元模式(Flyweight)
    比如我们的连接池中有1000条连接,请求来时,连接池从池中分配一条给请求,用完后收回,而不是销毁,等到下次有请求来时,又可以重复分配使用。
    这里写图片描述

    当使用了数据库连接池之后,在项目的实际开发中就不需要编写连接数据库的代码了,直接从数据源获得数据库的连接。比如:

    //DBCP 数据库连接池
    DataSource ds = BasicDataSourceFactory.createDataSource(prop);
    Connection conn = ds.getConnection();

    可以看到创建连接的工作很简单,因为复杂的分配、回收功能都交给了连接池去处理。

    当前有一些开源的数据连接池实现:

    • DBCP 数据库连接池
    • C3P0 数据库连接池

    另外阿里开源项目Druid(整个项目由数据库连接池、插件框架和SQL解析器组成)中的数据库连接池被很多互联网公司都采用在生产环境中。

    编写自己的数据库连接池

    编写的连接池需要做到以下几个基本点:
    1、可配置并管理多个连接节点的连接池
    这里写图片描述

    2、始使化时根据配置中的初始连接数创建指定数量的连接
    3、在连接池没有达到最大连接数之前,如果有可用的空闲连接就直接使用空闲连接,如果没有,就创建新的连接。
    4、当连接池中的活动连接数达到最大连接数,新的请求进入等待状态,直到有连接被释放。
    5、由于数据库连接闲置久了会超时关闭,因此需要连接池采用机制保证每次请求的连接都是有效可用的。
    6、线程安全
    7、连接池内部要保证指定最小连接数量的空闲连接。
    对于最小连接数在实际应用中的效果以及与初始连接数的区别,其实理解的不是很透。在程序中我采用的方式是,如果 活动连接数 + 空闲连接数 < 最小连接数,就补齐对应数量(最小连接数 - 活动连接数 - 空闲连接数)的空闲连接

    摘录一段:

    数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:
    最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费。
    最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作。
    如果最小连接数与最大连接数相差很大,那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,它将被放到连接池中等待重复使用或是超时后被释放。

    系统结构:

    1.连接池接口IConnectionPool:里面定义一些基本的获取连接的一些方法。
    2.连接池接口实现ConnectionPool
    3.连接池管理DBConnectionManager:管理不同的连接池,所有的连接都是通过这里获得。
    4.其它工具类,诸如属性读取类PropertiesManager,属性保存类DBPropertyBean。
    这里写图片描述

    工程结构:

    这里写图片描述

    工程代码:

    DBPropertyBean.java

    package com.twm.TDBConnectionPool;
    public class DBPropertyBean {
    
        private String nodeName;
        //数据连接驱动
        private String driverName;
        //数据连接url
        private String url;
        //数据连接username
        private String username;
        //数据连接密码
        private String password;
        //连接池最大连接数
        private int maxConnections ;
        //连接池最小连接数
        private int minConnections;
        //连接池初始连接数
        private int initConnections;
        //重连间隔时间 ,单位毫秒
        private int conninterval ;
        //获取连接超时时间 ,单位毫秒,0永不超时
        private int timeout ;
    
        //构造方法
        public DBPropertyBean(){
            super();
        }
    
        //下面是getter and setter
    
        /**
         * 获取数据库连接节点名称
         * @return
         */
        public String getNodeName() {
            return nodeName;
        }
    
        /**
         * 设置数据库连接节点名称
         * @param nodeName
         */
        public void setNodeName(String nodeName) {
            this.nodeName = nodeName;
        }
    
        /**
         * 获取数据库驱动
         * @return
         */
        public String getDriverName() {
            return driverName;
        }
    
        /**
         * 设置数据库驱动
         * @param driverName
         */
        public void setDriverName(String driverName) {
            this.driverName = driverName;
        }
    
        /**
         * 获取数据库url
         * @return
         */
        public String getUrl() {
            return url;
        }
    
        /**
         * 设置数据库url
         * @param url
         */
        public void setUrl(String url) {
            this.url = url;
        }
    
        /**
         * 获取用户名
         * @return
         */
        public String getUsername() {
            return username;
        }
    
        /**
         * 设置用户名
         * @param username
         */
        public void setUsername(String username) {
            this.username = username;
        }
    
        /**
         * 获取数据库连接密码
         * @return
         */
        public String getPassword(){
            return password;
        }
    
        /**
         * 设置数据库连接密码
         * @param password
         */
        public void setPassword(String password) {
            this.password = password;
        }
    
        /**
         * 获取最大连接数
         * @return
         */
        public int getMaxConnections() {
            return maxConnections;
        }
    
        /**
         * 设置最大连接数
         * @param maxConnections
         */
        public void setMaxConnections(int maxConnections) {
            this.maxConnections = maxConnections;
        }
    
        /**
         * 获取最小连接数(也是数据池初始连接数)
         * @return
         */
        public int getMinConnections() {
            return minConnections;
        }
    
        /**
         * 设置最小连接数(也是数据池初始连接数)
         * @param minConnections
         */
        public void setMinConnections(int minConnections) {
            this.minConnections = minConnections;
        }
    
        /**
         * 获取初始加接数
         * @return
         */
        public int getInitConnections() {
            return initConnections;
        }
    
        /**
         * 设置初始连接数
         * @param initConnections
         */
        public void setInitConnections(int initConnections) {
            this.initConnections = initConnections;
        }
    
        /**
         * 获取重连间隔时间,单位毫秒
         * @return
         */
        public int getConninterval() {
            return conninterval;
        }
    
        /**
         * 设置重连间隔时间,单位毫秒
         * @param conninterval
         */
        public void setConninterval(int conninterval) {
            this.conninterval = conninterval;
        }
    
        /**
         * 获取连接超时时间,单位毫秒
         * @return
         */
        public int getTimeout() {
            return timeout;
        }
    
        /**
         * 设置连接超时时间 ,单位毫秒,0-无限重连
         * @param timeout
         */
        public void setTimeout(int timeout) {
            this.timeout = timeout;
        }
    
    }

    IConnectionPool.java

    package com.twm.TDBConnectionPool;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    
    public interface IConnectionPool {
        /**
         * 获取一个数据库连接,如果等待超过超时时间,将返回null
         * @return 数据库连接对象
         */
        public Connection getConnection();
    
        /**
         * 获得当前线程的连接库连接
         * @return 数据库连接对象
         */
        public Connection getCurrentConnecton();
    
        /**
         * 释放当前线程数据库连接
         * @param conn 数据库连接对象
         * @throws SQLException
         */
        public void releaseConn(Connection conn) throws SQLException;
    
        /**
         * 销毁清空当前连接池
         */
        public void destroy();
    
        /**
         * 连接池可用状态
         * @return 连接池是否可用
         */
        public boolean isActive();
    
        /**
         * 定时器,检查连接池
         */
        public void checkPool();
    
        /**
         * 获取线程池活动连接数
         * @return 线程池活动连接数
         */
        public int getActiveNum();
    
        /**
         * 获取线程池空闲连接数
         * @return 线程池空闲连接数
         */
        public int getFreeNum();
    }

    ConnectionPool.java

    package com.twm.TDBConnectionPool;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.sql.Connection;
    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.TimerTask;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    import org.apache.log4j.Logger;
    
    
    /** 
     * 类说明 :友元类,包内可见,不提供给客户程序直接访问。
     */
    class ConnectionPool implements IConnectionPool {
    
        private static final Logger log = Logger.getLogger(ConnectionPool.class);
    
        private DBPropertyBean propertyBean=null;
    
        //连接池可用状态
        private Boolean isActive = true;
    
        // 空闲连接池 。由于List读写频繁,使用LinkedList存储比较合适
        private LinkedList<Connection> freeConnections = new LinkedList<Connection>();  
        // 活动连接池。活动连接数 <= 允许最大连接数(maxConnections)
        private LinkedList<Connection> activeConnections = new LinkedList<Connection>(); 
    
        //当前线程获得的连接
        private ThreadLocal<Connection> currentConnection= new ThreadLocal<Connection>();
    
        //构造方法无法返回null,所以取消掉。在下面增加了CreateConnectionPool静态方法。
        private ConnectionPool(){
            super();
        }
    
        public static ConnectionPool CreateConnectionPool(DBPropertyBean propertyBean) {
            ConnectionPool connpool=new ConnectionPool();
            connpool.propertyBean = propertyBean;
    
            //加载驱动 
    
            //在多节点环境配置下,因为在这里无法判断驱动是否已经加载,可能会造成多次重复加载相同驱动。
            //因此加载驱动的动作,挪到connectionManager管理类中去实现了。
            /*try {
                Class.forName(connpool.propertyBean.getDriverName());
                log.info("加载JDBC驱动"+connpool.propertyBean.getDriverName()+"成功");
            } catch (ClassNotFoundException e) {
                log.info("未找到JDBC驱动" + connpool.propertyBean.getDriverName() + ",请引入相关包");
                return null;
            }*/
    
            //基本点2、始使化时根据配置中的初始连接数创建指定数量的连接
            for (int i = 0; i < connpool.propertyBean.getInitConnections(); i++) {
                try {
                    Connection conn = connpool.NewConnection();
                    connpool.freeConnections.add(conn);
                } catch (SQLException | ClassNotFoundException e) {
                    log.error(connpool.propertyBean.getNodeName()+"节点连接池初始化失败");
                    return null;
                }
            }
    
            connpool.isActive = true;
            return connpool;
        }
    
    
    
        /**
         * 检测连接是否有效
         * @param 数据库连接对象
         * @return Boolean
         */
        private Boolean isValidConnection(Connection conn) throws SQLException{
            try {
                if(conn==null || conn.isClosed()){
                    return false;
                }
            } catch (SQLException e) {
                throw new SQLException(e);
            }
            return true;
        }
    
        /**
         * 创建一个新的连接
         * @return 数据库连接对象
         * @throws ClassNotFoundException
         * @throws SQLException
         */
        private Connection NewConnection() throws ClassNotFoundException,
                SQLException {
    
            Connection conn = null;
            try {
                if (this.propertyBean != null) {
                    //Class.forName(this.propertyBean.getDriverName());
                    conn = DriverManager.getConnection(this.propertyBean.getUrl(),
                            this.propertyBean.getUsername(),
                            this.propertyBean.getPassword());
                }
            } catch (SQLException e) {
                throw new SQLException(e);
            }
    
    
    
            return conn;
        }
    
    
        @Override
        public synchronized Connection getConnection() {
            Connection conn = null;
            if (this.getActiveNum() < this.propertyBean.getMaxConnections()) {
                // 分支1:当前使用的连接没有达到最大连接数  
                // 基本点3、在连接池没有达到最大连接数之前,如果有可用的空闲连接就直接使用空闲连接,如果没有,就创建新的连接。
                if (this.getFreeNum() > 0) {
                    // 分支1.1:如果空闲池中有连接,就从空闲池中直接获取
                    log.info("分支1.1:如果空闲池中有连接,就从空闲池中直接获取");
                    conn = this.freeConnections.pollFirst();
    
                    //连接闲置久了也会超时,因此空闲池中的有效连接会越来越少,需要另一个进程进行扫描监测,不断保持一定数量的可用连接。
                    //在下面定义了checkFreepools的TimerTask类,在checkPool()方法中进行调用。
    
                    //基本点5、由于数据库连接闲置久了会超时关闭,因此需要连接池采用机制保证每次请求的连接都是有效可用的。
                    try {
                        if(this.isValidConnection(conn)){
                            this.activeConnections.add(conn);
                            currentConnection.set(conn);
                        }else{
                            conn = getConnection();//同步方法是可重入锁
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                } else {
                    // 分支1.2:如果空闲池中无可用连接,就创建新的连接
                    log.info("分支1.2:如果空闲池中无可用连接,就创建新的连接");
                    try {
                        conn = this.NewConnection();
                        this.activeConnections.add(conn);
                    } catch (ClassNotFoundException | SQLException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                // 分支2:当前已到达最大连接数  
                // 基本点4、当连接池中的活动连接数达到最大连接数,新的请求进入等待状态,直到有连接被释放。
                log.info("分支2:当前已到达最大连接数 ");
                long startTime = System.currentTimeMillis();
    
                //进入等待状态。等待被notify(),notifyALL()唤醒或者超时自动苏醒  
                try{
                    this.wait(this.propertyBean.getConninterval());  
                }catch(InterruptedException e) {  
                    log.error("线程等待被打断");  
                }
    
                //若线程超时前被唤醒并成功获取连接,就不会走到return null。
                //若线程超时前没有获取连接,则返回null。
                //如果timeout设置为0,就无限重连。
                if(this.propertyBean.getTimeout()!=0){
                    if(System.currentTimeMillis() - startTime > this.propertyBean.getTimeout())  
                        return null;  
                }
                conn = this.getConnection();
    
            }
            return conn;
        }
    
    
        @Override
        public Connection getCurrentConnecton() {
            Connection conn=currentConnection.get();
            try {
                if(! isValidConnection(conn)){
                    conn=this.getConnection();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return conn;
        }
    
    
        @Override
        public synchronized void releaseConn(Connection conn) throws SQLException {
    
            log.info(Thread.currentThread().getName()+"关闭连接:activeConnections.remove:"+conn);
            this.activeConnections.remove(conn);
            this.currentConnection.remove();
            //活动连接池删除的连接,相应的加到空闲连接池中
            try {
                if(isValidConnection(conn)){
                    freeConnections.add(conn);
                }else{
                    freeConnections.add(this.NewConnection());
                }
    
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
            //唤醒getConnection()中等待的线程
            this.notifyAll();
        }
    
        @Override
        public synchronized void destroy() {
            for (Connection conn : this.freeConnections) {  
                try {
                    if (this.isValidConnection(conn)) { 
                        conn.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }   
            }  
            for (Connection conn : this.activeConnections) {  
                try {
                    if (this.isValidConnection(conn)) { 
                        conn.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                } 
            }
            this.isActive = false;
            this.freeConnections.clear();
            this.activeConnections.clear();
        }
    
        @Override
        public boolean isActive() {
            return this.isActive;
        }
    
    
        @Override
        public void checkPool() {
    
            final String nodename=this.propertyBean.getNodeName();
    
            ScheduledExecutorService ses=Executors.newScheduledThreadPool(2);
    
            //功能一:开启一个定时器线程输出状态
            ses.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    System.out.println(nodename +"空闲连接数:"+getFreeNum());  
                    System.out.println(nodename +"活动连接数:"+getActiveNum());   
    
                }
            }, 1, 1, TimeUnit.SECONDS);
    
            //功能二:开启一个定时器线程,监测并维持空闲池中的最小连接数
            ses.scheduleAtFixedRate(new checkFreepools(this), 1, 5, TimeUnit.SECONDS);
        }
    
        @Override
        public synchronized int getActiveNum() {
            return this.activeConnections.size();
        }
    
        @Override
        public synchronized int getFreeNum() {
            return this.freeConnections.size();
        }
    
        //基本点6、连接池内部要保证指定最小连接数量的空闲连接
        class checkFreepools extends TimerTask {
            private ConnectionPool conpool = null;
    
            public checkFreepools(ConnectionPool cp) {
                this.conpool = cp;
            }
    
            @Override
            public void run() {
                if (this.conpool != null && this.conpool.isActive()) {
                    int poolstotalnum = conpool.getFreeNum()
                            + conpool.getActiveNum();
                    int subnum = conpool.propertyBean.getMinConnections()
                            - poolstotalnum;
    
                    if (subnum > 0) {
                        System.out.println(conpool.propertyBean.getNodeName()
                                + "扫描并维持空闲池中的最小连接数,需补充" + subnum + "个连接");
                        for (int i = 0; i < subnum; i++) {
                            try {
                                conpool.freeConnections
                                        .add(conpool.NewConnection());
                            } catch (ClassNotFoundException | SQLException e) {
                                e.printStackTrace();
                            }
                        }
    
                    }
                }
    
            }
    
        }
    
    
    
    }

    ConnectionManager.java

    package com.twm.TDBConnectionPool;
    
    import java.sql.Connection;
    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.HashSet;
    import java.util.Hashtable;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Queue;
    import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;
    
    import org.apache.log4j.Logger;
    import com.twm.TDBConnectionPool.util.PropertiesManager;
    
    
    public class ConnectionManager {
        private final Logger log = Logger.getLogger(ConnectionPool.class);
    
        private static ConnectionManager dbm = null;
    
        /** 
         * 加载的驱动器名称集合 
         */  
        private Set<String> drivers = new HashSet<String>(); 
    
        /**
         * 数据库连接池字典
         * 为每个节点创建一个连接池(可配置多个节点)
         */
        private ConcurrentHashMap<String, IConnectionPool> pools = new ConcurrentHashMap<String, IConnectionPool>();
    
    
    
        private ConnectionManager() {
            createPools();
        }
    
    
        /**
         * 装载JDBC驱动程序,并创建连接池
         */
        private void createPools() {
            String str_nodenames = PropertiesManager.getProperty("nodename");
            //基本点1、可配置并管理多个连接节点的连接池
            for (String str_nodename : str_nodenames.split(",")) {
                DBPropertyBean dbProperty = new DBPropertyBean();
                dbProperty.setNodeName(str_nodename);
    
                //验证url配置正确性
                String url = PropertiesManager.getProperty(str_nodename + ".url");
                if (url == null) {
                    log.error(str_nodename+"节点的连接字符串为空,请检查配置文件");
                    continue;
                }
                dbProperty.setUrl(url);
    
                //验证driver配置正确性
                String driver = PropertiesManager.getProperty(str_nodename + ".driver");
                if (driver == null) {
                    log.error(str_nodename+"节点的driver驱动为空,请检查配置文件");
                    continue;
                }
                dbProperty.setDriverName(driver);
    
    
                //验证user配置正确性
                String user = PropertiesManager.getProperty(str_nodename + ".user");
                if (user == null) {
                    log.error(str_nodename+"节点的用户名设置为空,请检查配置文件");
                    continue;
                }
                dbProperty.setUsername(user);
    
    
                //验证password配置正确性
                String password = PropertiesManager.getProperty(str_nodename + ".password");
                if (password == null) {
                    log.error(str_nodename+"节点的密码设置为空,请检查配置文件");
                    continue;
                }
                dbProperty.setPassword(password);
    
                //验证最小连接数配置正确性
                String str_minconnections=PropertiesManager.getProperty(str_nodename + ".minconnections");
                int minConn;
                try {
                    minConn = Integer.parseInt(str_minconnections);
                } catch (NumberFormatException e) {
                    log.error(str_nodename + "节点最小连接数设置错误,默认设为5");
                    minConn=5;
                }
                dbProperty.setMinConnections(minConn);
    
                //验证初始连接数配置正确性
                String str_initconnections=PropertiesManager.getProperty(str_nodename + ".initconnections");
                int initConn;
                try {
                    initConn = Integer.parseInt(str_initconnections);
                } catch (NumberFormatException e) {
                    log.error(str_nodename + "节点初始连接数设置错误,默认设为5");
                    initConn=5;
                }
                dbProperty.setInitConnections(initConn);
    
                //验证最大连接数配置正确性
                String str_maxconnections=PropertiesManager.getProperty(str_nodename + ".maxconnections");
                int maxConn;
                try {
                    maxConn = Integer.parseInt(str_maxconnections);
                } catch (NumberFormatException e) {
                    log.error(str_nodename + "节点最大连接数设置错误,默认设为20");
                    maxConn=20;
                }
                dbProperty.setMaxConnections(maxConn);
    
                //验证conninterval配置正确性
                String str_conninterval=PropertiesManager.getProperty(str_nodename + ".conninterval");
                int conninterval;
                try {
                    conninterval = Integer.parseInt(str_conninterval);
                } catch (NumberFormatException e) {
                    log.error(str_nodename + "节点重新连接间隔时间设置错误,默认设为500ms");
                    conninterval = 500;
                }
                dbProperty.setConninterval(conninterval);
    
                //验证timeout配置正确性
                String str_timeout=PropertiesManager.getProperty(str_nodename + ".timeout");
                int timeout;
                try {
                    timeout = Integer.parseInt(str_timeout);
                } catch (NumberFormatException e) {
                    log.error(str_nodename + "节点连接超时时间设置错误,默认设为2000ms");
                    timeout = 2000;
                }
                dbProperty.setTimeout(timeout);
    
                //创建驱动
                if(!drivers.contains(dbProperty.getDriverName())){
                    try {
                        Class.forName(dbProperty.getDriverName());
                        log.info("加载JDBC驱动"+dbProperty.getDriverName()+"成功");
                        drivers.add(dbProperty.getDriverName());
                    } catch (ClassNotFoundException e) {
                        log.error("未找到JDBC驱动" + dbProperty.getDriverName() + ",请引入相关包");
                        e.printStackTrace();
                    }
                }
    
                //创建连接池。这里采用同步方法实现的连接池类ConnectionPool。
                //(如果后面我们还有别的实现方式,只需要更改这里就行了。)
                IConnectionPool cp = ConnectionPool.CreateConnectionPool(dbProperty);
                if (cp != null) {
                    pools.put(str_nodename, cp);
                    cp.checkPool();
                    log.info("创建" + str_nodename + "数据库连接池成功");
                } else {
                    log.info("创建" + str_nodename + "数据库连接池失败");
                }
            }
    
        }
    
        /**
         * 获得单例
         * 
         * @return DBConnectionManager单例
         */
        public synchronized static ConnectionManager getInstance() {
            if (dbm == null) {
                dbm = new ConnectionManager();
            }
            return dbm;
        }
    
        /**
         * 从指定连接池中获取可用连接
         * 
         * @param poolName要获取连接的连接池名称
         * @return连接池中的一个可用连接或null
         */
        public Connection getConnection(String poolName) {
            IConnectionPool pool =  pools.get(poolName);
            return pool.getConnection();
        }
    
    
        /**
         * 回收指定连接池的连接
         * 
         * @param poolName连接池名称
         * @param conn要回收的连接
         */
        public void closeConnection(String poolName, Connection conn) throws SQLException {
            IConnectionPool pool = pools.get(poolName);
            if (pool != null) {
                try {
                    pool.releaseConn(conn);
                } catch (SQLException e) {
                    log.error("回收"+poolName+"池中的连接失败。");
                    throw new SQLException(e);
                }
            }else{
                log.error("找不到"+poolName+"连接池,无法回收");
            }
        }
    
        /**
         * 关闭所有连接,撤销驱动器的注册
         */
        public void destroy() {
            for (Map.Entry<String, IConnectionPool> poolEntry : pools.entrySet()) {
                IConnectionPool pool = poolEntry.getValue();
                pool.destroy();
            }
            log.info("已经关闭所有连接");
        }
    }

    PropertiesManager.java

    package com.twm.TDBConnectionPool.util;
    
    import java.io.IOException;
    import java.util.Enumeration;
    import java.util.Properties;
    
    
    public class PropertiesManager {
        private static Properties pro = new Properties();
    
        private PropertiesManager() {
    
        }
    
        static {
            try {
                pro.load(PropertiesManager.class.getClassLoader().getResourceAsStream("DB.properties"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static String getProperty(String key) {
            return pro.getProperty(key);
        }
    
        public static String getProperty(String key, String defaultValue) {
            return pro.getProperty(key, defaultValue);
        }
    
        public static Enumeration<?> propertiesNames() {
            return pro.propertyNames();
        }
    }

    testpool.java

    package com.twm.TDBConnectionPool.run;
    
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.Iterator;
    import java.util.List;
    
    import com.twm.TDBConnectionPool.ConnectionManager;
    import com.twm.TDBConnectionPool.DBPropertyBean;
    import com.twm.TDBConnectionPool.IConnectionPool;
    import com.twm.TDBConnectionPool.util.PropertiesManager;
    
    
    public class testpool {
    
        /**
         * @param args
         * @throws InterruptedException 
         */
        public static void main(String[] args) throws InterruptedException {
    
            List<Thread> threadlist=new ArrayList<Thread>();
            for(int i=1;i<=3;i++){
                Thread subThread = new Thread(new workrun(i));
                subThread.start();
                threadlist.add(subThread);
            }
            for (Iterator<Thread> iterator = threadlist.iterator(); iterator.hasNext();) {
                Thread thread = iterator.next();
                thread.join();
            }
            //ConnectionManager.getInstance().destroy();
    
        }
    
    }
    class workrun implements Runnable{
        int i;
        public workrun(int i){
            this.i=i;
        }
        @Override
        public void run() {
            ConnectionManager cm = ConnectionManager.getInstance();
    
             //1.从数据池中获取数据库连接
            Connection conn = cm.getConnection("default");
            System.out.println("线程 " + Thread.currentThread().getName() + "获得连接:" + conn);
    
            //模拟查询耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
    
             //2.获取用于向数据库发送sql语句的statement
            Statement st = null;
            try {
                st = conn.createStatement();
            } catch (SQLException e) {
                e.printStackTrace();
            }
    
            String sql = "select * from product where id="+i;
            //3.向数据库发sql,并获取代表结果集的resultset
            //4.取出结果集的数据
            ResultSet rs = null;
            try {
                rs = st.executeQuery(sql);
                while(rs.next()){
                    System.out.println("productname=" + rs.getObject("productname"));
                    //System.out.println("price=" + rs.getObject("price"));
                    //System.out.println("cateid=" + rs.getObject("cateid"));
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
    
            //模拟查询耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //5.关闭链接,释放资源
            try {
                rs.close();
                st.close();
                //conn.close();
                cm.closeConnection("default",conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
    
        }
    
    }

    DB.properties

    nodename=default,testdb
    
    default.driver=com.mysql.jdbc.Driver
    default.url=jdbc:mysql://localhost:3306/tangwenmingdb
    default.user=root
    default.password=root
    default.maxconnections=10
    default.minconnections=5
    default.initconnections=2
    default.conninterval=500
    default.timeout = 5000
    
    testdb.driver=com.mysql.jdbc.Driver
    testdb.url=jdbc:mysql://192.168.1.17:3306/test
    testdb.user=root
    testdb.password=root
    testdb.maxconnections=10
    testdb.minconnections=2
    testdb.initconnections=2
    testdb.conninterval= 500
    testdb.timeout = 5000

    product表结构和数据:

    /*!40101 SET NAMES utf8 */;
    create table `product` (
        `id` int (10),
        `productname` varchar (150),
        `price` Decimal (12),
        `cateid` int (11)
    ); 
    insert into `product` (`id`, `productname`, `price`, `cateid`) values('1','iphone 8 plus','5700.00','1');
    insert into `product` (`id`, `productname`, `price`, `cateid`) values('2','华为G7','1700.00','1');
    insert into `product` (`id`, `productname`, `price`, `cateid`) values('3','HTC','1200.00','1');

    这里写图片描述

    至此,我们用同步方法synchronized实现了一个简单的数据库连接池。大家可以试着调整DB.properties里的参数配置(比如maxconnections,minconnections,initconnections),观察控制台的输出变化。也可以调整程序中的thread.sleep时长观察。


    代理Connection

    现在来做一个优化,各位一定要看完,这里搞得我很痛苦,也留下一个未解决的问题。希望大家帮忙。
    通常,用户在使用完毕数据库连接后会调用conn.close()关闭连接,而不是调用我们程序中的ConnectionManager.closeConnection("default",conn);方法关闭。
    为了让conn.close和我们自己写的closeConnection()方法保持一致的效果,就必须要改写connection的close()方法,在这个场景下,很容易想到使用代理方式。这里先采用动态代理方式来实现:

    通过改造ConnectionPool类:
    1、在ConnectionPool类中添加一个内部InvocationHandler实现类(和checkFreepools类平级的)

    class ConProxyHandler implements InvocationHandler{
            private Connection conn;
            private ConnectionPool cp;
    
            public ConProxyHandler(ConnectionPool cp, Connection conn) {
                this.cp = cp;
                this.conn = conn;
            }
    
            @Override
    
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                if(method.getName().equals("close")){
                    cp.releaseConn(this.conn);
                    return null;
                }else{
                    //如果不是调用close方法,就原样处理
                    return method.invoke(this.conn, args);
                }
            }
    
        }

    2、改造ConnectionPool类的NewConnection()方法,在return conn之前,加上一段

    if (conn != null) {
        ConProxyHandler handler = new ConProxyHandler(this, conn);
        Connection conn_return = (Connection) Proxy.newProxyInstance(
                ConnectionPool.class.getClassLoader(),
                new Class[] { Connection.class }, handler);
        System.out.println("生成代理类:" + conn_return);
        return conn_return;
    }

    最后在调用的testpool.java中把

    cm.closeConnection("default",conn);

    改为:

    conn.close();

    然后运行,前面都正常,到最后出问题了:
    当三个线程分别关闭连接后,空闲连接数加上去了,但活动连接数并没有相应的减少。
    心碎。。。。到底哪出问题了呢?

    打断点跟踪发现this.activeConnections.remove(conn);这句并没有成功移除conn。
    然后开始检查:
    1、打印ConProxyHandler类中的cp和ConnectionPool类中CreateConnectionPool()方法里的connpool,发现hashcode是一致的
    2、在ConProxyHandler类中的invoke方法中加上对equals方法的处理。发现压根不会进到这里。
    3、跟踪到this.activeConnections.remove(conn);
    发现this.activeConnections里保存了三个连接类
    这里写图片描述

    并且传入参数conn的hashcode和this.activeConnections中其中一个连接类是一致的

    这里写图片描述

    但是运行完this.activeConnections.remove(conn)后到下句,发现this.activeConnections里依然存在com.mysql.jdbc.JDBC4Connection@109cdd5对象。

    为什么删不掉呢?
    太打击积极性了,也不知道怎么调试查看原因。开始怀疑自己还适合做开发吗?

    即然删不掉,那就将this.activeConnections改成hashmap,用key来删掉。
    改动ConnectionPool .java如下:

    package com.twm.TDBConnectionPool;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.sql.Connection;
    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.Hashtable;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.TimerTask;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    import org.apache.log4j.Logger;
    
    
    /** 
     * @author TangWenming  E-mail:soonfly#gmail.com
     * @version 创建时间:2017-5-16 下午5:49:17 
     * 类说明 :友元类,包内可见,不提供给客户程序直接访问。
     */
    class ConnectionPool implements IConnectionPool {
    
        private static final Logger log = Logger.getLogger(ConnectionPool.class);
    
        private DBPropertyBean propertyBean=null;
    
        //连接池可用状态
        private Boolean isActive = true;
    
        // 空闲连接池 。
        private Hashtable<Integer,Connection> freeConnections = new Hashtable<Integer,Connection>();  
        // 活动连接池。
        private Hashtable<Integer,Connection> activeConnections = new Hashtable<Integer,Connection>(); 
    
        //当前线程获得的连接
        private ThreadLocal<Connection> currentConnection= new ThreadLocal<Connection>();
    
        //构造方法无法返回null,所以取消掉。在下面增加了CreateConnectionPool静态方法。
        private ConnectionPool(){
            super();
        }
    
        public static ConnectionPool CreateConnectionPool(DBPropertyBean propertyBean) {
            ConnectionPool connpool=new ConnectionPool();
            connpool.propertyBean = propertyBean;
    
            //加载驱动 
    
            //在多节点环境配置下,因为在这里无法判断驱动是否已经加载,可能会造成多次重复加载相同驱动。
            //因此加载驱动的动作,挪到connectionManager管理类中去实现了。
            /*try {
                Class.forName(connpool.propertyBean.getDriverName());
                log.info("加载JDBC驱动"+connpool.propertyBean.getDriverName()+"成功");
            } catch (ClassNotFoundException e) {
                log.info("未找到JDBC驱动" + connpool.propertyBean.getDriverName() + ",请引入相关包");
                return null;
            }*/
    
            //基本点2、始使化时根据配置中的初始连接数创建指定数量的连接
            for (int i = 0; i < connpool.propertyBean.getInitConnections(); i++) {
                try {
                    Connection conn = connpool.NewConnection();
                    connpool.freeConnections.put(conn.hashCode(), conn);
                } catch (SQLException | ClassNotFoundException e) {
                    log.error(connpool.propertyBean.getNodeName()+"节点连接池初始化失败");
                    return null;
                }
            }
    
            connpool.isActive = true;
            return connpool;
        }
    
    
    
        /**
         * 检测连接是否有效
         * @param 数据库连接对象
         * @return Boolean
         */
        private Boolean isValidConnection(Connection conn) throws SQLException{
            try {
                if(conn==null || conn.isClosed()){
                    return false;
                }
            } catch (SQLException e) {
                throw new SQLException(e);
            }
            return true;
        }
    
        /**
         * 创建一个新的连接
         * @return 数据库连接对象
         * @throws ClassNotFoundException
         * @throws SQLException
         */
        private Connection NewConnection() throws ClassNotFoundException,
                SQLException {
    
            Connection conn = null;
            try {
                if (this.propertyBean != null) {
                    //Class.forName(this.propertyBean.getDriverName());
                    conn = DriverManager.getConnection(this.propertyBean.getUrl(),
                            this.propertyBean.getUsername(),
                            this.propertyBean.getPassword());
                }
            } catch (SQLException e) {
                throw new SQLException(e);
            }
    
            if (conn != null) {
                ConProxyHandler handler = new ConProxyHandler(this, conn);
                Connection conn_return = (Connection) Proxy.newProxyInstance(
                        ConnectionPool.class.getClassLoader(),
                        new Class[] { Connection.class }, handler);
                //System.out.println("生成代理类:" + conn_return);
                return conn_return;
            }
    
            return conn;
        }
    
    
        @Override
        public synchronized Connection getConnection() {
            Connection conn = null;
            if (this.getActiveNum() < this.propertyBean.getMaxConnections()) {
                // 分支1:当前使用的连接没有达到最大连接数  
                // 基本点3、在连接池没有达到最大连接数之前,如果有可用的空闲连接就直接使用空闲连接,如果没有,就创建新的连接。
                if (this.getFreeNum() > 0) {
                    // 分支1.1:如果空闲池中有连接,就从空闲池中直接获取
                    int key = (int) this.freeConnections.keySet().toArray()[0];
                    log.info("分支1.1:如果空闲池中有连接,"+Thread.currentThread().getName()+"就从空闲池中直接获取"+key);
                    //conn = this.freeConnections.pollFirst();
    
                    conn = this.freeConnections.get(key);
                    this.freeConnections.remove(key);
    
    
                    //连接闲置久了也会超时,因此空闲池中的有效连接会越来越少,需要另一个进程进行扫描监测,不断保持一定数量的可用连接。
                    //在下面定义了checkFreepools的TimerTask类,在checkPool()方法中进行调用。
    
                    //基本点5、由于数据库连接闲置久了会超时关闭,因此需要连接池采用机制保证每次请求的连接都是有效可用的。
                    try {
                        if(this.isValidConnection(conn)){
                            this.activeConnections.put(conn.hashCode(), conn);
                            currentConnection.set(conn);
                        }else{
                            conn = getConnection();//同步方法是可重入锁
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(this.propertyBean.getNodeName() +"空闲连接数:"+getFreeNum());  
                    System.out.println(this.propertyBean.getNodeName() +"活动连接数:"+getActiveNum());
                } else {
    
                    try {
                        conn = this.NewConnection();
                        // 分支1.2:如果空闲池中无可用连接,就创建新的连接
                        log.info("分支1.2:如果空闲池中无可用连接,"+Thread.currentThread().getName()+"就创建新的连接"+conn.hashCode());
                        this.activeConnections.put(conn.hashCode(), conn);
                    } catch (ClassNotFoundException | SQLException e) {
                        e.printStackTrace();
                    }
                    System.out.println(this.propertyBean.getNodeName() +"空闲连接数:"+getFreeNum());  
                    System.out.println(this.propertyBean.getNodeName() +"活动连接数:"+getActiveNum());
                }
            } else {
                // 分支2:当前已到达最大连接数  
                // 基本点4、当连接池中的活动连接数达到最大连接数,新的请求进入等待状态,直到有连接被释放。
                log.info("分支2:"+Thread.currentThread().getName()+":当前已到达最大连接数 ");
                long startTime = System.currentTimeMillis();
    
                //进入等待状态。等待被notify(),notifyALL()唤醒或者超时自动苏醒  
                try{
                    this.wait(this.propertyBean.getConninterval());  
                }catch(InterruptedException e) {  
                    log.error("线程等待被打断");  
                }
    
                //若线程超时前被唤醒并成功获取连接,就不会走到return null。
                //若线程超时前没有获取连接,则返回null。
                //如果timeout设置为0,就无限重连。
                if(this.propertyBean.getTimeout()!=0){
                    if(System.currentTimeMillis() - startTime > this.propertyBean.getTimeout())  
                        return null;  
                }
                conn = this.getConnection();
    
            }
            return conn;
        }
    
    
        @Override
        public Connection getCurrentConnecton() {
            Connection conn=currentConnection.get();
            try {
                if(! isValidConnection(conn)){
                    conn=this.getConnection();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return conn;
        }
    
    
        @Override
        public synchronized void releaseConn(Connection conn) throws SQLException {
    
            log.info(Thread.currentThread().getName()+"关闭连接:activeConnections.remove:"+conn.hashCode());
            this.activeConnections.remove(conn.hashCode());
            this.currentConnection.remove();
            //活动连接池删除的连接,相应的加到空闲连接池中
            try {
                if(isValidConnection(conn)){
                    freeConnections.put(conn.hashCode(), conn);
                }else{
                    Connection newconn = this.NewConnection();
                    freeConnections.put(newconn.hashCode(), newconn);
                }
    
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
            System.out.println(this.propertyBean.getNodeName() +"空闲连接数:"+getFreeNum());  
            System.out.println(this.propertyBean.getNodeName() +"活动连接数:"+getActiveNum());
            //唤醒getConnection()中等待的线程
            this.notifyAll();
        }
    
        @Override
        public synchronized void destroy() {
            for(int key:this.freeConnections.keySet()){
                Connection conn = this.freeConnections.get(key);
                try {
                    if (this.isValidConnection(conn)) { 
                        conn.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            for(int key:this.activeConnections.keySet()){
                Connection conn = this.activeConnections.get(key);
                try {
                    if (this.isValidConnection(conn)) { 
                        conn.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            this.isActive = false;
            this.freeConnections.clear();
            this.activeConnections.clear();
        }
    
        @Override
        public boolean isActive() {
            return this.isActive;
        }
    
    
        @Override
        public void checkPool() {
    
            /*final String nodename=this.propertyBean.getNodeName();
    
            ScheduledExecutorService ses=Executors.newScheduledThreadPool(2);
    
            //功能一:开启一个定时器线程输出状态
            ses.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    System.out.println(nodename +"空闲连接数:"+getFreeNum());  
                    System.out.println(nodename +"活动连接数:"+getActiveNum());   
    
                }
            }, 1, 1, TimeUnit.SECONDS);
    
            //功能二:开启一个定时器线程,监测并维持空闲池中的最小连接数
            ses.scheduleAtFixedRate(new checkFreepools(this), 1, 5, TimeUnit.SECONDS);*/
        }
    
        @Override
        public synchronized int getActiveNum() {
            return this.activeConnections.size();
        }
    
        @Override
        public synchronized int getFreeNum() {
            return this.freeConnections.size();
        }
    
        //基本点6、连接池内部要保证指定最小连接数量的空闲连接
        class checkFreepools extends TimerTask {
            private ConnectionPool conpool = null;
    
            public checkFreepools(ConnectionPool cp) {
                this.conpool = cp;
            }
    
            @Override
            public void run() {
                if (this.conpool != null && this.conpool.isActive()) {
                    int poolstotalnum = conpool.getFreeNum()
                            + conpool.getActiveNum();
                    int subnum = conpool.propertyBean.getMinConnections()
                            - poolstotalnum;
    
                    if (subnum > 0) {
                        System.out.println(conpool.propertyBean.getNodeName()
                                + "扫描并维持空闲池中的最小连接数,需补充" + subnum + "个连接");
                        for (int i = 0; i < subnum; i++) {
                            try {
                                Connection newconn = conpool.NewConnection();
                                conpool.freeConnections.put(newconn.hashCode(), newconn);
                            } catch (ClassNotFoundException | SQLException e) {
                                e.printStackTrace();
                            }
                        }
    
                    }
                }
    
            }
    
        }
    
        class ConProxyHandler implements InvocationHandler{
            private Connection conn;
            private ConnectionPool cp;
    
            public ConProxyHandler(ConnectionPool cp, Connection conn) {
                this.cp = cp;
                this.conn = conn;
            }
    
            @Override
    
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                if(method.getName().equals("close")){
                    cp.releaseConn(this.conn);
                    return this.conn;
                }else{
                            return true;
                        }
                    }else {
                        return true;
                    }
                }else{
                    //如果不是调用close方法,就原样处理
                    return method.invoke(this.conn, args);
                }
            }
    
        }
    
    }

    再次运行。。。。又出西西了。发现如果某个线程获取到的是另一个线程使用过的connection,就无法调用close方法正常关闭。

    可以把下面这几个参数值改动一下,很快就见效:

    default.maxconnections=2
    default.minconnections=1
    default.initconnections=1

    运行程序后,可以看到最后获取到连接的线程,并没能成功调用con.close()。

    至此,数据库连接池是成功实现了。
    但是最后优化过程中使用代理Connection对象的方式并没有成功。希望大家帮我一起分析一下是什么原因导致的,可以留言,也可站内信给我。主要为了弄明白到底是怎么回事,不甚感激!

    展开全文
  • 下面的案例就是使用线程同时查询出con列表中两个sql的数据并append到全局变量data中 import threading import time import pymysql data= [] def conect(sql): global data # 打开数据库连接 db = pymysql....
  • 日常项目中,经常会出现一个场景,同时批量插入数据库数据,由于逻辑复杂或者其它原因,我们无法使用sql进行批量插入,这个时候我们首先想到多线程并发插入,但是如何控制事务呢 … 直接上干货 实现效果 开启多条子...
  •  例1:cursor会为空的情况,打印cursor的时候不为空,使用的时候就为空了,原因考虑是,多线程操作数据库导致数据库异常  例2:提示正在尝试打开一个已经被关闭的数据库:在多线程访问数据库的时候会出现这样的...
  • 最近在做项目的时候出了个问题,大家很熟悉的指针异常,说实话已经很久没遇到这个异常了,就算有时候遇到也就是分分钟解决的事,但是这次搞了一晚上,纠结了一晚上。。。 用的框架是springmvc 当时的...
  • 线程中使用@Resource或者@Autowired注入全部NULL, 原来是Spring不能在线程中注入。 有两种方法可以参考,以下方法本人在实际项目中都已使用到过 要的Bean作为线程的的构造函数的参数传入 使用...
  • Python爬虫进阶之多线程爬取数据并保存到数据库

    千次阅读 多人点赞 2020-03-24 16:38:14
    总体程序我会利用多线程的方式来充分利用CPU的空闲时间,其中我也会加入数据库,有机会的话还想用邮件模块来大家展示定时给自己发送邮件。哈哈哈,本博主就是喜欢如此花里胡哨。 一、网页分析 首先我的想法是爬取...
  • System.out.println("当前运行的线程名称:" + Thread.currentThread().getName()); stockService.uptStock(stockBeans); System.out.println("当前运行的线程ID:" + Thread.currentThread().getId()); ...
  • public class ConnectionPool { /** * 空闲连接池 */ private LinkedList<Connection> pool =...当在指定的时间内无法获取到连接时,将会返回为空,核心代码fetchConnection. 摘自《Java并发编程的艺术》
  • 30000条数据循环了4次,方法进去了,但是好像没有执行,最后数据库只插入了1000条数据,麻烦大神们张张眼,这是咋回事呀
  • 串行效率低,耗时长,为了提高效率,这个时候我们首先想到多线程并发插入,但是如何控制事务呢 … 直接上干货 实现效果 开启多条子线程,并发插入数据库 当其中一条线程出现异常,或者处理结果非预期结果...
  • java多线程 使用CompletionService提交异步任务,直接上代码 final int count = 1000; public static final ExecutorService executorService = Executors.newFixedThreadPool(3); public void deal()...
  • java excel导入并多线程批量插入数据库

    万次阅读 多人点赞 2018-06-14 17:02:29
    最近写了个excel导入并多线程持久化到数据库的功能,捣鼓了一天才弄好,先记录下来防止自己忘了。 (1)先controller类中方法。 @AccessLog @ApiOperation(value = "导入excel", httpMethod = "...
  • java多线程读取多个文件 导入数据库

    万次阅读 2016-10-14 16:59:21
    近期在做java读文件的项目,由于数据量较大,因此研究了一下多线程,总结了一下:一. 多个线程读文件和单个线程读文件,效率差不多,甚至可能不如单线程,原因如下:如果只是单纯的读文件,一个线程足够了,因为一般...
  • 一个多线程引起的指针

    千次阅读 2018-02-26 07:38:14
    先来看一下出现的问题,下面代码是将这个任务放入线程池中执行,获取到bitmap后再在UI线程做一些UI的更新。一些特殊场景下走到mVideoView这一行会出现指针,界面已经退出,此时用户感知不到特殊场景:非wifi且是播放...
  • 问题描述:在Controller中使用@Autowired注入的db对象,可以操作数据库,但是在多线程中注入的dbnull,导致一直指针异常 解决办法: 1.用单例模式创建一个数据库辅助类: public class DBHelper { ...
  • C#--多进程多线程访问数据库

    千次阅读 2012-11-28 17:24:28
    C#--多进程多线程访问数据库 如何让多进程多线程访问数据库,而不会选择相同的数据,这在设计分布式程序的时候经常用到,多台机器的多个进程,每个进程都有多个线程,每个线程要从数据库里取数据来处理,要实现不能...
  • python多线程插入1万条数据

    千次阅读 2019-11-30 13:21:40
    在业务中,经常碰到需要从外部批量读取数据然后导入到mysql等数据库的操作,通常情况下,我们使用一个insert语句就可以完成,但在数据量上万甚至百万的时候,这样做是不是太耗时了呢? 下面我们先来看一个简单的...
  • 1数据库类  """ 使用须知: 代码中数据表名 aces ,需要更改该数据表名称的注意更改 """ import pymysql class Database(): # 设置本地数据库用户名和密码 host = "localhost" user = "root" password = ...
  • 多线程死锁以及解决方法

    千次阅读 2017-06-09 23:11:44
    大多数软件测试产生不了足够的负载,所以不可能暴露所有的线程错误。在这里中,下面将讨论开发过程常见的4类典型的死锁和解决对策。 (1)数据库死锁   在数据库中,如果一个连接占用了另一个连接所需的数据库锁...
  • 在SQLiteOpenHelper里有两个构造方法可以重写,参数四个,第一个参数是Context,第二个是数据库名,第三个参数允许我们在查询数据时返回一个自定义的Cursor,一般都是null,第四个参数就是当前数据库的版本号,...
  • Python3 多线程(连接池)操作MySQL插入数据

    万次阅读 热门讨论 2019-06-01 16:07:55
    DBUtils : 允许在多线程应用和数据库之间连接的模块套件 Threading : 提供多线程功能 2.创建连接池 PooledDB 基本参数: mincached : 最少的空闲连接数,如果空闲连接数小于这个数,Pool自动创建新连接; maxcached ...
  • 多线程 在单独线程中运行任务的简单过程: 1. 定义一个实现了Runnable接口的类,任务代码写到run方法中 2. 创建一个该类的实例 3. 将这个实例传递给Thread类的构造体,实例化一个Thread对象 4. 通过Thread...
  • 如何让多进程多线程访问数据库,而不会选择相同的数据,这在设计分布式程序的时候经常用到,多台机器的多个进程,每个进程都有多个线程,每个线程要从数据库里取数据来处理,要实现不能漏取数据,也不能重复取数据,...
  • Java多线程超详解

    万次阅读 多人点赞 2019-06-11 01:00:30
    随着计算机的配置越来越高,我们需要将进程进一步优化,细分线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,今天本帅将记录自己线程的学习。 线程的相关API //获取当前...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 167,858
精华内容 67,143
关键字:

多线程数据库为空