【问题标题】:Java thread safe database connectionsJava 线程安全数据库连接
【发布时间】:2012-02-25 02:03:33
【问题描述】:

我正在编写一个 servlet,它通过访问和修改数据库中的某些表来处理每个请求。我希望与数据库的连接是线程安全的。我不想为此使用现有的库/框架(spring、hibernate 等)。

我知道我可以通过以下方式使用 java 的 ThreadLocal:

public class DatabaseRegistry { //assume it's a singleton


    private Properties prop = new Properties();
    
    public static final ThreadLocal<Connection> threadConnection = new ThreadLocal<Connection>();
    
    private Connection connect() throws SQLException {
        try {
            // This will load the MySQL driver, each DB has its own driver
            Class.forName("com.mysql.jdbc.Driver");
            // Setup the connection with the DB
            Connection connection = DriverManager
                    .getConnection("jdbc:mysql://" + prop.getProperty("hostname") + "/" + prop.getProperty("database") + "?"
                            + "user=" + prop.getProperty("username") + "&password=" + prop.getProperty("password"));
            return connection;
        } catch (SQLException e) {          
            throw e;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } 
        
        return null;
        
    }
    
    public Connection getConnection() throws SQLException {
        
        if (threadConnection.get() == null) {
            Connection connection = connect();
            threadConnection.set(connection);
            return threadConnection.get();
        } else {
            return threadConnection.get();
        }
    } 

    private void freeConnection(Connection connection) throws SQLException {
        connection.close();
        threadConnection.remove();
    }
}

每次调用getConnection() 时,都会将新连接添加到ThreadLocal 对象中,然后在释放连接时将其删除。

这是正确的做法还是应该 DatabaseRegistry 本身扩展 ThreadLocal&lt;Connection&gt; 类?还是有更好的方法来确保所有连接线程安全?

【问题讨论】:

  • 我认为这不是一个好习惯。请使用连接池,它将保持可用连接的核心大小。如果您使用 ThreadLocal,则每个请求都将拥有一个连接,如果您的 Web 服务器被阻塞,则连接将不会按时释放。

标签: java mysql database thread-safety thread-local


【解决方案1】:

我不认为使数据库连接线程安全是一种常见的做法。通常你想要的是:

  • 序列化对 servlet 某些部分的访问,这样一次执行代码的 servlet 不超过一个(例如,实现 SingleThreadModel 接口)。
  • 锁定特定表/表页/行,以便您可以对某些特定元组进行操作(通过更改数据库隔离级别)。
  • 使用乐观锁定检测表中的修改行(使用表的某些引用属性来检查当前版本是否与表中的版本相同)。

AFAIK,ThreadLocal&lt;Connection&gt; 的典型用途是为每个线程存储一个唯一的数据库连接,以便可以在业务逻辑中的不同方法中使用相同的连接,而无需每次都将其作为参数传递。因为普通的 servlet 容器实现使用一个线程来完成一个 HTTP 请求,所以保证两个不同的请求使用两个不同的数据库连接。

【讨论】:

  • 您的最后一条评论适用于此。我在单个请求中使用连接。每个请求都可以通过多次调用需要连接的函数。例如,我将使用 url 中传递的 id 通过 Finder 类在数据库中查找域对象。然后我将编辑该对象并通过其他一些类在数据库中更新它。虽然我每次都会关闭我的结果集,但我希望连接是相同的。
  • 我仍然认为这是在重新发明轮子。如果您要在容器(如 Tomcat)中运行 servlet,请使用 JNDI 查找数据源并配置数据源以为您连接连接池。那里有很多例子。
  • 这不是 sql DriverManager 在执行 getConnection() 时所做的;
  • 没有。 DriverManager.getConnection() 总是打开一个新的连接,但这是一个相对昂贵的操作,如果你要序列化数据库访问,那么你只需要一个连接。相反,您可以自己配置一个小型连接池,或者更好的是,通过使用 JNDI 获取对 DataSource 的引用,在容器中配置一个。
【解决方案2】:

我知道您说过您不想使用库来执行此操作,但如果您这样做了,您会变得更好。选择一个标准的连接池(C3P0、DBCP 或其他东西),你会比你自己做的更快乐。为什么不能使用库来执行此操作?

【讨论】:

    【解决方案3】:

    我不确定您为什么希望您的数据库连接是线程安全的。大多数时候建立与数据库的连接是事务中最长的部分。通常在请求之间重用连接并管理打开的连接池(通过框架或更典型的应用程序服务器)。

    如果您担心对同一个表的并发修改,您可能需要查看同步方法:http://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html

    【讨论】:

    • -1,使用Java同步来保护数据库事务不发生冲突是一个可怕的想法
    • 我不会在常规应用程序中这样做,但听起来这是一种小型的临时项目,他不想使用任何框架并想在内部管理连接池。在这种情况下,这不是一个坏主意。是的,你会让其他请求等待,但它完成了肮脏的工作。
    • 以“正确的方式”做这件事并没有那么难,这会教人养成糟糕的习惯。它还留下了大量的代码债务,几乎不可能在以后扩展应用程序的范围。
    猜你喜欢
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多