如何使用Spring的JdbcTemplate重用相同的连接?
我有以下代码:
@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]从DataSource获取JDBC连接 2011-02-10 12:24:17 DriverManagerDataSource [DEBUG]创建新的JDBC DriverManager连接到[jdbc:h2:/ data / h2 / testa] 2011-02-10 12:24:17 DataSourceUtils [DEBUG]返回到DataSource的JDBC连接 2011-02-10 12:24:17 JdbcTemplate [DEBUG]执行SQL语句[SELECT 1] 2011-02-10 12:24:17 DataSourceUtils [DEBUG]从DataSource获取JDBC连接 2011-02-10 12:24:17 DriverManagerDataSource [DEBUG]创建新的JDBC DriverManager连接到[jdbc:h2:/ data / h2 / testa] 2011-02-10 12:24:17 DataSourceUtils [DEBUG]返回到DataSource的JDBC连接
上面的例子运行得非常快,但是我有一个更大的代码片段,基本上做同样的事情并且在Creating new JDBC DriverManager Connection
上挂了很长时间。 我从来没有得到错误,但它使代码运行得非常慢。 我可以以某种方式重构上面的代码只是使用相同的连接?
谢谢
以下是使用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
Spring提供了一个特殊的DataSource,允许您执行此操作: SingleConnectionDataSource
将代码更改为此应该可以解决问题:
SingleConnectionDataSource dataSource = new SingleConnectionDataSource(); .... // The rest stays as is
为了在multithreading应用程序中使用,您可以通过从池中借用新连接并将其包装在数据库密集型代码段中来使代码可重入:
// ... 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 }
您需要将调用包装在单个事务中。 通常,您可以在应用程序中使用Spring的AOP + @Transactional
注释执行此操作。 您还可以使用PlatformTranactionManager
, TransactionTemplate
以编程方式执行此操作,并将代码包装在TransactionCallback
执行。 请参阅交易文档 。
看看Spring的代码,这是我对高层的理解。
您正在创建DriverManagerDataSource 。 这在内部使用DataSourceUtils来获取连接。 并且只有在正在进行的活动事务时才会重新使用该连接。 因此,如果您在单个事务中同时运行两个执行,那么它将使用相同的连接。 或者您也可以使用1连接池,以便创建并重用单个连接。
总之,Spring JDBCTemplate DriverManagerDataSource
不支持连接池。 如果您想使用连接池, DBCP
和C3P0
都是不错的选择。
让我们通过JDBCTemplate源代码来了解为什么……
无论是调用update
, queryForObject
等方法,它们最终都会调用execute
方法:
@Override public T execute(ConnectionCallback 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.getConnection
来释放连接。
从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()
方法不是关闭连接,而是返回连接池的连接。
这才是神奇的!
我知道这是情境化的(取决于你想要使用的function集),但你可以简单地使用JdbcTemplate.batchUpdate
方法。