【问题标题】:How to reuse the same connection with a Spring's JdbcTemplate?如何使用 Spring 的 JdbcTemplate 重用相同的连接?
【发布时间】:2011-06-25 01:46:30
【问题描述】:

我有以下代码:

@Test public void springTest() throws SQLException{ //Connect to the DB. DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.h2.Driver"); dataSource.setUrl("jdbc:h2:/data/h2/testa"); dataSource.setUsername(""); dataSource.setPassword(""); JdbcTemplate jt=new JdbcTemplate(dataSource); jt.execute("SELECT 1"); jt.execute("SELECT 1"); }

我希望两个 execute() 行可以重用相同的连接。但是,日志输出显示:

2011-02-10 12:24:17 DriverManagerDataSource [INFO] 加载的 JDBC 驱动程序:org.h2.Driver 2011-02-10 12:24:17 JdbcTemplate [DEBUG] 执行 SQL 语句 [SELECT 1] 2011-02-10 12:24:17 DataSourceUtils [DEBUG] 从数据源获取 JDBC 连接 2011-02-10 12:24:17 DriverManagerDataSource [DEBUG] 创建新的 JDBC DriverManager 连接到 [jdbc:h2:/data/h2/testa] 2011-02-10 12:24:17 DataSourceUtils [DEBUG] 返回 JDBC 连接到 DataSource 2011-02-10 12:24:17 JdbcTemplate [DEBUG] 执行 SQL 语句 [SELECT 1] 2011-02-10 12:24:17 DataSourceUtils [DEBUG] 从数据源获取 JDBC 连接 2011-02-10 12:24:17 DriverManagerDataSource [DEBUG] 创建新的 JDBC DriverManager 连接到 [jdbc:h2:/data/h2/testa] 2011-02-10 12:24:17 DataSourceUtils [DEBUG] 返回 JDBC 连接到 DataSource

上面的例子运行得非常快,但是我有一段更大的代码,它基本上做同样的事情,并且在Creating new JDBC DriverManager Connection 上挂了很长时间。我从来没有收到错误,但它使代码运行非常缓慢。我可以以某种方式重构上述代码以仅使用相同的连接吗?

谢谢

【问题讨论】:

    标签: java spring jdbc


    【解决方案1】:

    我知道这是特定情况(取决于您要使用的功能集),但您可以简单地使用 JdbcTemplate.batchUpdate 方法。

    【讨论】:

    • 我认为这对插入和更新很有用,但对选择没有用,除非有一种聪明的方法可以从中获得结果……但在这种情况下,一个定义更明确的 SQL 语句可能会提高效率。
    【解决方案2】:

    Spring 提供了一个特殊的 DataSource 允许您执行此操作:SingleConnectionDataSource

    将您的代码更改为此应该可以解决问题:

    SingleConnectionDataSource dataSource = new SingleConnectionDataSource();
    ....
    // The rest stays as is
    

    为了在多线程应用程序中使用,您可以通过从池中借用新连接并将其包装在数据库密集型代码部分周围来使代码可重入:

    // ... this code may be invoked in multiple threads simultaneously ...
    
    try(Connection conn = dao.getDataSource().getConnection()) {
        JdbcTemplate db = new JdbcTemplate(new SingleConnectionDataSource(conn, true));
    
        // ... database-intensive code goes here ... 
        // ... this code also is safe to run simultaneously in multiple threads ...
        // ... provided you are not creating new threads inside here
    }
    

    【讨论】:

    • 这不能被使用,因为 SingleConnectionDataSource 从它的 java 文档“显然,这不是多线程能力的”不是线程安全的。而且由于单独的测试方法是由junit在他们自己的线程中执行的,所以OP必须在每个方法中设置SingleConnectionDataSource,这会破坏他正在寻找的性能
    • 问题中描述的用例绝不暗示多线程支持是一项要求。
    • 根据定义,在多个线程中使用同一个连接不是线程安全的吗?
    • 您可以通过在需要时创建连接来使其成为线程安全的,然后在线程中重新使用它。查看我的编辑。
    • 我比较了 DriverManagerDataSource、SingleConnectionDataSource、BasicDataSource (commons.dbcp) 而你的建议 SingleConnectionDataSource 是最懒的
    【解决方案3】:

    一句话,Spring JDBCTemplate DriverManagerDataSource 不支持连接池。如果要使用连接池,DBCPC3P0 都是不错的选择。

    让我们通过JDBCTemplate源代码看看为什么......

    无论调用updatequeryForObject等方法,最终都会调用execute方法:

        @Override
        public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
            Assert.notNull(action, "Callback object must not be null");
    
            Connection con = DataSourceUtils.getConnection(getDataSource());
            try {
                Connection conToUse = con;
                if (this.nativeJdbcExtractor != null) {
                    // Extract native JDBC Connection, castable to OracleConnection or the like.
                    conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
                }
                else {
                    // Create close-suppressing Connection proxy, also preparing returned Statements.
                    conToUse = createConnectionProxy(con);
                }
                return action.doInConnection(conToUse);
            }
            catch (SQLException ex) {
                // Release Connection early, to avoid potential connection pool deadlock
                // in the case when the exception translator hasn't been initialized yet.
                DataSourceUtils.releaseConnection(con, getDataSource());
                con = null;
                throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
            }
            finally {
                DataSourceUtils.releaseConnection(con, getDataSource());
            }
        }
    

    调用DataSourceUtils.getConnection方法获取连接,调用DataSourceUtils.releaseConnection释放连接。

    DataSourceUtils 源代码,我们看到Connection con = dataSource.getConnection();con.close();

    也就是说get连接操作是通过实现DataSource接口定义的,关闭连接操作是通过实现Connection接口定义的。这允许其他 DataSource/Connection 实现轻松注入 Spring JDBCTemplate。

    Spring JDBCTemplate 中的DataSource 实现是DriverManagerDataSource。来自:

    protected Connection getConnectionFromDriverManager(String url, Properties props) throws SQLException {
        return DriverManager.getConnection(url, props);
    }
    

    public static void doCloseConnection(Connection con, DataSource dataSource) throws SQLException {
        if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource).shouldClose(con)) {
            con.close();
        }
    }
    

    我们看到每次它返回一个新连接,并关闭当前连接。这就是它不支持连接池的原因。

    虽然在DBCP 中,DataSource 的实现是PoolingDataSource,但我们看到getConnection() 来自连接池; Connection 的实现是PoolableConnection,我们看到close() 方法不是关闭连接,而是将连接返回到连接池。

    这就是魔法!

    【讨论】:

      【解决方案4】:

      Looking at the Spring's code这是我高层次的理解。

      您正在创建一个DriverManagerDataSource。这在内部使用DataSourceUtils 来获取连接。并且它仅在正在进行活动事务时才重用连接。因此,如果您在单个事务中运行两个执行,那么它将使用相同的连接。或者,您也可以将池与 1 个连接一起使用,以便创建和重用单个连接。

      【讨论】:

      • @shams 链接已更新。你也可以从源代码级别看到我的答案:)
      【解决方案5】:

      您需要将调用包装在单个事务中。通常,您会在应用程序中使用 Spring 的 AOP + @Transactional 注释来执行此操作。您还可以使用PlatformTranactionManagerTransactionTemplate 以编程方式执行此操作,并将代码包装在TransactionCallback 中执行。请参阅transaction documentation

      【讨论】:

        【解决方案6】:

        这是一个使用 Apache DBCP 的示例:-

        BasicDataSource dbcp = new BasicDataSource();
        dbcp.setDriverClassName("com.mysql.jdbc.Driver");
        dbcp.setUrl("jdbc:mysql://localhost/test");
        dbcp.setUsername("");
        dbcp.setPassword("");
        
        JdbcTemplate jt = new JdbcTemplate(dbcp);
        jt.execute("SELECT 1");
        jt.execute("SELECT 1");
        

        log4j 的输出是:-

        [DEBUG] [JdbcTemplate] [execute:416] - Executing SQL statement [SELECT 1]
        [DEBUG] [DataSourceUtils] [doGetConnection:110] - Fetching JDBC Connection from DataSource
        [DEBUG] [DataSourceUtils] [doReleaseConnection:332] - Returning JDBC Connection to DataSource
        [DEBUG] [JdbcTemplate] [execute:416] - Executing SQL statement [SELECT 1]
        [DEBUG] [DataSourceUtils] [doGetConnection:110] - Fetching JDBC Connection from DataSource
        [DEBUG] [DataSourceUtils] [doReleaseConnection:332] - Returning JDBC Connection to DataSource
        

        【讨论】:

        • 这是一个简单的改变,而且似乎奏效了。它是线程安全的吗?
        • 它是线程安全的,因为这就是连接池的全部意义所在。 :) 我也在我的测试用例中使用了这个,当然我连接 dataSource 而不是以编程方式创建它。
        • 是时候举行盛大的庆祝活动了。我买啤酒。 :)
        • 奇怪,在另一台机器上它根本没有帮助速度。我正在使用 H2 嵌入式..任何提示?
        • 如何确保使用相同的连接? dbcp 依赖提供的 DataStore 不能返回到 JdbcTemplate 的新连接吗?连接池的目的不是吗?
        猜你喜欢
        • 2014-09-28
        • 2021-05-28
        • 2015-02-20
        • 2015-05-08
        • 1970-01-01
        • 2017-04-27
        • 2017-02-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多