web-dev-qa-db-fra.com

Comment réutiliser la même connexion avec un JdbcTemplate d'un ressort?

J'ai le code suivant:


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

Je m'attends à ce que les deux lignes execute () réutilisent la même connexion. Cependant, la sortie du journal indique:

 2011-02-10 12:24:17 DriverManagerDataSource [INFO] Pilote JDBC chargé: org.h2.Driver 
 2011-02-10 12:24:17 JdbcTemplate [DEBUG] Exécution d'une instruction SQL [SELECT 1] 
 2011-02-10 12:24:17 DataSourceUtils [DEBUG] Récupération de la connexion JDBC à partir de DataSource 
 2011-02-10 12:24:17 DriverManagerDataSource [DEBUG] Création d'une nouvelle connexion JDBC DriverManager vers [jdbc: h2: /data/h2/testa]
2011-02-10 12:24:17 DataSourceUtils [DEBUG] Retour de la connexion JDBC à DataSource 
 2011-02-10 12:24:17 JdbcTemplate [DEBUG] Exécution d'une instruction SQL [ SELECT 1] 
 2011-02-10 12:24:17 DataSourceUtils [DEBUG] Récupération de la connexion JDBC à partir de DataSource 
 2011-02-10 12:24:17 DriverManagerDataSource [DEBUG] Création d'une nouvelle connexion JDBC DriverManager avec [jdbc : h2:/data/h2/testa] 
 2011-02-10 12:24:17 DataSourceUtils [DEBUG] Retour de la connexion JDBC à DataSource 

L'exemple ci-dessus est assez rapide, mais j'ai un morceau de code plus gros qui fait la même chose et reste bloqué longtemps sur Creating new JDBC DriverManager Connection. Je n'ai jamais d'erreur, mais le code est exécuté très lentement. Puis-je en quelque sorte refactoriser le code ci-dessus pour utiliser simplement la même connexion?

Merci

23
User1

Voici un exemple utilisant 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 sortie log4j est: -

[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
19
limc

Spring fournit un DataSource spécial qui vous permet de le faire: SingleConnectionDataSource

Changer votre code pour ceci devrait faire l'affaire:

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

Pour une utilisation dans des applications multithreads, vous pouvez rendre le code réentrant en empruntant une nouvelle connexion du pool et en l'enveloppant dans la section de code gourmande en base de données:

// ... 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
}
24
Axel Fontaine

En regardant le code du printemps c'est ma compréhension à un niveau élevé. 

Vous créez un DriverManagerDataSource . Ceci utilise en interne DataSourceUtils pour obtenir une connexion. Et il ne réutilise la connexion que si une transaction active est en cours. Donc, si vous exécutez les deux exécutions dans une transaction unique, il utilisera la même connexion. Vous pouvez également utiliser le regroupement avec 1 connexion de sorte qu'une seule connexion soit créée et réutilisée.

4
Aravind R. Yarram

Vous avez besoin que les appels soient emballés dans une transaction unique. En règle générale, vous le feriez avec l'annotation AOP + @Transactional de Spring dans une application. Vous pouvez également le faire par programme avec une PlatformTranactionManager, une TransactionTemplate et encapsuler le code à exécuter dans une TransactionCallback. Voir la documentation de la transaction .

4
ColinD

Dans un mot, Spring JDBCTemplate DriverManagerDataSource ne prend pas en charge le pool de connexions. Si vous souhaitez utiliser le pool de connexions, DBCP et C3P0 sont des choix judicieux.

Passons en revue le code source JDBCTemplate pour voir pourquoi ...

Peu importe l'appel update, queryForObject et d'autres méthodes, ils vont finalement appeler la méthode 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());
        }
    }

Il appelle la méthode DataSourceUtils.getConnection pour obtenir la connexion et DataSourceUtils.releaseConnection pour libérer la connexion.

Dans DataSourceUtils code source, nous voyons Connection con = dataSource.getConnection(); et con.close();.

Ce qui signifie que l'opération de connexion est définie par l'implémentation de l'interface DataSource et que l'opération de connexion fermée est définie par l'implémentation de l'interface Connection . Cela permet à d'autres implémentations DataSourceConnection d'injecter facilement dans Spring JDBCTemplate. 

L'implémentation DataSource dans Spring JDBCTemplate est _/DriverManagerDataSource . De:

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

Et

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

On voit à chaque fois qu'il retourne une nouvelle connexion, et ferme la connexion en cours. C'est pourquoi il ne prend pas en charge le pool de connexion. 

Alors que dans DBCP, l'implémentation DataSource est PoolingDataSource , nous voyons que getConnection() provient d'un pool de connexions; l'implémentation Connection est PoolableConnection , nous voyons que la méthode close() ne permet pas de fermer la connexion, mais renvoie la connexion au pool de connexions.

C'est la magie!

4
coderz

Je sais que cela est lié à la situation (en fonction du jeu de fonctionnalités que vous souhaitez utiliser), mais vous pouvez simplement utiliser les méthodes JdbcTemplate.batchUpdate.

0
skirsch