2011-02-10 13 views
20

Tengo el siguiente código:¿Cómo reutilizar la misma conexión con Spring's 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"); 
    } 
 

espero que los dos execute() líneas de reutilizar la misma conexión. Sin embargo, la salida del registro dice:

 
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 

El ejemplo anterior funciona bastante rápido, pero tengo un pedazo más grande de código que hace básicamente lo mismo y se cuelga durante mucho tiempo en Creating new JDBC DriverManager Connection. Nunca recibo un error, pero hace que el código se ejecute muy lentamente. ¿De alguna manera puedo refactorizar el código anterior para simplemente usar la misma conexión?

Gracias

Respuesta

16

Aquí hay una n ejemplo usando 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"); 

La salida log4j es: -

[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 
+0

Eso es un cambio fácil y parece haber funcionado. ¿Es seguro para subprocesos? – User1

+2

Es seguro para subprocesos, porque esto es de lo que se trata un conjunto de conexiones. :) Usé esto también en mis casos de prueba, con la condición de que cablee 'dataSource' en lugar de crearlo programáticamente. – limc

+2

¡Vaya, mi gran código acaba de obtener 100 veces más rápido! – User1

4

Looking at the Spring's code Este es mi entendimiento en un nivel alto.

Usted está creando DriverManagerDataSource. Esto internamente usa DataSourceUtils para obtener una conexión. Y solo reutiliza la conexión si hay una transacción activa en progreso. Entonces, si ejecuta los dos ejecutables en una sola transacción, usará la misma conexión. O también puede usar la agrupación con 1 conexión para que se cree y reutilice una sola conexión.

+1

¡Ups no encontrado! – Shams

+1

@shams Enlaces actualizados. Y también puede ver mi respuesta desde el nivel del código fuente :) – coderz

23

Spring proporciona una fuente de datos especial que le permite hacer esto: SingleConnectionDataSource

Cómo cambiar el código de este debe hacer el truco:

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

Para su uso en aplicaciones de subprocesos múltiples, puede hacer que el código reentrante pidiendo prestado una nueva conexión de la agrupación y envolviéndolo alrededor de la sección de base de datos intensivos de código:

// ... 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 
} 
+2

Esto no se puede utilizar porque SingleConnectionDataSource no es seguro para subprocesos desde su documento de Java "Obviamente, esto no es multi-threading capaz". Y dado que los métodos de prueba individuales se ejecutan en su propio hilo por junit, el OP tiene que configurar SingleConnectionDataSource en cada método que derrota el rendimiento que está buscando –

+4

El caso de uso descrito en la pregunta de ninguna manera alude al soporte de múltiples subprocesos siendo un requisito. –

+4

¿No está utilizando la misma conexión en varios subprocesos que no son seguros para subprocesos, por definición? –

4

Se necesitan las llamadas que se van envueltos en una sola transacción. Por lo general, haría esto con la anotación AOP de Spring + @Transactional en una aplicación. También puede hacerlo programáticamente con un PlatformTranactionManager, un TransactionTemplate y envolviendo el código para ejecutar en un TransactionCallback. Ver el transaction documentation.

3

En una palabra, Spring JDBCTemplate DriverManagerDataSource no admite el grupo de conexiones. Si desea utilizar el grupo de conexiones, DBCP y C3P0 son buenas opciones.

Vamos a través de JDBCTemplate código fuente para ver qué ...

No importa lo llaman update, queryForObject y otros métodos, que finalmente se llamarán execute método:

@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()); 
     } 
    } 

Se llama DataSourceUtils.getConnection método para obtener conexión y DataSourceUtils.releaseConnection para liberar la conexión.

De DataSourceUtils código fuente, vemos Connection con = dataSource.getConnection(); y con.close();.

Lo que significa que la operación get connection se define implementando la interfaz DataSource, y la operación de conexión cercana se define implementando la interfaz Connection. Esto permite que otras implementaciones de DataSource/Connection se inserten fácilmente en Spring JDBCTemplate.

La implementación DataSource en Spring JDBCTemplate es DriverManagerDataSource. De:

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

Y

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

Vemos cada vez que devuelve una nueva conexión y conexión actual de cerca. Es por eso que no es compatible con el grupo de conexiones.

Mientras que en DBCP, la implementación DataSource es PoolingDataSource, vemos getConnection() es de un grupo de conexión; la implementación Connection es PoolableConnection, vemos que el método close() no es para cerrar la conexión, sino que devuelve la conexión al grupo de conexiones.

¡Esa es la magia!

0

Sé que esto es situacional (dependiendo del conjunto de características que desee utilizar), pero podría simplemente usar los métodos JdbcTemplate.batchUpdate.

+0

Creo que esto es útil para inserciones y actualizaciones , pero no Selects a menos que haya una manera inteligente de obtener resultados a partir de esto ... pero en ese caso, una declaración SQL más definida sería probablemente más eficiente. –

Cuestiones relacionadas