如何使用Spring的JdbcTemplate重用相同的连接?

时间:2011-02-10 18:34:55

标签: java spring jdbc

我有以下代码:


    @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] Loaded JDBC driver: org.h2.Driver
2011-02-10 12:24:17 JdbcTemplate [DEBUG] Executing SQL statement [SELECT 1]
2011-02-10 12:24:17 DataSourceUtils [DEBUG] Fetching JDBC Connection from DataSource
2011-02-10 12:24:17 DriverManagerDataSource [DEBUG] Creating new JDBC DriverManager Connection to [jdbc:h2:/data/h2/testa]
2011-02-10 12:24:17 DataSourceUtils [DEBUG] Returning JDBC Connection to DataSource
2011-02-10 12:24:17 JdbcTemplate [DEBUG] Executing SQL statement [SELECT 1]
2011-02-10 12:24:17 DataSourceUtils [DEBUG] Fetching JDBC Connection from DataSource
2011-02-10 12:24:17 DriverManagerDataSource [DEBUG] Creating new JDBC DriverManager Connection to [jdbc:h2:/data/h2/testa]
2011-02-10 12:24:17 DataSourceUtils [DEBUG] Returning JDBC Connection to DataSource

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

由于

6 个答案:

答案 0 :(得分:26)

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
}

答案 1 :(得分:20)

以下是使用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

答案 2 :(得分:7)

总之,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();

这意味着通过实现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()方法不是关闭连接,而是返回连接池的连接。

这就是魔术!

答案 3 :(得分:5)

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

答案 4 :(得分:4)

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

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

答案 5 :(得分:0)

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