Java线程安全的数据库连接

3

我正在编写一个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<Connection>类?还是有更好的方法使所有连接都线程安全?

1
https://dev59.com/pnM_5IYBdhLWcg3w1G2N - Peter Cetinski
我认为这不是一个好的做法。请使用连接池,它将保持可用连接的核心大小。如果您使用ThreadLocal,每个请求都将拥有一个连接,如果您的Web服务器被阻塞,连接将无法及时释放。 - zg_spring
3个回答

4
我认为,使数据库连接线程安全并不是一种常见的做法。通常你想要的是:
  • 序列化对servlet的访问,以便每次最多只有一个servlet执行代码(例如实现SingleThreadModel接口)。
  • 锁定特定的表/表页/行,以便您可以在某个特定元组上操作(通过更改数据库隔离级别)。
  • 使用乐观锁定来检测表中修改的行(使用表的某个参考属性检查当前版本是否与表中的版本相同)。
据我所知,ThreadLocal<Connection> 的典型用法是为每个线程存储一个唯一的数据库连接,因此可以在业务逻辑中的不同方法中使用相同的连接而无需每次将其作为参数传递。由于常见的servlet容器实现使用一个线程来处理HTTP请求,因此保证两个不同的请求使用两个不同的数据库连接。

你上一条评论在这里适用。我正在单个请求中使用连接。每个请求可以通过许多需要连接的函数调用。例如,我将使用通过Finder类在数据库中查找域对象的url传递的id。然后我将编辑该对象并通过其他类在数据库中更新它。虽然我每次都关闭我的结果集,但我希望连接保持不变。 - Sotirios Delimanolis
我仍然认为这是在重复造轮子。如果您要在容器(如Tomcat)中运行servlet,请使用JNDI查找数据源并配置数据源以为您池化连接。有很多示例可供参考。 - dbrin
当你执行getConnection()时,这不就是sql DriverManager所做的吗? - Sotirios Delimanolis
No. DriverManager.getConnection()总是打开一个新连接,但这是一个相对昂贵的操作,如果你要序列化DB访问,那么你只需要一个连接。相反,你可以自己配置一个小的连接池,甚至更好的是,通过使用JNDI获取对DataSource的引用,在容器中获取一个配置好的连接池。 - Gabriel Belingueres

2

我知道你说你不想使用库来完成这个任务,但如果你使用一个标准的连接池(如C3P0、DBCP或其他),你将会比自己编写更加高效。为什么不能使用库来完成呢?


0

我不确定为什么你想让你的数据库连接是线程安全的。大多数情况下,与数据库建立连接是交易中最耗时的部分。通常,在请求之间重用连接,并管理打开连接的池(通过框架或更典型的应用程序服务器)。

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


1
-1,使用Java同步来保护数据库事务不发生冲突是一个非常糟糕的想法。 - Steven Schlansker
我不会在常规应用程序中这样做,但听起来这是某种小型一次性项目,他想要不使用框架并希望在内部管理连接池。在这种情况下,这并不是一个坏主意。是的,你将使其他请求等待,但它可以完成肮脏的工作。 - dbrin
按照“正确的方式”去做其实并不难,而这种方法教会了糟糕的习惯。同时也会留下大量的代码债务,使得今后扩展应用程序的范围几乎变得不可能。 - Steven Schlansker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接