精华内容
下载资源
问答
  • 一 前言需要处理的问题是:后台数据库中的表有一个src属性,该属性列有四种不同的情况,这个属性是业务相关的,只能够在源码中可以查找到。由于涉及的数据库表有700多个,所以用这个工具来对表进行分类。先对表的src...

    一   前言

    需要处理的问题是:后台数据库中的表有一个src属性,该属性列有四种不同的情况,这个属性是业务相关的,只能够在源码中可以查找到。由于涉及的数据库表有700多个,所以用这个工具来对表进行分类。先对表的src属性进行统一个假设,如果HTTP请求返回的是200,则说明与假设是匹配的,如果返回的是500错误,则进行下一次筛选。

    二    POST实现

    package urltest;

    import java.io.BufferedReader;

    import java.io.IOException;

    import java.io.InputStreamReader;

    import java.io.PrintWriter;

    import java.net.URL;

    import java.net.URLConnection;

    public class PostTest {

    public static String sendPost(String url) {

    PrintWriter out = null;

    BufferedReader in = null;

    String result = "";

    try {

    URL realUrl = new URL(url);

    URLConnection conn = realUrl.openConnection();

    /*用于装载HTTP请求的头信息,可以通过postman里面设置的信息进行填写,也可以通过查看一条正常的发送信息进行填写*/

    conn.setRequestProperty("accept", "application/json");

    conn.setRequestProperty("Content-Type", "application/json");

    conn.setRequestProperty("connection", "Keep-Alive");

    conn.setRequestProperty("Authorization", "Basic cm9vdDpwdWJsaWM=");

    conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36");

    conn.setUseCaches(false);

    conn.setDoOutput(true);

    conn.setDoInput(true);

    out = new PrintWriter(conn.getOutputStream());

    out.flush();

    in = new BufferedReader(new InputStreamReader(conn.getInputStream()));

    /*下面几行注释是用来接受post的返回值的代码,可以查看每一条请求返回的数据*/

    // String line;

    // while ((line = in.readLine()) != null) {

    // result += line;

    // }

    result = "src = eml";

    } catch (Exception e) {

    System.out.println("发送 POST 请求出现异常!"+e);

    result = "src != eml";//这个主要是用于筛选出不能够匹配的表名

    return result;

    }

    finally{

    try{

    if(out!=null){

    out.close();

    }

    if(in!=null){

    in.close();

    }

    }

    catch(IOException ex){

    ex.printStackTrace();

    }

    }

    return result;

    }

    }

    三   测试类

    packageurltest;importjava.io.BufferedReader;importjava.io.BufferedWriter;importjava.io.File;importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;public classTestClass {public static voidmain(String[] args) {/*读取数据并输出结果*/

    try{

    /*br用于读取表名,bw用于输出结果*/

    BufferedReader br= new BufferedReader(new InputStreamReader(new FileInputStream(new File("C:/Users/HZ08881/Desktop/TestTableSrc.txt")),"UTF-8"));

    BufferedWriter bw= new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File("C:/Users/HZ08881/Desktop/Result.txt")),"UTF-8"));

    String lineTxt= null;while ((lineTxt = br.readLine()) != null) {

    String[] urls= lineTxt.split(",");for(String url : urls) {

    String urlmid= "http://localhost:8181/dataview/select/c37e5169-8181-41f2-904a-afc414cb9867/"+url;

    String sr=PostTest.sendPost(urlmid);

    bw.write(url+" "+sr);

    bw.newLine();

    }

    }

    br.close();

    bw.close();

    }catch(Exception e) {

    System.err.println("read errors :" +e);

    }

    }

    }

    通过切换代码中的src属性,可以筛选出每一次假设的表名,从而达到每一个表匹配其对应属性的目的,不需去翻源码。

    四   总结

    HTTP的所有请求都是可以通过java代码来进行实现的,而且可以达到自己想要的效果。postman这种工具使用起来固然方便,但是它只用一些通用的功能,对于一些特殊的情形是无法处理的。所以遇到一些需要特殊处理的HTTP请求时,可以自己设计一个发送请求的java小工具。其中需要注意的点是:header内容往往是需要根据实际情况来配置,比如Authorization,如果配置不当的话,会报401缺少权限的错误。

    展开全文
  •  需要处理的问题是:后台数据库中的表有一个src属性,该属性列有四种不同的情况,这个属性是业务相关的,只能够在源码中可以查找到。由于涉及的数据库表有700多个,所以用这个工具来对表进行分类。先对表的src属性...

    一   前言

      需要处理的问题是:后台数据库中的表有一个src属性,该属性列有四种不同的情况,这个属性是业务相关的,只能够在源码中可以查找到。由于涉及的数据库表有700多个,所以用这个工具来对表进行分类。先对表的src属性进行统一个假设,如果HTTP请求返回的是200,则说明与假设是匹配的,如果返回的是500错误,则进行下一次筛选。

     

    二    POST实现 

     

    package urltest;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.URL;
    import java.net.URLConnection;
    
    public class PostTest {
    
        public static String sendPost(String url) {
            PrintWriter out = null;
            BufferedReader in = null;
            String result = "";
            try {
                URL realUrl = new URL(url);
    
                URLConnection conn = realUrl.openConnection();
           /*用于装载HTTP请求的头信息,可以通过postman里面设置的信息进行填写,也可以通过查看一条正常的发送信息进行填写*/
                conn.setRequestProperty("accept", "application/json");
                conn.setRequestProperty("Content-Type", "application/json");
                conn.setRequestProperty("connection", "Keep-Alive");
                conn.setRequestProperty("Authorization", "Basic cm9vdDpwdWJsaWM=");
                conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36");
                conn.setUseCaches(false);
    
                conn.setDoOutput(true);
                conn.setDoInput(true);
    
                out = new PrintWriter(conn.getOutputStream());
    
                out.flush();
    
                in = new BufferedReader(new InputStreamReader(conn.getInputStream()));

        /*下面几行注释是用来接受post的返回值的代码,可以查看每一条请求返回的数据*/ // String line; // while ((line = in.readLine()) != null) { // result += line; // } result = "src = eml"; } catch (Exception e) { System.out.println("发送 POST 请求出现异常!"+e); result = "src != eml";//这个主要是用于筛选出不能够匹配的表名 return result; } finally{ try{ if(out!=null){ out.close(); } if(in!=null){ in.close(); } } catch(IOException ex){ ex.printStackTrace(); } } return result; } }

     

    三   测试类 

    package urltest;
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    
    public class TestClass {
    
    
        public static void main(String[] args) {
    
            /* 读取数据并输出结果 */
            try {
          /*br用于读取表名,bw用于输出结果*/ BufferedReader br
    = new BufferedReader(new InputStreamReader(new FileInputStream(new File("C:/Users/HZ08881/Desktop/TestTableSrc.txt")),"UTF-8")); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File("C:/Users/HZ08881/Desktop/Result.txt")),"UTF-8")); String lineTxt = null; while ((lineTxt = br.readLine()) != null) { String[] urls = lineTxt.split(","); for (String url : urls) { String urlmid = "http://localhost:8181/dataview/select/c37e5169-8181-41f2-904a-afc414cb9867/"+url; String sr=PostTest.sendPost(urlmid); bw.write(url+" "+sr); bw.newLine(); } } br.close(); bw.close(); } catch (Exception e) { System.err.println("read errors :" + e); } } }

    通过切换代码中的src属性,可以筛选出每一次假设的表名,从而达到每一个表匹配其对应属性的目的,不需去翻源码。

     

    四   总结

      HTTP的所有请求都是可以通过java代码来进行实现的,而且可以达到自己想要的效果。postman这种工具使用起来固然方便,但是它只用一些通用的功能,对于一些特殊的情形是无法处理的。所以遇到一些需要特殊处理的HTTP请求时,可以自己设计一个发送请求的java小工具。其中需要注意的点是:header内容往往是需要根据实际情况来配置,比如Authorization,如果配置不当的话,会报401缺少权限的错误。

    转载于:https://www.cnblogs.com/jplong/p/7307409.html

    展开全文
  • 本例采用mysql数据库,因此请先下载mysql-connection.jar 众所周知,创建数据库连接需要消耗较多的资源,且创建时间也较长。...那么我们可以根据需要创建一个连接池,它负责分配、管理和释放数据库连接,它允许

    本例采用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对象的方式并没有成功。希望大家帮我一起分析一下是什么原因导致的,可以留言,也可站内信给我。主要为了弄明白到底是怎么回事,不甚感激!

    展开全文
  • Servlet超越CGI的优势之在于,不仅多个请求可以共享公用资源,而且还可以在不同用户请求之间保留持续数据。本文介绍种充分发挥该特色的实用技术,即数据库连接池。实现连接池的意义动态Web站点往往用数据库...

    分类: Java

    2006-03-28 16:06:36

    用连接池提高Servlet访问数据库的效率(1)

    Java Servlet作为首选的服务器端数据处理技术,正在迅速取代CGI脚本。Servlet超越CGI的优势之一在于,不仅多个请求可以共享公用资源,而且还可以在不同用户请求之间保留持续数据。本文介绍一种充分发挥该特色的实用技术,即数据库连接池。

    一、实现连接池的意义

    动态Web站点往往用数据库存储的信息生成Web页面,每一个页面请求导致一次数据库访问。连接数据库不仅要开销一定的通讯和内存资源,还必须完成用户验证、安全上下文配置这类任务,因而往往成为最为耗时的操作。当然,实际的连接时间开销千变万化,但1到2秒延迟并非不常见。如果某个基于数据库的Web应用只需建立一次初始连接,不同页面请求能够共享同一连接,就能获得显著的性能改善。

    Servlet是一个Java类。Servlet引擎(它可能是Web服务软件的一部分,也可能是一个独立的附加模块)在系统启动或Servlet第一次被请求时将该类装入Java虚拟机并创建它的一个实例。不同用户请求由同一Servlet实例的多个独立线程处理。那些要求在不同请求之间持续有效的数据既可以用Servlet的实例变量来保存,也可以保存在独立的辅助对象中。

    用JDBC访问数据库首先要创建与数据库之间的连接,获得一个连接对象(Connection),由连接对象提供执行SQL语句的方法。本文介绍的数据库连接池包括一个管理类DBConnectionManager,负责提供与多个连接池对象(DBConnectionPool类)之间的接口。每一个连接池对象管理一组JDBC连接对象,每一个连接对象可以被任意数量的Servlet共享。

    类DBConnectionPool提供以下功能:

    1) 从连接池获取(或创建)可用连接。

    2) 把连接返回给连接池。

    3) 在系统关闭时释放所有资源,关闭所有连接。

    此外, DBConnectionPool类还能够处理无效连接(原来登记为可用的连接,由于某种原因不再可用,如超时,通讯问题),并能够限制连接池中的连接总数不超过某个预定值。

    管理类DBConnectionManager用于管理多个连接池对象,它提供以下功能:

    1) 装载和注册JDBC驱动程序。

    2) 根据在属性文件中定义的属性创建连接池对象。

    3) 实现连接池名字与其实例之间的映射。

    4) 跟踪客户程序对连接池的引用,保证在最后一个客户程序结束时安全地关闭所有连接池。

    本文余下部分将详细说明这两个类,最后给出一个示例演示Servlet使用连接池的一般过程。

    二、具体实现

    DBConnectionManager.java程序清单如下:

    001 import java.io.*;

    002 import java.sql.*;

    003 import java.util.*;

    004 import java.util.Date;

    005

    006 /**

    007 * 管理类DBConnectionManager支持对一个或多个由属性文件定义的数据库连接

    008 * 池的访问.客户程序可以调用getInstance()方法访问本类的唯一实例.

    009 */

    010 public class DBConnectionManager {

    011 static private DBConnectionManager instance; // 唯一实例

    012 static private int clients;

    013

    014 private Vector drivers = new Vector();

    015 private PrintWriter log;

    016 private Hashtable pools = new Hashtable();

    017

    018 /**

    019 * 返回唯一实例.如果是第一次调用此方法,则创建实例

    020 *

    021 * @return DBConnectionManager 唯一实例

    022 */

    023 static synchronized public DBConnectionManager getInstance() {

    024 if (instance == null) {

    025 instance = new DBConnectionManager();

    026 }

    027 clients++;

    028 return instance;

    029 }

    030

    031 /**

    032 * 建构函数私有以防止其它对象创建本类实例

    033 */

    034 private DBConnectionManager() {

    035 init();

    036 }

    037

    038 /**

    039 * 将连接对象返回给由名字指定的连接池

    040 *

    041 * @param name 在属性文件中定义的连接池名字

    042 * @param con 连接对象

    043 */

    044 public void freeConnection(String name, Connection con) {

    045 DBConnectionPool pool = (DBConnectionPool) pools.get(name);

    046 if (pool != null) {

    047 pool.freeConnection(con);

    048 }

    049 }

    050

    051 /**

    052 * 获得一个可用的(空闲的)连接.如果没有可用连接,且已有连接数小于最大连接数

    053 * 限制,则创建并返回新连接

    054 *

    055 * @param name 在属性文件中定义的连接池名字

    056 * @return Connection 可用连接或null

    057 */

    058 public Connection getConnection(String name) {

    059 DBConnectionPool pool = (DBConnectionPool) pools.get(name);

    060 if (pool != null) {

    061 return pool.getConnection();

    062 }

    063 return null;

    064 }

    065

    066 /**

    067 * 获得一个可用连接.若没有可用连接,且已有连接数小于最大连接数限制,

    068 * 则创建并返回新连接.否则,在指定的时间内等待其它线程释放连接.

    069 *

    070 * @param name 连接池名字

    071 * @param time 以毫秒计的等待时间

    072 * @return Connection 可用连接或null

    073 */

    074 public Connection getConnection(String name, long time) {

    075 DBConnectionPool pool = (DBConnectionPool) pools.get(name);

    076 if (pool != null) {

    077 return pool.getConnection(time);

    078 }

    079 return null;

    080 }

    081

    082 /**

    083 * 关闭所有连接,撤销驱动程序的注册

    084 */

    085 public synchronized void release() {

    086 // 等待直到最后一个客户程序调用

    087 if (--clients != 0) {

    088 return;

    089 }

    090

    091 Enumeration allPools = pools.elements();

    092 while (allPools.hasMoreElements()) {

    093 DBConnectionPool pool = (DBConnectionPool) allPools.nextElement();

    094 pool.release();

    095 }

    096 Enumeration allDrivers = drivers.elements();

    097 while (allDrivers.hasMoreElements()) {

    098 Driver driver = (Driver) allDrivers.nextElement();

    099 try {

    100 DriverManager.dereGISterDriver(driver);

    101 log("撤销JDBC驱动程序 " + driver.getClass().getName()+"的注册");

    102 }

    103 catch (SQLException e) {

    104 log(e, "无法撤销下列JDBC驱动程序的注册: " + driver.getClass().getName());

    105 }

    106 }

    107 }

    108

    109 /**

    110 * 根据指定属性创建连接池实例.

    111 *

    112 * @param props 连接池属性

    113 */

    114 private void createPools(Properties props) {

    115 Enumeration propNames = props.propertyNames();

    116 while (propNames.hasMoreElements()) {

    117 String name = (String) propNames.nextElement();

    118 if (name.endsWith(".url")) {

    119 String poolName = name.substring(0, name.lastIndexOf("."));

    120 String url = props.getProperty(poolName + ".url");

    121 if (url == null) {

    122 log("没有为连接池" + poolName + "指定URL");

    123 continue;

    124 }

    125 String user = props.getProperty(poolName + ".user");

    126 String password = props.getProperty(poolName + ".password");

    127 String maxconn = props.getProperty(poolName + ".maxconn", "0");

    128 int max;

    129 try {

    130 max = Integer.valueOf(maxconn).intValue();

    131 }

    132 catch (NumberFormatException e) {

    133 log("错误的最大连接数限制: " + maxconn + " .连接池: " + poolName);

    134 max = 0;

    135 }

    136 DBConnectionPool pool =

    137 new DBConnectionPool(poolName, url, user, password, max);

    138 pools.put(poolName, pool);

    139 log("成功创建连接池" + poolName);

    140 }

    141 }

    142 }

    143

    144 /**

    145 * 读取属性完成初始化

    146 */

    147 private void init() {

    148 InputStream is = getClass().getResourceAsStream("/db.properties");

    149 Properties dbProps = new Properties();

    150 try {

    151 dbProps.load(is);

    152 }

    153 catch (Exception e) {

    154 System.err.println("不能读取属性文件. " +

    155 "请确保db.properties在CLASSPATH指定的路径中");

    156 return;

    157 }

    158 String logFile = dbProps.getProperty("logfile", "DBConnectionManager.log");

    159 try {

    160 log = new PrintWriter(new FileWriter(logFile, true), true);

    161 }

    162 catch (IOException e) {

    163 System.err.println("无法打开日志文件: " + logFile);

    164 log = new PrintWriter(System.err);

    165 }

    166 loadDrivers(dbProps);

    167 createPools(dbProps);

    168 }

    169

    170 /**

    171 * 装载和注册所有JDBC驱动程序

    172 *

    173 * @param props 属性

    174 */

    175 private void loadDrivers(Properties props) {

    176 String driverClasses = props.getProperty("drivers");

    177 StringTokenizer st = new StringTokenizer(driverClasses);

    178 while (st.hasMoreElements()) {

    179 String driverClassName = st.nextToken().trim();

    180 try {

    181 Driver driver = (Driver)

    182 Class.forName(driverClassName).newInstance();

    183 DriverManager.reGISterDriver(driver);

    184 drivers.addElement(driver);

    185 log("成功注册JDBC驱动程序" + driverClassName);

    186 }

    187 catch (Exception e) {

    188 log("无法注册JDBC驱动程序: " +

    189 driverClassName + ", 错误: " + e);

    190 }

    191 }

    192 }

    193

    194 /**

    195 * 将文本信息写入日志文件

    196 */

    197 private void log(String msg) {

    198 log.println(new Date() + ": " + msg);

    199 }

    200

    201 /**

    202 * 将文本信息与异常写入日志文件

    203 */

    204 private void log(Throwable e, String msg) {

    205 log.println(new Date() + ": " + msg);

    206 e.printStackTrace(log);

    207 }

    208

    209 /**

    210 * 此内部类定义了一个连接池.它能够根据要求创建新连接,直到预定的最

    211 * 大连接数为止.在返回连接给客户程序之前,它能够验证连接的有效性.

    212 */

    213 class DBConnectionPool {

    214 private int checkedOut;

    215 private Vector freeConnections = new Vector();

    216 private int maxConn;

    217 private String name;

    218 private String password;

    219 private String URL;

    220 private String user;

    221

    222 /**

    223 * 创建新的连接池

    224 *

    225 * @param name 连接池名字

    226 * @param URL 数据库的JDBC URL

    227 * @param user 数据库帐号,或 null

    228 * @param password 密码,或 null

    229 * @param maxConn 此连接池允许建立的最大连接数

    230 */

    231 public DBConnectionPool(String name, String URL, String user, String password,

    232 int maxConn) {

    233 this.name = name;

    234 this.URL = URL;

    235 this.user = user;

    236 this.password = password;

    237 this.maxConn = maxConn;

    238 }

    239

    240 /**

    241 * 将不再使用的连接返回给连接池

    242 *

    243 * @param con 客户程序释放的连接

    244 */

    245 public synchronized void freeConnection(Connection con) {

    246 // 将指定连接加入到向量末尾

    247 freeConnections.addElement(con);

    248 checkedOut--;

    249 notifyAll();

    250 }

    251

    252 /**

    253 * 从连接池获得一个可用连接.如没有空闲的连接且当前连接数小于最大连接

    254 * 数限制,则创建新连接.如原来登记为可用的连接不再有效,则从向量删除之,

    255 * 然后递归调用自己以尝试新的可用连接.

    256 */

    257 public synchronized Connection getConnection() {

    258 Connection con = null;

    259 if (freeConnections.size() > 0) {

    260 // 获取向量中第一个可用连接

    261 con = (Connection) freeConnections.firstElement();

    262 freeConnections.removeElementAt(0);

    263 try {

    264 if (con.isClosed()) {

    265 log("从连接池" + name+"删除一个无效连接");

    266 // 递归调用自己,尝试再次获取可用连接

    267 con = getConnection();

    268 }

    269 }

    270 catch (SQLException e) {

    271 log("从连接池" + name+"删除一个无效连接");

    272 // 递归调用自己,尝试再次获取可用连接

    273 con = getConnection();

    274 }

    275 }

    276 else if (maxConn == 0 || checkedOut < maxConn) {

    277 con = newConnection();

    278 }

    279 if (con != null) {

    280 checkedOut++;

    281 }

    282 return con;

    283 }

    284

    285 /**

    286 * 从连接池获取可用连接.可以指定客户程序能够等待的最长时间

    287 * 参见前一个getConnection()方法.

    288 *

    289 * @param timeout 以毫秒计的等待时间限制

    290 */

    291 public synchronized Connection getConnection(long timeout) {

    292 long startTime = new Date().getTime();

    293 Connection con;

    294 while ((con = getConnection()) == null) {

    295 try {

    296 wait(timeout);

    297 }

    298 catch (InterruptedException e) {}

    299 if ((new Date().getTime() - startTime) >= timeout) {

    300 // wait()返回的原因是超时

    301 return null;

    302 }

    303 }

    304 return con;

    305 }

    306

    307 /**

    308 * 关闭所有连接

    309 */

    310 public synchronized void release() {

    311 Enumeration allConnections = freeConnections.elements();

    312 while (allConnections.hasMoreElements()) {

    313 Connection con = (Connection) allConnections.nextElement();

    314 try {

    315 con.close();

    316 log("关闭连接池" + name+"中的一个连接");

    317 }

    318 catch (SQLException e) {

    319 log(e, "无法关闭连接池" + name+"中的连接");

    320 }

    321 }

    322 freeConnections.removeAllElements();

    323 }

    324

    325 /**

    326 * 创建新的连接

    327 */

    328 private Connection newConnection() {

    329 Connection con = null;

    330 try {

    331 if (user == null) {

    332 con = DriverManager.getConnection(URL);

    333 }

    334 else {

    335 con = DriverManager.getConnection(URL, user, password);

    336 }

    337 log("连接池" + name+"创建一个新的连接");

    338 }

    339 catch (SQLException e) {

    340 log(e, "无法创建下列URL的连接: " + URL);

    341 return null;

    342 }

    343 return con;

    344 }

    345 }

    346 }

    三、类DBConnectionPool说明

    该类在209至345行实现,它表示指向某个数据库的连接池。数据库由JDBC URL标识。一个JDBC URL由三部分组成:协议标识(总是jdbc),驱动程序标识(如 odbc、idb、Oracle等),数据库标识(其格式依赖于驱动程序)。例如,jdbc:odbc:demo,即是一个指向demo数据库的JDBC URL,而且访问该数据库要使用JDBC-ODBC驱动程序。每个连接池都有一个供客户程序使用的名字以及可选的用户帐号、密码、最大连接数限制。如果Web应用程序所支持的某些数据库操作可以被所有用户执行,而其它一些操作应由特别许可的用户执行,则可以为两类操作分别定义连接池,两个连接池使用相同的JDBC URL,但使用不同的帐号和密码。

    类DBConnectionPool的建构函数需要上述所有数据作为其参数。如222至238行所示,这些数据被保存为它的实例变量:

    如252至283行、285至305行所示, 客户程序可以使用DBConnectionPool类提供的两个方法获取可用连接。两者的共同之处在于:如连接池中存在可用连接,则直接返回,否则创建新的连接并返回。如果没有可用连接且已有连接总数等于最大限制数,第一个方法将直接返回null,而第二个方法将等待直到有可用连接为止。

    所有的可用连接对象均登记在名为freeConnections的向量(Vector)中。如果向量中有多于一个的连接,getConnection()总是选取第一个。同时,由于新的可用连接总是从尾部加入向量,从而使得数据库连接由于长时间闲置而被关闭的风险减低到最小程度。

    第一个getConnection()在返回可用连接给客户程序之前,调用了isClosed()方法验证连接仍旧有效。如果该连接被关闭或触发异常,getConnection()递归地调用自己以尝试获取另外的可用连接。如果在向量freeConnections中不存在任何可用连接,getConnection()方法检查是否已经指定最大连接数限制。如已经指定,则检查当前连接数是否已经到达极限。此处maxConn为0表示没有限制。如果没有指定最大连接数限制或当前连接数小于该值,该方法尝试创建新的连接。如创建成功,则增加已使用连接的计数并返回,否则返回空值。

    如325至345行所示,创建新连接由newConnection()方法实现。创建过程与是否已经指定数据库帐号、密码有关。

    JDBC的DriverManager类提供多个getConnection()方法,这些方法要用到JDBC URL与其它一些参数,如用户帐号和密码等。DriverManager将使用指定的JDBC URL确定适合于目标数据库的驱动程序及建立连接。

    在285至305行实现的第二个getConnection()方法需要一个以毫秒为单位的时间参数,该参数表示客户程序能够等待的最长时间。建立连接的具体操作仍旧由第一个getConnection()方法实现。

    该方法执行时先将startTime初始化为当前时间。在while循环中尝试获得一个连接。如果失败,则以给定的时间值为参数调用wait()。wait()的返回可能是由于其它线程调用notify()或notifyAll(),也可能是由于预定时间已到。为找出wait()返回的真正原因,程序用当前时间减开始时间(startTime),如差值大于预定时间则返回空值,否则再次调用getConnection()。

    把空闲的连接登记到连接池由240至250行的freeConnection()方法实现,它的参数为返回给连接池的连接对象。该对象被加入到freeConnections向量的末尾,然后减少已使用连接计数。调用notifyAll()是为了通知其它正在等待可用连接的线程。

    许多Servlet引擎为实现安全关闭提供多种方法。数据库连接池需要知道该事件以保证所有连接能够正常关闭。DBConnectionManager类负协调整个关闭过程,但关闭连接池中所有连接的任务则由DBConnectionPool类负责。在307至323行实现的release()方法供DBConnectionManager调用。该方法遍历freeConnections向量并关闭所有连接,然后从向量中删除这些连接。

    四、类DBConnectionManager 说明

    该类只能创建一个实例,其它对象能够调用其静态方法(也称为类方法)获得该唯一实例的引用。如031至036行所示,DBConnectionManager类的建构函数是私有的,这是为了避免其它对象创建该类的实例。

    DBConnectionManager类的客户程序可以调用getInstance()方法获得对该类唯一实例的引用。如018至029行所示,类的唯一实例在getInstance()方法第一次被调用期间创建,此后其引用就一直保存在静态变量instance中。每次调用getInstance()都增加一个DBConnectionManager的客户程序计数。即,该计数代表引用DBConnectionManager唯一实例的客户程序总数,它将被用于控制连接池的关闭操作。

    该类实例的初始化工作由146至168行之间的私有方法init()完成。其中 getResourceAsStream()方法用于定位并打开外部文件。外部文件的定位方法依赖于类装载器的实现。标准的本地类装载器查找操作总是开始于类文件所在路径,也能够搜索CLASSPATH中声明的路径。db.properties是一个属性文件,它包含定义连接池的键-值对。可供定义的公用属性如下:

    drivers 以空格分隔的JDBC驱动程序类列表

    logfile 日志文件的绝对路径

    其它的属性和特定连接池相关,其属性名字前应加上连接池名字:

    .url 数据库的 JDBC URL

    .maxconn 允许建立的最大连接数,0表示没有限制

    .user 用于该连接池的数据库帐号

    .password 相应的密码

    其中url属性是必需的,而其它属性则是可选的。数据库帐号和密码必须合法。用于Windows平台的db.properties文件示例如下:

    drivers=sun.jdbc.odbc.JdbcOdbcDriver jdbc.idbDriver

    logfile=D:\\user\\src\\Java\\DBConnectionManager\\log.txt

    idb.url=jdbc:idb:c:\\local\\javawebserver1.1\\db\\db.prp

    idb.maxconn=2

    Access.url=jdbc:odbc:demo

    access.user=demo

    access.password=demopw

    注意在Windows路径中的反斜杠必须输入2个,这是由于属性文件中的反斜杠同时也是一个转义字符。

    init()方法在创建属性对象并读取db.properties文件之后,就开始检查logfile属性。如果属性文件中没有指定日志文件,则默认为当前目录下的DBConnectionManager.log文件。如日志文件无法使用,则向System.err输出日志记录。

    装载和注册所有在drivers属性中指定的JDBC驱动程序由170至192行之间的loadDrivers()方法实现。该方法先用StringTokenizer将drivers属性值分割为对应于驱动程序名称的字符串,然后依次装载这些类并创建其实例,最后在 DriverManager中注册该实例并把它加入到一个私有的向量drivers。向量drivers将用于关闭服务时从DriverManager取消所有JDBC 驱动程序的注册。

    init()方法的最后一个任务是调用私有方法createPools()创建连接池对象。如109至142行所示,createPools()方法先创建所有属性名字的枚举对象(即Enumeration对象,该对象可以想象为一个元素系列,逐次调用其nextElement()方法将顺序返回各元素),然后在其中搜索名字以“.url”结尾的属性。对于每一个符合条件的属性,先提取其连接池名字部分,进而读取所有属于该连接池的属性,最后创建连接池对象并把它保存在实例变量pools中。散列表(Hashtable类 )pools实现连接池名字到连接池对象之间的映射,此处以连接池名字为键,连接池对象为值。

    为便于客户程序从指定连接池获得可用连接或将连接返回给连接池,类DBConnectionManager提供了方法getConnection()和freeConnection()。所有这些方法都要求在参数中指定连接池名字,具体的连接获取或返回操作则调用对应的连接池对象完成。它们的实现分别在051至064行、066至080行、038至049行。

    如082至107行所示,为实现连接池的安全关闭,DBConnectionManager提供了方法release()。在上面我们已经提到,所有DBConnectionManager的客户程序都应该调用静态方法getInstance()以获得该管理器的引用,此调用将增加客户程序计数。客户程序在关闭时调用release()可以递减该计数。当最后一个客户程序调用release(),递减后的引用计数为0,就可以调用各个连接池的release()方法关闭所有连接了。管理类release()方法最后的任务是撤销所有JDBC驱动程序的注册。

    五、Servlet使用连接池示例

    Servlet API所定义的Servlet生命周期类如:

    1) 创建并初始化Servlet(init()方法)。

    2) 响应客户程序的服务请求(service()方法)。

    3) Servlet终止运行,释放所有资源(destroy()方法)。

    本例演示连接池应用,上述关键步骤中的相关操作为:

    1) 在init(),用实例变量connMgr 保存调用DBConnectionManager.getInstance()所返回的引用。

    2) 在service(),调用getConnection(),执行数据库操作,用freeConnection()将连接返回给连接池。

    3) 在destroy(),调用release()关闭所有连接,释放所有资源。

    示例程序清单如下:

    import java.io.*;

    import java.sql.*;

    import javax.servlet.*;

    import javax.servlet.http.*;

    public class TestServlet extends HttpServlet {

    private DBConnectionManager connMgr;

    public void init(ServletConfig conf) throws ServletException {

    super.init(conf);

    connMgr = DBConnectionManager.getInstance();

    }

    public void service(HttpServletRequest req, HttpServletResponse res)

    throws IOException {

    res.setContentType("text/html");

    PrintWriter out = res.getWriter();

    Connection con = connMgr.getConnection("idb");

    if (con == null) {

    out.println("不能获取数据库连接.");

    return;

    }

    ResultSet rs = null;

    ResultSetMetaData md = null;

    Statement stmt = null;

    try {

    stmt = con.createStatement();

    rs = stmt.executeQuery("SELECT * FROM EMPLOYEE");

    md = rs.getMetaData();

    out.println("

    职工数据");

    while (rs.next()) {

    out.println("

    ");

    for (int i = 1; i < md.getColumnCount(); i++) {

    out.print(rs.getString(i) + ", ");

    }

    }

    stmt.close();

    rs.close();

    }

    catch (SQLException e) {

    e.printStackTrace(out);

    }

    connMgr.freeConnection("idb", con);

    }

    public void destroy() {

    connMgr.release();

    super.destroy();

    }

    }

    阅读(1384) | 评论(0) | 转发(0) |

    给主人留下些什么吧!~~

    评论热议

    请登录后评论。

    展开全文
  • 请求支援:java 连接数据库如何实现多条件并列查询?关注:168答案:4mip版解决时间 2021-02-07 17:28提问者等妳¬硪唯一鍀执念2021-02-07 14:13条件可以有很多,也可以只有条,但是sql语句都可以执行最佳答案二级...
  • Java实现HTTP请求通信

    2021-03-06 10:04:01
    在校项目涉及到的一个技术,实现逻辑为:厂商提供第三方API,我这边通过HTTP请求调用其API获取Json数据进行处理,然后导入数据库。所以本文总结一下Http请求技术。 一、实现方式 目前JAVA实现HTTP请求的方法用的最多...
  • 实现跨域请求需要用到java的api,java.net.URL和java.net.URLConnection,先来简单的了解一下java的这两常用与网络编程的api。java.net.URL:认识IP、认识URL是进行网络编程的第步。java.net.URL提供了丰富的...
  • 前言基础的很浅层次原理解析场景分析实现原理一个servlet处理多个请求自动化前端数据封装成对象通过上面的分析,自己实现一个看一下大佬实现的简化数据库操作,智能化数据库的操作大佬的实现导包-maven:数据库的...
  • 首先来看以下我们的需求:用java编写一个监听程序,监听指定的端口,通过浏览器如http://localhost:7777来访问时,可以把请求到的内容记录下来,记录可以存文件,sqlit,mysql数据库,然后把接受到的信息在浏览器中...
  • 首先来看以下我们的需求:用java编写一个监听程序,监听指定的端口,通过浏览器如http://localhost:7777来访问时,可以把请求到的内容记录下来,记录可以存文件,sqlit,mysql数据库,然后把接受到的信息在浏览器中...
  • 分情况来说:普通单应用并发、多应用或多台服务器并发情况一:普通单应用并发使用关键字synchronized就可实现。情况二:多应用或多台服务器并发因多个应用之间并非同...2)写入请求分离到一个独立应用项目中,这个实...
  • 首先来看以下我们的需求:用java编写一个监听程序,监听指定的端口,通过浏览器如http://localhost:7777来访问时,可以把请求到的内容记录下来,记录可以存文件,sqlit,mysql数据库,然后把接受到的信息在浏览器中...
  • 因多个应用之间并非同一个jvm(应用)内,因此使用synchronized并不能满足需求。 具体处理方案包含以下几种: 1)数据库行级索,优点:简单粗暴;缺点:容易死锁&amp;性能差,非数据库专业人士不建议使用。 2...
  • 首先感谢博主的分享,我在这里只是对博主的分享进行一个自我的学习总结和记录为什么使用数据库连接池? 每一次web请求都要建立一次数据库连接。建立连接是一个费时的活动,每次都得花费0.05s~1s的时间,而且系统...
  • 一个Object:记为“o1”,表示请求代号,以String作为原本形态。 第二个Object:记为“o2”,承载一些后续会用到的一些数值或文字,如:用户的用户名、密码和账号等。以String表示,用“/”分隔。如:String test
  • 实现一个ajax请求的简单例子

    万次阅读 2019-04-10 16:31:40
    效果:在前端页面的text框输入一个id,点击按钮,发送ajax请求数据库查询对应用户数据返回,将用户信息显示在页面,实现局部刷新。 不说废话直接来! 1.前端的jsp页面 (代码的注释写的非常详细了,直接看代码就...
  • 最近在做Spring的项目,想做一个缓存,访问数据库,定期来做数据更新要实现两个功能可以通过http请求来立刻刷新缓存缓存可以通过自己配置的时间间隔来定期刷新通过Controller来做因为需要通过http来刷新缓存,所以第...
  • (2)如何写一个dao? step1,实体类--->entity包,Employee类 step2,dao接口--->dao包,EmployeeDAO接口 step3,实现dao接口--->dao.jdbc包,实现类 step4,工厂--->util包,Factor...
  • 发生条件:接受到一个请求,该请求没有执行完成又接受到相同请求,导致数据错误(如果是前一个请求执行完成,马上又接受相同请求不会有问题)问题分析:是由于数据库的脏读导致问题解决思路1.加一把大大的锁 (是最简单...
  • 最近,碰到了一个业务,是将数据库中所有的地址信息请求百度接口获取经纬度保存起来。
  • 在完成一个数据库查询操作部署到Tomcat时报错java.lang.NoClassDefFoundError: Could not initialize class. 在没有使用服务器时,查询功能可以正常运行. 在启动服务器查询时报错 Servlet代码如下: ``` ...
  • 问题的设置是基于一个Web服务(Spring /Java,Tomcat7和MySql),每个用户都获取自己的数据库,因此每个请求都需要自己的连接.由于所有数据库都是在运行时动态创建的,所以在启动之前静态配置不是一个选择.要优化数据库...
  • 有时需要临时增加一个定时任务(需要根据数据库查询结果然后发送HTTP请求),如果在项目中额外增加(Java+Spring+Quartz),则需要编写很多代码,而且还需要重新编译发布,比较麻烦,特别是在紧急情况下的时候。...
  • 流程是这样的,当一个web请求进来时,web容器会先检查请求的URL是否设置了Filter,如果设置了,则执行该Filter的doFilter方法。所有Filter都实现了javax.servlet.Filter接口,doFilter是定义在该接口中的最重要的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,255
精华内容 502
关键字:

java实现一个请求数据库

java 订阅