JDBC之 连接池

JDBC之 连接池

有这样的一种现象:

用java代码操作数据库,需要数据库连接对象,一个用户至少要用到一个连接。现在假设有成千上百万个用户,就要创建十分巨大数量的连接对象,这会使数据库承受极大的压力,为了解决这种现象,一种技术出现了,这就是数据库连接池。

什么是数据库连接池(原理)

所谓数据库连接池,可以看作 :在用户和数据库之间创建一个”池”,这个池中有若干个连接对象,当用户想要连接数据库,就要先从连接池中获取连接对象,然后操作数据库。一旦连接池中的连接对象被拿光了,下一个想要操作数据库的用户必须等待,等待其他用户释放连接对象,把它放回连接池中,这时候等待的用户才能获取连接对象,从而操作数据库。

数据库连接池的属性

连接对象初始的数量:initSize,一开始就创建若干个,当不够时再添加

连接对象最大数量:maxSize,添加到最大值则不会再添加

下面我们用代码下一个自己的连接池吧~

实现自己的连接池

看下面代码和注释吧~

public class MyPool {
    //设置注册属性
    private String url = "jdbc:mysql://localhost:3306/vmaxtam";
    private String user = "root";
    private String password = "root";
    private static String driverClass="com.mysql.jdbc.Driver";

    //设置连接池属性
    private int initSize = 5;
    private int maxSize = 8;

    //用LinkedList对象来保存connection对象
    private LinkedList<Connection> connList = new LinkedList<Connection>();
    //声明一个临时变量来计算连接对象的数量
    private int currentsize = 0;

    //声明MyPool对象时自动注册驱动
    static{
        try {
            Class.forName(driverClass);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    //获取连接的方法
    private Connection getConnection()
    {
        Connection conn=null;
        try {
            conn = DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return conn;
    }

    //构造方法,初始化连接池,并往里面添加连接对象
    public MyPool() {
        for(int i = 0; i < initSize; i++)
        {
            Connection connection = this.getConnection();
            connList.add(connection);
            currentsize++;
        }
    }

    //获取连接池中的一个连接对象
    public Connection getConnFromPool()
    {
        //当连接池还没空
        if(connList.size()>0){
            Connection connection = connList.getFirst();
            connList.removeFirst();
            return connection;

        }else if(connList.size()==0&&currentsize<8){
            //连接池被拿空,且连接数没有达到上限,创建新的连接
            currentsize++;
            connList.addLast(this.getConnection());

            Connection connection = connList.getFirst();
            connList.removeFirst();
            return connection;
        }

        throw new RuntimeException("连接数达到上限,请等待");
    }

    //把用完的连接放回连接池
    public void releaseConnection(Connection connection)
    {
        connList.addLast(connection);
    }

}

有了连接池后,我们写一个测试来调用一下它吧~

    @Test
    public void test1()
    {
        //获得连接池
        MyPool mypool = new MyPool();

        /*从连接池中尝试获取9个连接
        for(int i = 0 ; i<9; i++){
            Connection conn = mypool.getConnFromPool();
            System.out.println(conn.toString());
        }*/

        //获取第五个连接后,释放一下,然后再获取
        for(int i = 0 ; i<9; i++){
            Connection conn = mypool.getConnFromPool();
            if(i==5){
                mypool.releaseConnection(conn);
            }
            System.out.println(conn.toString());
        }
    }

上面这样就实现了自己的一个连接池,但是这个连接池依然存在着很多问题,一个较为明显的问题就是:

如果一个用户获取了一个连接对象,然后调用了close()方法把它关闭了,没有放回池中,这样池中的这个对象就回不来了,造成最大连接上限为8个的连接池实际上只有7个连接在工作。

为了解决这个问题,我们需要对close()方法进行改造,是用户调用close()方法时,实际上是把连接放回连接池中,而不是关闭它。

下面就为解决这个问题来分析下~

解决用户调用close()方法关闭连接

方法一:使用静态代理,写一个myConnection()类来继承connection的实现类,然后重写它的close()方法.

方法二:使用动态代理,使用jdbc动态代理类:java.lang.reflect.Proxy类

动态代理类中有这样一个方法可以创建它的实例

public static Object newProxyInstance(ClassLoader loader,

Class<?>[] interfaces,

InvocationHandler h)

参数解析:

ClassLoader:类加载器,只要在同一个JDK中的类即可

Class<?>[]:要代理的接口的集合

InvocationHandler:代理接口的方法处理器

根据需要,我们要给MyPool中的Connection加一个动态代理,所以我们用的前两个参数是:MyPool.Class.GetClassLoader 与 new Class<>{Connection}

最后还剩方法处理器,我们要修改Connection中的close方法,所以我们写出一个这样做的处理器即可,具体实现看下面代码与注释~

//获取连接的方法
    private Connection getConnection()
    {

        try {
            //获取一个连接
            final Connection conn=DriverManager.getConnection(url, user, password);

            //把连接交给动态代理类转换为代理的连接对象
            Connection myconn = (Connection)Proxy.newProxyInstance(
                    MyPool.class.getClassLoader(),
                    new Class[] {Connection.class},
                             //编写一个方法处理器
                    new InvocationHandler() {

                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                    Object value = null;

                    //当遇到close方法,就会把对象放回连接池中,而不是关闭连接
                    if(method.getName().equals("close"))
                    {
                        MyPool.connList.addLast(conn);
                    }else
                    {
                        //其它方法不变
                        value = method.invoke(conn, args);
                    }
                    return value;
                }}
            );
            return myconn;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

以上就是利用动态代理的方式解决close的问题了~~但是,我们自己写的连接池还有很多其他问题:

1)当前多个并发用户同时获取连接时,可能拿到同一个连接对象

2)当前用户连接数超过了最大连接数时,不能直接抛出异常,应该有机制,控制用户等待时间........

所以,这时候已经有人站出来,为我们写好了一些功能相对完善的连接池,这些第三方的连接池得到了广泛的用途,下面我们来介绍一下常见的连接池工具吧~

DBCP连接池

简介:DBCP连接池是开源组织Apache软件基金组织开发的连接池实现。

事实上,tomcat服务器默认就会使用这个连接池道具。

如何使用DBCP连接池呢,下面我来一一演示。

DBCP的使用步骤

步骤一:导包,使用第三方的道具,必须导入相应的jar包。

需要导入两个jar包:commons-dbcp-1.4.jar包

commons-pool-1.5.6.jar包

步骤二:使用代码~看看下面代码的演示吧

public class DBCPTest {
        private String url = "jdbc:mysql://localhost:3306/vmaxtam";
        private String user = "root";
        private String password = "root";
        private String classDriver = "com.mysql.jdbc.Driver";

        @Test
        public void Test1()
        {
            //创建DBCP连接池对象
            BasicDataSource ds = new BasicDataSource();

            //设置连接参数来进行连接
            ds.setUrl(url);
            ds.setUsername(user);
            ds.setPassword(password);
            ds.setDriverClassName(classDriver);

            //然后可以设置连接池的一些属性啦~
            ds.setInitialSize(5);
            ds.setMaxActive(8);
            ds.setMaxWait(3000);//设置最大的等待时长,毫秒为单位

            //从连接池中获取对象
            for(int i = 0 ; i<8;i++)
            {
                Connection conn = null;
                try {
                    conn = ds.getConnection();
                    System.out.println(conn.hashCode());

                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

        }
}

为了测试效果,我们可以在循环中设置拿9个连接额,这样在拿第九个连接时就会出现等待,等待到结束都没有连接被释放回连接池,就会出现报错。

也可以把For循环改成下面那样,测试close方法:

//从连接池中获取对象
            for(int i = 0 ; i<9;i++)
            {
                Connection conn = null;
                try {
                    conn = ds.getConnection();
                    System.out.println(conn.hashCode());
                    if(i==5)
                    {
                        conn.close();
                    }

                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

上面的代码还是有点地方可以得到优化,例如可以通过配置文件来配置连接的参数,还有数据库连接池的属性参数。

配置文件:

url=jdbc:mysql://localhost:3306/vmaxtam
username=root
password=root
classDriver=com.mysql.jdbc.Driver
initialSize=5
maxActive=8
maxWait=3000

用对象读取配置文件:

@Test
        public void Test2()
        {

            try {
                //创建配置对象
                Properties properties = new Properties();
                properties.load(DBCPTest.class.getResourceAsStream("/dbcp.properties"));

                //创建连接池对象,并且用连接池工厂来加载配置对象的信息
                BasicDataSource ds = (BasicDataSource)BasicDataSourceFactory.createDataSource(properties);

            //从连接池中获取对象
            for(int i = 0 ; i<8;i++)
            {
                Connection conn = null;
                conn = ds.getConnection();
                System.out.println(conn.hashCode());
            }
        }catch (Exception e2) {
                e2.printStackTrace();
            }
        }

以上就是DBCP连接池的基本用法了~下面我们来学习另一个连接池~

C3P0连接池

简介: C3P0是一个开源组织的产品,开源框架的内部的连接池一般都使用C3P0来实现,例如:Hibernate

C3P0的使用步骤

步骤一:导包,使用第三方的工具必须导入jar包

要导入的包:c3p0-0.9.1.2.jar 包

步骤二:看下面的代码显示怎么使用这个连接池吧~

@Test
    public void Test1()
    {

        try {
            //获取连接池对象
            ComboPooledDataSource cp = new ComboPooledDataSource();

            //设置连接参数
            cp.setJdbcUrl(url);
            cp.setUser(user);
            cp.setPassword(password);
            cp.setDriverClass(classDriver);

            //设置连接池的参数
            cp.setInitialPoolSize(5);//初始数量
            cp.setMaxPoolSize(8);//最大数量
            cp.setCheckoutTimeout(3000);//最大等待时间

            for(int i = 0 ; i<8 ; i++)
            {
                Connection conn = cp.getConnection();
                System.out.println(conn.hashCode());
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

可以看出,C3P0的用法和DBCP的用法非常的相似~这里不做累赘。

特别的是C3PO读取参数文件的方式,C3P0除了能像DBCP那样读取配置文件,它还提供了一种特殊的设置参数的方式,就是把参数数据写在一个名叫c3p0-config.xml的XML文件中,在创建C3P0对象时会自动在classpath去寻找该文件来读取~

也就是说:c3p0会到classpath下读取名字为c3p0-config.xml文件

这份XML文件有特殊的要求,下面我们来写一下这份XML文件:

<c3p0-config>
    <!-- 默认配置 -->
    <default-config>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/vmaxtam</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>

        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">8</property>
        <property name="checkoutTimeout">3000</property>

    </default-config>

    <!-- mysql的连接配置 -->
    <named-config name="mysql">
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/vmaxtam</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>

        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">8</property>
        <property name="checkoutTimeout">3000</property>
    </named-config>

    <!-- 使用 oracal就会用这份配置-->
    <!-- 也可以写其他数据库的配置 -->

</c3p0-config>

写完xml文件,现在我们就读取它吧~

@Test
    public void Test2()
    {
        try {
            //获取连接池对象,写上参数就会去找xml文件找这个数据库的配置来读取,当无参时,就会使用默认设置。
            ComboPooledDataSource cp = new ComboPooledDataSource("mysql");

            for(int i = 0 ; i<9 ; i++)
            {
                Connection conn = cp.getConnection();
                System.out.println(conn.hashCode());
                if(i==5)
                {
                    conn.close();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

使用这种读取方法,显得代码十分简便。

时间: 2024-10-06 18:37:41

JDBC之 连接池的相关文章

day18(JDBC事务&连接池介绍&DBUtils工具介绍&BaseServlet作用)

day18总结 今日思维导图: 今日内容 事务 连接池 ThreadLocal BaseServlet自定义Servlet父类(只要求会用,不要求会写) DBUtils à commons-dbutils ? 事务 ? 事务的四大特性:ACID: mysql中操作事务 jdbc中操作事务 ? ? ? 事务概述 为了方便演示事务,我们需要创建一个account表: CREATE TABLE account( ????id INT PRIMARY KEY AUTO_INCREMENT, ????NA

集合、多线程、网络编程、JDBC、连接池、反射、综合应用

1.使用TCP连接服务器和客户端. 2.结合反射的知识.创建JDBC和连接池. 3.客户端输入信息并保存到一个对象中,发送到服务器. 4.服务器接收对象,通过JDBC存储进数据库中. 5.把服务器放到线程中,保持启动状态. 6.客户端读取数据库的数据信息,放到集合中. 7.遍历集合,输出数据库的数据. 服务器和客户端的项目结构: 服务器 客户端 实体类: package com.luo.project.pojo; import java.io.Serializable; public class

JDBC自定义连接池

最近学习了JDBC的相关知识,写一下自定义连接池 一些说明: 本代码参考了部分别人的代码!!! JDBCCon类具体创建了连接: MyConnection类集成了Connection类用来管理连接与池,其中的close方法必须pool.add(): MyDataSource则是具体实现连接池. 具体步骤: 1.导mysql的jar包 2.配置db.propertites 1 driver = com.mysql.cj.jdbc.Driver 2 url = jdbc:mysql://localh

jdbc之连接池

第三方数据库连接池DBCP的应用 连接池的原理 1.缓存Connection对象 2.缓存Statement及PreparedStatement package jdbcdemo2; import java.io.InputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import java.u

JDBC从连接池获取连接

DBCP介绍 DBCP是Java开源连接池中最常用的一种,它一般应用在数据库使用非常频繁的系统中,可以处理数据库连接的大量请求,是做大型站点的首选数据库配置. 简单配置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/test username=root password=123456 maxActive=50 maxIdle=20 maxWait=60000 添加maven <dependency>

hibernate+spring+struts中使用原生态的sql导致oping JDBC connection连接池资源抽光问题

打代码已经有好几个月了. HibernateTemplate hibernateTemplate = getHibernateTemplate();SessionFactory sessionFactory = hibernateTemplate.getSessionFactory(); Session session = sessionFactory.openSession(); Query query = session.createSQLQuery(hql).addEntity(Argum

java JDBC (八) 连接池

package cn.sasa.demo1; import javax.sql.DataSource; import org.apache.commons.dbcp2.BasicDataSource; public class DBUtils { private static BasicDataSource datasource = new BasicDataSource(); static { /** * 连接池: * 存放连接的容器 * 不需要每次都创建连接.释放连接 * * DataSou

boneCP连接池配置

jdbc和连接池对于你这个场景来说,都足够,既然用spring管理了,建议还是使用连接池,另外,spring自身没有实现连接池,一般都是对第三方连接池的包装,常见的有C3P0,dbcp以及最近比较流行的boneCP等,这几个配置都差不多太多,以boneCP为例: <bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">

dbcp,c3p0连接池

<!-- 配置dbcp数据源 --> <bean id="dataSource2" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <pr