ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

数据库连接池

2022-05-19 12:00:07  阅读:39  来源: 互联网

标签:java 数据库 st sql import 连接 连接池


数据库连接池

该文部分代码和内容节选自其他作者文章,仅用作个人学习,特此声明

链接(1条消息) 数据库连接池学习笔记(一):原理介绍+常用连接池介绍_CrankZ的博客-CSDN博客_数据库连接池

1、学前了解

数据库连接 --> 执行完毕 --> 释放 --> 连接 --> 释放......

以上步骤十分浪费系统资源

池化技术:把一些能够复用的东西(比如说数据库连接、线程)放到池中,避免重复创建、销毁的开销,从而极大提高性能 [池化技术 - 云+社区 - 腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1613322#:~:text=池化技术:把一些能够复用的东西(比如说数据库连接、线程)放到池中,避免重复创建、销毁的开销,从而极大提高性能。 在开发过程中我们会用到很多的连接池,像是数据库连接池、HTTP,连接池、 Redis 连接池等等。)

也就是说不用 JDBCUtils.getConnection() 了,直接拿到数据库连接

举个比较简单的例子,模拟一个营业场景

之前我们做的练习相当于下面这种营业方式

开门 --> 只服务一个人 --> 关门 --> 开门 --> 服务下一个人 --> 关闭......

很明显这个流程多了很多不必要的开门关门操作

我们稍微改进一下,也就是现实中的营业场景其实是这样的

开门 --> 营业员:等待客户 --> 服务客户 --> 等待下一个客户 --> ...... --> 不想营业的时候关门

池化技术的使用其实就类似于上边这种现实的营业场景

针对上面的例子,我们来仔细分析一下数据库连接池

  • 开门:相当于创建连接

  • 营业员的数量相当于连接数 连接数涉及到以下几个基本概念

    • 最小连接数:常用连接数为多少,就设置最小连接数为多少
    • 最大连接数:业务最高承载上限。当实际连接数超过最大连接数以后,就要排队等待
    • 等待超时:等待时间超过预定时间就不等了,放弃排队(你去饭店吃饭没桌了可不就得等待,如果等的人太多了你还会继续等吗?)
  • 服务客户:相当于数据库连接成功,进行数据库操作

  • 关门:销毁连接(释放连接),最高管理员可以随时释放连接停止服务

现在应该对连接池有初步了解了,接下来我们正式进入数据库连接池的学习


2、传统连接与数据库连接池运行机制区别

2.1 不使用连接池

下面以访问MySQL为例,执行一个SQL命令,如果不使用连接池,需要经过哪些流程。

不使用数据库连接池的步骤:

1.TCP建立连接的三次握手
2.MySQL认证的三次握手
3.真正的SQL执行
4.MySQL的关闭
5.TCP的四次握手关闭

可以看到,为了执行一条SQL,却多了非常多我们不关心的网络交互

优点:
实现简单

缺点:
网络IO较多
数据库的负载较高
响应时间较长及QPS较低
应用频繁的创建连接和关闭连接,导致临时对象较多,GC频繁
在关闭连接后,会出现大量TIME_WAIT 的TCP状态(在2个MSL之后关闭)


2.2 使用连接池

使用连接池的步骤

第一次访问的时候,需要建立连接。 但是之后的访问,均会复用之前创建的连接,直接执行SQL语句。

优点

减少了网络开销

系统的性能会有一个实质的提升

没了麻烦的TIME_WAIT状态


3、连接池工作原理

  • 连接池的建立

    一般在系统初始化时,连接池会根据系统配置建立,并在池中创建几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。

  • 连接池的管理

    连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。

    当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。

    当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。

    该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。

  • 连接池的关闭

    当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。


4、连接池主要参数

在使用连接池之前,要配置一下参数

1.最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费.

2.最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作

3.最大空闲时间

4.获取连接超时时间

5.超时重试连接次数


5、连接池注意点

  • 并发问题

    为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为各个语言自身提供了对并发管理的支持像java,c#等等,使用synchronized(java)lock(C#)关键字即可确保线程是同步的。

  • 事务处理

    我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-OR-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
    我们知道当2个线程共用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是我们仍然不能确定哪个数据库操作是对应哪个事务的,这是因为有2个线程都在进行事务操作而引起的。为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。

  • 连接池分配与释放

    连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
    对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。每当用户请求一个连接时,系统检查这个List中有没有可以分配的连接。如果有就把那个最合适的连接分配给他;如果没有就抛出一个异常给用户,List中连接是否可以被分配由一个线程来专门管理

  • 连接池配置与维护

    连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。比方说,最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过软件需求上得到。
    如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。


6、DBCP-C3P0连接池

6.1 DBCP

  • 需要用到的jar包为 commons-dbcp-1.4 和 commons-pool-1.6 ,下载完成后直接导入idea

  • 配置文件 dbcp.properties

    #连接设置
    driverClassName=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/jdbctest?useUnicode=true&characterEncoding=utf8&useSSL=false
    username=root
    password=xy680501*
    
    #初始化连接
    initialSize=10
    
    #最大连接数量
    maxActive=50
    
    #最大空闲连接
    maxIdle=20
    
    #最小空闲连接
    minIdle=5
    
    #超时等待时间以毫秒为单位 6000毫秒/1000等于60秒
    maxWait=60000
    #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:【属性名=property;】
    #注意:user 与 password 两个属性会被明确地传递,因此这里不需要包含他们。
    connectionProperties=useUnicode=true;characterEncoding=UTF8
    
    #指定由连接池所创建的连接的自动提交(auto-commit)状态。
    defaultAutoCommit=true
    
    #driver default 指定由连接池所创建的连接的只读(read-only)状态。
    #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
    defaultReadOnly=
    
    #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
    #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
    defaultTransactionIsolation=READ_COMMITTED
    
  • 工具类

    import org.apache.commons.dbcp.BasicDataSourceFactory;
    
    import javax.sql.DataSource;
    import java.io.IOException;
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Properties;
    
    /**
     * @ClassName: JDBCDBCPUtils
     * @Description: TODO 类描述
     * @Version: 1.0
     */
    public class JDBCDBCPUtils {
        private static DataSource dataSource = null;
    
        static {
            try {
                InputStream in = JDBCDBCPUtils.class.getClassLoader().getResourceAsStream("dbcp.properties");
                Properties properties = new Properties();
                properties.load(in);
                //创建数据源 工厂模式
                dataSource = BasicDataSourceFactory.createDataSource(properties);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 获取连接
         */
        public static Connection getConnection() throws SQLException {
            //从数据源中获取连接
            return dataSource.getConnection();
        }
    
        /**
         * 释放资源
         */
        public static void release(Connection con, Statement st, ResultSet rs) {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (st != null) {
                try {
                    st.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (con != null) {
                try {
                    con.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    
  • 测试类

    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * @ClassName: TestDBCP
     * @Description: TODO 类描述
     * @Version: 1.0
     */
    public class TestDBCP {
        public static void main(String[] args) {
            Connection con = null;
            PreparedStatement st = null;
            ResultSet rs = null;
            try {
                con = JDBCDBCPUtils.getConnection();
                //使用?占位符代替参数
                String sql = "INSERT INTO users(`id`,`name`,`password`,`email`,`birthday`) VALUES (?,?,?,?,?)";
                //预编译SQL,先写SQL,然后不执行
                st = con.prepareStatement(sql);
                //手动给参数赋值
                st.setInt(1, 4);
                st.setString(2, "赵六");
                st.setString(3, "123456");
                st.setString(4, "qianqi@sina.com");
                st.setDate(5, new java.sql.Date(new java.util.Date().getTime()));
                int num = st.executeUpdate();
                if (num > 0) {
                    System.out.println("插入成功!");
                }
    
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                JDBCDBCPUtils.release(con, st, rs);
            }
    
        }
    }
    

    如下图所示,插入数据成功


6.2 C3P0

  • 需要用到的jar包为 c3p0-0.9.5.5.jar 和 mchange-commons-java-0.2.19.jar,下载完成后直接导入idea

  • 配置文件c3p0-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <c3p0-config>
        <!--
        c3p0的缺省(默认)配置
        如果在代码中ComboPooledDataSource ds=new ComboPooledDataSource();这样写就表示使用的是c3p0的缺省(默认)
        -->
        <default-config>
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbctest?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=UTC</property>
            <property name="user">root</property>
            <property name="password">xy680501*</property>
            <property name="acquiredIncrement">5</property>
            <property name="initialPoolSize">10</property>
            <property name="minPoolSize">5</property>
            <property name="maxPoolSize">20</property>
        </default-config>
    
    
        <!--
        c3p0的命名配置
        如果在代码中ComboPooledDataSource ds=new ComboPooledDataSource("MySQL");这样写就表示使用的是name是MySQL
        -->
        <name-config name="MySQL">
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbctest?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=UTC</property>
            <property name="user">root</property>
            <property name="password">xy680501*</property>
            <property name="acquiredIncrement">5</property>
            <property name="initialPoolSize">10</property>
            <property name="minPoolSize">5</property>
            <property name="maxPoolSize">20</property>
        </name-config>
    </c3p0-config>
    

  • 工具类

    import com.mchange.v2.c3p0.ComboPooledDataSource;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    /**
     * @ClassName: JDBCC3P0Utils
     * @Description: TODO 类描述
     * @Version: 1.0
     */
    public class JDBCC3P0Utils {
        private static DataSource dataSource = null;
        //private static ComboPooledDataSource dataSource = null;
    
    
        static {
            try {
                //代码的方式配置
    //            dataSource = new ComboPooledDataSource();
    //            dataSource.setDriverClass();
    //            dataSource.setJdbcUrl();
    //            dataSource.setUser();
    //            dataSource.setPassword();
    //            dataSource.setMaxPoolSize();
    //            dataSource.setMinPoolSize();
                //配置文件写法
                dataSource = new ComboPooledDataSource("MySQL");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 获取连接
         */
        public static Connection getConnection() throws SQLException {
            //从数据源中获取连接
            return dataSource.getConnection();
        }
    
        /**
         * 释放资源
         */
        public static void release(Connection con, Statement st, ResultSet rs) {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (st != null) {
                try {
                    st.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (con != null) {
                try {
                    con.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

  • 测试类

    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * @ClassName: TestC3P0
     * @Description: TODO 类描述
     * @Version: 1.0
     */
    public class TestC3P0 {
        public static void main(String[] args) {
            Connection con = null;
            PreparedStatement st = null;
            ResultSet rs = null;
            try {
                con = JDBCC3P0Utils.getConnection();
                //使用?占位符代替参数
                String sql = "INSERT INTO users(`id`,`name`,`password`,`email`,`birthday`) VALUES (?,?,?,?,?)";
                //预编译SQL,先写SQL,然后不执行
                st = con.prepareStatement(sql);
                //手动给参数赋值
                st.setInt(1, 5);
                st.setString(2, "钱七");
                st.setString(3, "123456");
                st.setString(4, "liuba@sina.com");
                st.setDate(5, new java.sql.Date(new java.util.Date().getTime()));
                int num = st.executeUpdate();
                if (num > 0) {
                    System.out.println("插入成功!");
                }
    
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                JDBCC3P0Utils.release(con, st, rs);
            }
    
        }
    }
    

    如下图所示插入数据成功


6.3 总结

无论用什么数据源,本质还是一样的,DataSource接口不会变,方法就不会变

其实可以不用像上面那样这么麻烦

直接用maven,以后再说~~

c3p0-0.9.5.5.jar下载及Maven、Gradle引入代码,pom文件及包内class -时代Java (nowjava.com)

标签:java,数据库,st,sql,import,连接,连接池
来源: https://www.cnblogs.com/xypersonal/p/16287999.html

专注分享技术,共同学习,共同进步。侵权联系[admin#icode9.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有