【问题标题】:Singleton Database Class for JDBC Pool using Apache Tomcat JDBC使用 Apache Tomcat JDBC 的 JDBC 池单例数据库类
【发布时间】:2019-01-07 10:01:19
【问题描述】:

我正在尝试配置一个单例数据库类,以允许使用 JDK8 和 Tomcat7 对 Oracle XE 18c 数据库进行连接池。

由于以下错误,我无法编译源代码:

未报告的异常 SQLException;必须被抓获或被宣布为 扔了

类源代码:

package com.example.webapp.db;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.PoolProperties;
public class DatabaseManager {

    private static final DatabaseManager SINGLE_INSTANCE = new DatabaseManager();

    private DatabaseManager() throws SQLException {
        PoolProperties p = new PoolProperties();
        p.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
        p.setDriverClassName("oracle.jdbc.driver.OracleDriver");
        p.setUsername("scott");
        p.setPassword("tiger");
        p.setJmxEnabled(true);
        p.setTestWhileIdle(false);
        p.setTestOnBorrow(true);
        p.setValidationQuery("SELECT 1");
        p.setTestOnReturn(false);
        p.setValidationInterval(30000);
        p.setTimeBetweenEvictionRunsMillis(30000);
        p.setMaxActive(100);
        p.setInitialSize(10);
        p.setMaxWait(10000);
        p.setRemoveAbandonedTimeout(60);
        p.setMinEvictableIdleTimeMillis(30000);
        p.setMinIdle(10);
        p.setLogAbandoned(true);
        p.setRemoveAbandoned(true);
        p.setJdbcInterceptors("org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;"+
          "org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer");
        DataSource datasource = new DataSource();
        datasource.setPoolProperties(p); 

        Connection con = null;
        try {
          con = datasource.getConnection();
          Statement st = con.createStatement();
          ResultSet rs = st.executeQuery("select * from user");
          int cnt = 1;
          while (rs.next()) {
              System.out.println((cnt++)+". Host:" +rs.getString("Host")+
                " User:"+rs.getString("User")+" Password:"+rs.getString("Password"));
          }
          rs.close();
          st.close();
        } finally {
          if (con!=null) try {con.close();}catch (Exception ignore) {}
        }
    }

    public static DatabaseManager getInstance() {
        return SINGLE_INSTANCE;
    }
}

【问题讨论】:

  • ...那条消息有什么不清楚的地方?这是一个检查异常,所以它“必须被捕获或声明被抛出”。由于您不能在静态初始化程序中声明它,因此您需要捕获它。
  • 你让你的构造函数抛出了一个SQLException,它需要在调用这个构造函数时被捕获。这意味着包括new DatabaseManager() 的每一行都必须包含在try {...} catch (SQLException e) 中。在您的情况下,这将是困难的,因为该行是 private static final DatabaseManager... 所以也许在构造函数代码中的 try {...} finally {...} 中添加一个 catch 子句,并从构造函数签名中删除 throws SQLException
  • DatabaseManager 的初始化不应该仅仅因为它当前无法连接到数据库而失败。可以说,它甚至不应该尝试在构造函数中做类似的事情。

标签: java oracle tomcat jdbc java-8


【解决方案1】:

我认为您应该在 getInstance() 中抛出或处理 SQLException 并使用 try-with 资源(自动关闭资源)而不是 finally 块。您应该使用会引发 Exception 进行初始化的构造函数将 instance 设为常量。

package com.example.webapp.db;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.PoolProperties;

public class DatabaseManager {

    // not final anymore and null as default
    private static DatabaseManager instance = null;

    private DatabaseManager() {
        PoolProperties p = new PoolProperties();
        p.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
        p.setDriverClassName("oracle.jdbc.driver.OracleDriver");
        p.setUsername("scott");
        p.setPassword("tiger");
        p.setJmxEnabled(true);
        p.setTestWhileIdle(false);
        p.setTestOnBorrow(true);
        p.setValidationQuery("SELECT 1");
        p.setTestOnReturn(false);
        p.setValidationInterval(30000);
        p.setTimeBetweenEvictionRunsMillis(30000);
        p.setMaxActive(100);
        p.setInitialSize(10);
        p.setMaxWait(10000);
        p.setRemoveAbandonedTimeout(60);
        p.setMinEvictableIdleTimeMillis(30000);
        p.setMinIdle(10);
        p.setLogAbandoned(true);
        p.setRemoveAbandoned(true);
        p.setJdbcInterceptors("org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;"
                + "org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer");
        javax.sql.DataSource datasource = new DataSource();
        datasource.setPoolProperties(p);

        // use a try-with resource to get rid of the finally block...
        try (Connection con = datasource.getConnection()) {
            Statement st = con.createStatement();
            ResultSet rs = st.executeQuery("select * from user");
            int cnt = 1;

            while (rs.next()) {
                System.out.println((cnt++) + ". Host:" + rs.getString("Host")
                        + " User:" + rs.getString("User")
                        + " Password:" + rs.getString("Password"));
            }

            rs.close();
            st.close();
        // ... and handle the exception
        } catch (SQLException e) {
            System.err.println("SQLException while constructing the instance of DatabaseManager");
            e.printStackTrace();
        }
    }

    public static DatabaseManager getInstance() {
        // check for null here:
        if (instance == null) {
            instance = new DatabaseManager();
        }
        return instance;
    }
}

也许,为数据库连接创建一个初始化方法比在构造函数中初始化所有内容更好,但这是基于意见的。

【讨论】:

  • @Hulk 好的,那该怎么办?同步还是设为volatile?你有什么建议?
  • 我不确定 - 我可能会移动数据库 - 从构造函数中访问代码。或者它甚至不应该是一个单例。单例意味着它必须永远不会被构造超过一次,如果这确实是要求,那么在失败的情况下崩溃/终止应用程序是可以的(但我怀疑这是这里想要的行为) .
  • @Hulk 是的,我知道the discussion about Singletons。有时人们只是因为订单或与单身人士有关的特殊任务而不得不使用它们。我们不知道这里是否是这种情况,所以我只是试图简化提供的代码。好吧,如果它不是线程安全的,那就不是一个好的答案。
  • 您可以只使用getInstance-方法synchronized- 这会损害性能,但要保持简单且相对正确。但是,这将在每次调用时重试 DB 连接,直到它成功并且可能仍会返回 null(因为您吞下了异常)。这对于getInstance() 来说是出乎意料的,但抛出异常也会出人意料,所以如果不进行重大重新设计,我真的看不出有什么好的出路。不太严重:public Optional<DatabaseManager> tryToGetInstance()...
【解决方案2】:

改代码如下图,你要捕获异常。

private static  DatabaseManager SINGLE_INSTANCE = null;
static {
    try {
        SINGLE_INSTANCE = new DatabaseManager();
    }
    catch(Exception e) {
        e.printStackTrace();
    }
}

【讨论】:

  • 虽然这会使其编译,但它可能不是一个好主意,因为您现在需要在想要访问 SINGLE_INSTANCE 时进行空检查。
  • 是的。正如 Hulk 所说,需要进行空值检查并修改了方法 public static DatabaseManager getInstance() throws SQLException { if(SINGLE_INSTANCE == null) { synchronized(DatabaseManager.class){ if(SINGLE_INSTANCE == null) { SINGLE_INSTANCE = new DatabaseManager(); } } } return SINGLE_INSTANCE; }}
  • 最好抛出ExceptionInInitializerError,而不是忽略错误并假装什么都没发生。
猜你喜欢
  • 2014-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-24
  • 2012-03-02
  • 1970-01-01
  • 2013-09-04
相关资源
最近更新 更多