【问题标题】:How to establish a connection pool in JDBC?JDBC中如何建立连接池?
【发布时间】:2011-02-19 13:49:27
【问题描述】:

谁能提供有关如何建立 JDBC 连接池的示例或链接?

从谷歌搜索中,我看到了许多不同的方法,这很令人困惑。

最终我需要代码来返回一个java.sql.Connection 对象,但我在开始时遇到了问题。欢迎任何建议。

更新: javax.sqljava.sql 没有池连接实现吗?为什么不最好使用这些?

【问题讨论】:

  • 不,现有的 JDBC 不提供连接池。为此,您需要一个单独的库。大多数应用服务器和 servlet 容器都包含连接池。此外,JPA 实现通常也提供实现。
  • 现代 Java 用户的更新。 JDBC 3.0+(我相信它在 Java 6 中使用?)有一个用于池化数据库连接的实现。 Java 7 使用 JDBC 4,而 Java 8 JDBC 4.1。

标签: java jdbc connection-pooling


【解决方案1】:

如果你需要一个独立的连接池,我更喜欢C3P0 而不是DBCP(我在这个previous answer 中提到过),我只是在重负载下使用DBCP 遇到了太多问题。使用 C3P0 非常简单。来自documentation

ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "org.postgresql.Driver" ); //loads the jdbc driver
cpds.setJdbcUrl( "jdbc:postgresql://localhost/testdb" );
cpds.setUser("swaldman");
cpds.setPassword("test-password");

// the settings below are optional -- c3p0 can work with defaults
cpds.setMinPoolSize(5);
cpds.setAcquireIncrement(5);
cpds.setMaxPoolSize(20);

// The DataSource cpds is now a fully configured and usable pooled DataSource 

但是,如果您在应用程序服务器中运行,我建议使用它提供的内置连接池。在这种情况下,您需要对其进行配置(请参阅应用服务器的文档)并通过 JNDI 检索 DataSource:

DataSource ds = (DataSource) new InitialContext().lookup("jdbc/myDS");

【讨论】:

  • 同上,那个。多年来,我一直在观察 DBCP 在负载下的死锁。一个又一个版本。
  • 是的,但 C3P0 也是,我在 BoneCP 方面有过最好的体验
  • 看起来 BoneCP 已经 deprecated 支持 HikariCPan answer below中也提到了HikariCP。
【解决方案2】:

HikariCP

它现代、快速、简单。我将它用于每个新项目。 我更喜欢它而不是 C3P0,不太了解其他池。

【讨论】:

    【解决方案3】:

    通常,如果您需要一个连接池,则您正在编写一个在某些托管环境中运行的应用程序,即您在应用程序服务器中运行。如果是这种情况,请务必在尝试任何其他选项之前check what connection pooling facilities your application server provides

    开箱即用的解决方案将与其他应用服务器设施进行最佳集成。但是,如果您不在应用程序服务器中运行,我建议您使用 Apache Commons DBCP Component。它被广泛使用,并提供了大多数应用程序所需的所有基本池功能。

    【讨论】:

      【解决方案4】:

      不要重新发明轮子。

      尝试现成的第 3 方组件之一:

      • Apache DBCP - 这个是 由 Tomcat 内部使用,并由 真的是你的。
      • c3p0

      Apache DBCP 提供了关于如何设置池 javax.sql.DataSource 的不同示例。这是一个sample,可以帮助您入门。

      【讨论】:

      • 它叫做C3P0。顺便说一下,它在多线程环境中比 DBCP 性能更高,因为 DBCP 锁定对单个线程的访问。
      • @BalusC。感谢您的指正,我的disclecsia 胜过我。你可以看到链接是正确的。 :)
      • @Mudassir。我建议查看从 Spring -> static.springsource.com/projects/tc-server/2.0/admin/htmlsingle/… 为 Tomcat 贡献的 DBCP 的替代品。您不需要整个 Tomcat 服务器来使用它,只需一个 jar tomcat-jdbc。你可以从 Maven 中心获取它 -> org.apache.tomcat:tomcat-jdbc:jar:7.0.22 -> search.maven.org/…
      • @AlexanderPogrebnyak:谢谢亚历山大,你真好。我计划在 Axis Web 服务中使用 CP。会考虑你的建议。 – Mudassir 7 分钟前
      【解决方案5】:

      我建议使用commons-dbcp 库。有很多examples 列出了如何使用它,这里是移动simple one 的链接。用法很简单:

       BasicDataSource ds = new BasicDataSource();
       ds.setDriverClassName("oracle.jdbc.driver.OracleDriver")
       ds.setUsername("scott");
       ds.setPassword("tiger");
       ds.setUrl(connectURI);
       ...
       Connection conn = ds.getConnection();
      

      您只需要创建一次数据源,因此如果您不知道如何操作,请务必阅读文档。如果您不知道如何正确编写 JDBC 语句以免泄漏资源,您可能还想阅读此Wikipedia 页面。

      【讨论】:

      • 这是否真的创建了一个连接池?
      • @llm 当然! javax.sql.DataSource 接口的定义包含“连接池”的实现。(此外,我想你已经知道什么是 JDBC 接口了)
      【解决方案6】:

      在我工作的地方使用的应用服务器(我记得是 Oracle 应用服务器 10g)中,池由应用服务器处理。我们使用带有javax.sql.InitialContext 的JNDI 查找来检索javax.sql.DataSource

      它做了这样的事情

      try {     
         context = new InitialContext();
         jdbcURL = (DataSource) context.lookup("jdbc/CachedDS");
         System.out.println("Obtained Cached Data Source ");
      }
      catch(NamingException e)   
      {  
          System.err.println("Error looking up Data Source from Factory: "+e.getMessage());
      }
      

      (这段代码不是我们写的,是从this documentation复制过来的。)

      【讨论】:

        【解决方案7】:

        • 池化机制是预先创建对象的方式。加载类时。
        • 它改进了应用程序 performance [通过重新使用相同的对象对 Object-Data 执行任何操作] & memory [分配和取消分配许多对象会产生大量内存管理开销]。
        • 不需要清理对象,因为我们使用的是同一个对象,从而减少了垃圾收集负载。

        « 池化 [Object 池,String 常量池,Thread 池,连接池]

        字符串常量池

        • 字符串文字池仅维护每个不同字符串值的一份副本。必须是不可变的。
        • 调用 intern 方法时,它使用 equals 方法检查池中具有相同内容的对象可用性。 « 如果字符串副本在池中可用,则返回引用。 « 否则,将 String 对象添加到池中并返回引用。

        示例:用于验证池中Unique Object 的字符串。

        public class StringPoolTest {
            public static void main(String[] args) { // Integer.valueOf(), String.equals()
                String eol = System.getProperty("line.separator"); //java7 System.lineSeparator();
        
                String s1 = "Yash".intern();
                System.out.format("Val:%s Hash:%s SYS:%s "+eol, s1, s1.hashCode(), System.identityHashCode(s1));
                String s2 = "Yas"+"h".intern();
                System.out.format("Val:%s Hash:%s SYS:%s "+eol, s2, s2.hashCode(), System.identityHashCode(s2));
                String s3 = "Yas".intern()+"h".intern();
                System.out.format("Val:%s Hash:%s SYS:%s "+eol, s3, s3.hashCode(), System.identityHashCode(s3));
                String s4 = "Yas"+"h";
                System.out.format("Val:%s Hash:%s SYS:%s "+eol, s4, s4.hashCode(), System.identityHashCode(s4));
            }
        }
        

        使用 Type-4 的连接池 Driver 使用 3rd 方库[DBCP2, c3p0, Tomcat JDBC]

        Type 4 - The Thin driver converts JDBC calls directly into the vendor-specific database protocol Ex[Oracle - Thick, MySQL - Quora].wiki

        在连接池机制中,当类被加载时,它会得到physical JDBC connection 对象,并为用户提供一个封装的物理连接对象。 PoolableConnection 是实际连接的包装器。

        • getConnection() 从连接 objectpool 中选择一个免费的包装连接并将其返回。
        • close() 不是关闭它,而是将包装连接返回到池中。

        示例:在 Java 7 中使用 ~ DBCP2 连接池[try-with-resources]

        public class ConnectionPool {
            static final BasicDataSource ds_dbcp2 = new BasicDataSource();
            static final ComboPooledDataSource ds_c3p0 = new ComboPooledDataSource();
            static final DataSource ds_JDBC = new DataSource();
        
            static Properties prop = new Properties();
            static {
                try {
                    prop.load(ConnectionPool.class.getClassLoader().getResourceAsStream("connectionpool.properties"));
        
                    ds_dbcp2.setDriverClassName( prop.getProperty("DriverClass") );
                    ds_dbcp2.setUrl( prop.getProperty("URL") );
                    ds_dbcp2.setUsername( prop.getProperty("UserName") );
                    ds_dbcp2.setPassword( prop.getProperty("Password") );
                    ds_dbcp2.setInitialSize( 5 );
        
                    ds_c3p0.setDriverClass( prop.getProperty("DriverClass") );
                    ds_c3p0.setJdbcUrl( prop.getProperty("URL") );
                    ds_c3p0.setUser( prop.getProperty("UserName") );
                    ds_c3p0.setPassword( prop.getProperty("Password") );
                    ds_c3p0.setMinPoolSize(5);
                    ds_c3p0.setAcquireIncrement(5);
                    ds_c3p0.setMaxPoolSize(20);
        
                    PoolProperties pool = new PoolProperties();
                    pool.setUrl( prop.getProperty("URL") );
                    pool.setDriverClassName( prop.getProperty("DriverClass") );
                    pool.setUsername( prop.getProperty("UserName") );
                    pool.setPassword( prop.getProperty("Password") );
                    pool.setValidationQuery("SELECT 1");// SELECT 1(mysql) select 1 from dual(oracle)
        
                    pool.setInitialSize(5);
                    pool.setMaxActive(3);
                    ds_JDBC.setPoolProperties( pool );
                } catch (IOException e) {   e.printStackTrace();
                } catch (PropertyVetoException e) { e.printStackTrace(); }
            }
        
            public static Connection getDBCP2Connection() throws SQLException {
                return ds_dbcp2.getConnection();
            }
        
            public static Connection getc3p0Connection() throws SQLException {
                return ds_c3p0.getConnection();
            }
        
            public static Connection getJDBCConnection() throws SQLException {
                return ds_JDBC.getConnection();
            }
        }
        public static boolean exists(String UserName, String Password ) throws SQLException {
            boolean exist = false;
            String SQL_EXIST = "SELECT * FROM users WHERE username=? AND password=?";
            try ( Connection connection = ConnectionPool.getDBCP2Connection();
                  PreparedStatement pstmt = connection.prepareStatement(SQL_EXIST); ) {
                pstmt.setString(1, UserName );
                pstmt.setString(2, Password );
        
                try (ResultSet resultSet = pstmt.executeQuery()) {
                    exist = resultSet.next(); // Note that you should not return a ResultSet here.
                }
            }
            System.out.println("User : "+exist);
            return exist;
        }
        

        jdbc:<DB>:<drivertype>:<HOST>:<TCP/IP PORT>:<dataBaseName> jdbc:oracle:thin:@localhost:1521:myDBName jdbc:mysql://localhost:3306/myDBName

        connectionpool.properties

        URL         : jdbc:mysql://localhost:3306/myDBName
        DriverClass : com.mysql.jdbc.Driver
        UserName    : root
        Password    :
        

        Web 应用程序:为了避免在所有连接关闭时出现连接问题[MySQL "wait_timeout" 默认 8 小时],以便重新打开与底层数据库的连接。

        您可以通过设置 testOnBorrow = true 和 validationQuery= "SELECT 1" 来测试每个连接,并且不要对 MySQL 服务器使用 autoReconnect,因为它已被弃用。 issue

        ===== ===== context.xml ===== =====
        <?xml version="1.0" encoding="UTF-8"?>
        <!-- The contents of this file will be loaded for a web application -->
        <Context>
            <Resource name="jdbc/MyAppDB" auth="Container" 
                factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" 
                type="javax.sql.DataSource" 
        
                initialSize="5" minIdle="5" maxActive="15" maxIdle="10"
        
                testWhileIdle="true"
                    timeBetweenEvictionRunsMillis="30000"
        
                testOnBorrow="true"
                    validationQuery="SELECT 1"
                    validationInterval="30000"
        
        
                driverClassName="com.mysql.jdbc.Driver" 
                url="jdbc:mysql://localhost:3306/myDBName" 
                username="yash" password="777"
            />
        </Context>
        
        ===== ===== web.xml ===== =====
        <resource-ref>
            <description>DB Connection</description>
            <res-ref-name>jdbc/MyAppDB</res-ref-name>
            <res-type>javax.sql.DataSource</res-type>
            <res-auth>Container</res-auth>
        </resource-ref>
        ===== ===== DBOperations ===== =====
        servlet «   init() {}
        Normal call used by sevlet  « static {}
        
        static DataSource ds;
        static {
            try {
                Context ctx=new InitialContext();
                Context envContext = (Context)ctx.lookup("java:comp/env");
                ds  =   (DataSource) envContext.lookup("jdbc/MyAppDB");
            } catch (NamingException e) {   e.printStackTrace();    }
        }
        

        另请参阅:

        【讨论】:

        • 在字符串常量池示例中,我理解当您写“如果池中的字符串副本可用[.equals()] 则返回引用。« 否则,字符串对象被添加到池并返回参考。”但是在 public class StringPoolTest 中只有 2 个 void 方法,所以它们不会返回任何东西。该代码实际上是否经历了管理字符串池的过程?它甚至似乎没有使用任何参数。
        • @jeffery_the_wind :- 这只是为了了解池的概念,对于字符串池验证,我只使用了 hashCode,identityHashCode methodes。修改了代码...
        • 抱歉,s1 没有定义?
        • 好的,只是想确保我看到了这一切。我会努力的。我需要更接近您的ConnectionPool 类的东西。非常感谢。
        【解决方案8】:

        在 2017 年末,Proxool、BoneCP、C3P0、DBCP 在这个时候大部分都已经失效了。 HikariCP(创建于 2012 年)看起来很有前途,让我所知道的任何其他事情都大吃一惊。 http://www.baeldung.com/hikaricp

        Proxool 存在许多问题:
        - 在重负载下可能会超过最大连接数并且不会低于最大连接数
        - 即使在连接过期后也可以设法不返回最小连接
        - 如果在 HouseKeeper 线程期间无法连接到数据库,可以锁定整个池(以及所有服务器/客户端线程)(不使用 .setQueryTimeout)
        - HouseKeeper 线程在为其进程拥有连接池锁定时,请求 Prototyper 线程重新创建连接(扫描),这可能导致竞争条件/锁定。在这些方法调用中,最后一个参数在循环期间应始终为 sweep:false,在其下方只有 sweep:true。
        - HouseKeeper 只需要最后一个 PrototypeController 扫描,并且还有更多[上面提到]
        - 在查看哪些连接可能过期之前,HouseKeeper 线程会检查连接测试[测试过期连接的风险可能会因防火墙中数据库的其他超时而中断/终止,等等。]
        - 项目有未完成的代码(已定义但未执行的属性)
        - 如果未定义,默认最长连接寿命为 4 小时(过长)
        - 每个池每五秒运行一次 HouseKeeper 线程(过多)

        您可以修改代码并进行这些改进。但由于它是在 2003 年创建并在 2008 年更新的,它缺乏像 hikaricp 这样的解决方案所利用的近 10 年的 Java 改进。

        【讨论】:

          【解决方案9】:

          正如其他人的回答,您可能会对Apache Dbcpc3p0 感到满意。两者都很受欢迎,而且效果很好。

          关于你的疑问

          javax.sql 或 java.sql 没有 池连接实现?为什么 使用这些不是最好吗?

          它们不提供实现,而是提供接口和一些支持类,只与实现第三方库(池或驱动程序)的程序员相关。通常你甚至不看那个。您的代码应该以透明的方式处理池中的连接,就像它们是“普通”连接一样。

          【讨论】:

            【解决方案10】:

            Vibur DBCP 是用于此目的的另一个库。可以在其网站上找到几个示例,展示如何配置它以与 Hibernate、Spring+Hibernate 或以编程方式一起使用:http://www.vibur.org/

            另外,请参阅免责声明here

            【讨论】:

              【解决方案11】:

              Apache Commons 有一个用于此目的的库:DBCP。除非您对游泳池有奇怪的要求,否则我会使用库,因为它肯定会比您希望的更棘手和更微妙。

              【讨论】:

                【解决方案12】:

                您应该考虑使用 UCP。 Universal Connection Pool (UCP) 是一个 Java 连接池。它是一个功能丰富的连接池,并与 Oracle 的 Real Application Clusters (RAC)、ADG、DG 数据库紧密集成。

                有关 UCP 的更多详细信息,请参阅此page

                【讨论】:

                  【解决方案13】:

                  MiniConnectionPoolManager 是一个单 java 文件实现,如果您正在寻找可嵌入的解决方案并且不太关心性能(尽管我没有在这方面对其进行测试)。

                  它是多许可的EPLLGPLMPL

                  它的文档还提供了值得检查的替代方案(在 DBCP 和 C3P0 之上):

                  【讨论】:

                    猜你喜欢
                    • 2012-06-12
                    • 1970-01-01
                    • 2014-09-13
                    • 2014-11-13
                    • 1970-01-01
                    • 2016-07-06
                    • 2015-02-25
                    • 2015-01-16
                    • 2011-10-10
                    相关资源
                    最近更新 更多