Connexion à la base de données - doit-elle être passée en paramètre?

11

Nous avons un système par lequel la connexion à la base de données est établie une fois en utilisant une méthode commune et est transmise dans la classe appropriée à utiliser. Il y a des doutes que le passage de la connexion à la base de données en tant que paramètre à différentes classes causerait un problème, donc je vérifie ici pour voir si cela est réellement viable, et y a-t-il de meilleurs modèles pour le faire?

Je sais qu'il existe des outils ORM pour faire de la persistance, mais nous ne pouvons pas y aller pour l'instant ..

Toute rétroaction est la bienvenue, merci.

ipohfly
la source
De quel genre de problèmes parlez-vous? Qui a ces doutes? (Pas vous, je suppose.)
Greg Hewgill
1
Des problèmes comme le fait que le développeur oublie de fermer la connexion, j'essaie généralement de voir si c'est une bonne pratique de passer la connexion à la base de données à diverses méthodes en tant que paramètre. Ya les doutes viennent d'un autre développeur.
ipohfly

Réponses:

8

Oui, il est possible de contourner une connexion. Vous gérez la connexion dans un bloc de contrôle externe. Il n'y a rien de dangereux à cela.

Ce qui n'est pas sûr, c'est d'écrire du code qui ne garantit pas que la connexion est correctement éliminée en temps opportun. Oublier de nettoyer une ressource n'est pas lié à sa transmission. Vous pouvez tout aussi facilement écrire du code qui laisse une connexion suspendue sans la faire passer n'importe où.

En C ++, vous êtes protégé par RAII si vous allouez sur la pile ou utilisez des pointeurs intelligents. En C #, établissez une règle stricte selon laquelle tous les objets jetables (tels que les connexions) doivent être déclarés dans un bloc "using". En Java, nettoyez avec la logique try-finally. Ayez des revues de code sur tout le code de la couche de données pour vous en assurer.

Le cas d'utilisation le plus courant est lorsque vous avez plusieurs opérations qui peuvent être combinées dans de nombreuses permutations. Et chacune de ces permutations doit être une transaction atomique (toutes réussissent ou annulent). vous devez ensuite transmettre la transaction (et donc la connexion correspondante) à toutes les méthodes.

Supposons que nous ayons de nombreuses actions foobar () qui peuvent être combinées de diverses manières en tant que transactions atomiques.

//example in C#
//outer controlling block handles clean up via scoping with "using" blocks.
using (IDbConnection conn = getConn())
{
    conn.Open();
    using (IDbTransaction tran = conn.BeginTransaction())
    {
        try
        {//inner foobar actions just do their thing. They don't need to clean up.
            foobar1(tran);
            foobar2(tran);
            foobar3(tran);
            tran.Commit();
        }
        catch (Exception ex)
        { tran.Rollback(); }
    }
}//connection is returned to the pool automatically

BTW, vous voudrez ouvrir les connexions le plus tard possible, les éliminer dès que possible. Vos coéquipiers pourraient avoir raison si vous traitez les connexions en tant que membres d'objet, en les présentant comme un état inutile et en laissant les connexions ouvertes beaucoup plus longtemps que nécessaire. Mais l'acte de passer une connexion ou une transaction en tant que paramètre n'est pas intrinsèquement mauvais.

BTW. Selon la prise en charge de votre langue pour les fonctions de première classe, vous pouvez effectuer une liste d'actions foobar (). Une seule fonction peut donc gérer toutes les permutations des actions. Élimination de la duplication du bloc de contrôle externe pour chaque permutation.

mike30
la source
marquer cela comme la réponse car cela me donne plus d'idée sur la situation
ipohfly
6

Il semble que vous soyez après l' injection de dépendance . Autrement dit, la connexion groupée est créée une fois et injectée partout où cela est nécessaire. La connexion via un paramètre de méthode est certainement une façon d'injecter des dépendances, mais un conteneur IoC tel que Guice, PicoContainer ou Spring est une autre manière (plus sûre) de le faire.

L'utilisation de DI signifie que vous pouvez parfaitement résumer la logique autour de la création, de l'ouverture, de l'utilisation et de la fermeture de la connexion, loin de votre logique métier principale.

Spring JDBC et al sont d'autres exemples de ce type de comportement pour vous

Martijn Verburg
la source
Emm ne regarde pas vraiment l'injection de dépendance. J'essaie simplement de savoir si c'est généralement une bonne pratique de le faire, et si ce n'est pas le cas, quelle est la meilleure façon de gérer la connexion à la base de données (DI est cependant une façon de le faire).
ipohfly
-1. Une connexion ne convient pas à un système multi-utilisateur. Il peut sembler fonctionner en raison du faible volume d'utilisateurs et de l'exécution rapide. Avec la mise en commun, il est préférable d'instancier un objet de connexion par action, même dans un système mono-utilisateur.
mike30
2

Le fait de contourner des éléments de base de données plutôt que des éléments de données peut entraîner des problèmes. Dans cette mesure, chaque fois que cela est possible, ne passez pas une chose de base de données à moins que l'on puisse garantir une bonne hygiène de la base de données.

Le problème avec la transmission des éléments de la base de données est qu'elle peut être bâclée. J'ai vu plus d'un bogue dans le code avec quelqu'un passant autour d'une connexion à la base de données, que quelqu'un saisit ensuite un jeu de résultats et se cache dans un objet local (le jeu de résultats, toujours connecté à la base de données), puis attache un curseur dans le base de données pendant un temps significatif. Une autre instance, quelqu'un a passé un jeu de résultats à quelqu'un d'autre (qui a ensuite été caché), puis la méthode qui a passé le jeu de résultats l'a fermé (et l'instruction), ce qui a entraîné des erreurs lorsque d'autres méthodes ont essayé de travailler avec le jeu de résultats qui ne l'était plus.

Tout cela découle du non-respect de la base de données, de la connexion, de l'instruction, du jeu de résultats et de leur cycle de vie.

Pour éviter cela, il existe des modèles et des structures qui fonctionnent mieux avec les bases de données et qui n'ont pas besoin de bases de données pour sortir des classes dans lesquelles ils sont confinés. Les données entrent, les données sortent, la base de données reste en place.


la source
1
Les connexions +1 db doivent avoir la durée la plus courte possible. Ouvrez-le, utilisez-le, fermez-le le plus rapidement possible. De nos jours, il existe de nombreuses implémentations de pool de connexions, donc l'utilisation d'une connexion pour plusieurs opérations est une fausse économie. Et une invitation pour les bugs ou les problèmes de performances (maintien des verrous sur les tables, utilisation des ressources de connexion)
jqa
Quels sont les noms de certains de ces modèles et structures existants?
Daniel Kaplan
@tieTYT Le principal qui vient au premier plan est l' objet d'accès aux données qui sert à cacher la base de données du reste de l'application. Voir aussi la couche d'accès aux données et le mappage relationnel-objet
Quand je pense à ces modèles, je sens qu'ils sont à un niveau d'abstraction plus élevé que ce qu'il demande. Disons que vous avez un moyen d'obtenir Rootun Dao. Mais alors vous réalisez que vous voulez également un moyen d'obtenir un Nodesans retirer tout l' Rootobjet avec. Comment faites-vous pour que le RootDao appelle le Nodecode Dao (c'est-à-dire: réutilisation), mais assurez-vous que le NodeDao ne ferme la connexion que lorsque le NodeDao est directement appelé et maintient la connexion ouverte lorsque le RootDao est appelé?
Daniel Kaplan
1
Je voulais juste ajouter que si vous n'êtes pas en mode de validation automatique, le fait de passer une connexion pourrait conduire à une situation où un objet met à jour la base de données, puis un autre objet (éventuellement non lié) obtient la connexion, a une erreur et se termine annuler les modifications du premier objet. Ces types d'erreurs peuvent être très difficiles à déboguer.
TMN
2

La transmission d' Connectioninstances n'est généralement pas un problème, même si dans la plupart des situations, seules les implémentations DAO devraient avoir quelque chose à voir avec elles. Maintenant, votre problème étant que les connexions ne sont pas fermées après utilisation, il est en fait facile à résoudre: l' Connectionobjet doit être fermé au même niveau qu'il est ouvert, c'est-à-dire dans la même méthode. J'utilise personnellement le modèle de code suivant:

final Connection cnx = dataSource.getConnection();
try {
    // Operations using the instance
} finally {
    cnx.close();
}

De cette façon, je m'assure que toutes les connexions sont toujours fermées, même si une exception est levée dans le bloc. En fait, je vais aussi longtemps que j'utilise exactement le même modèle pour Statementet les ResultSetinstances, et tout s'est bien déroulé jusqu'à présent.

Edit 2018-03-29: Comme indiqué par user1156544 dans les commentaires ci-dessous, à partir de Java 7, l'utilisation de la construction try-with-resources doit être privilégiée. En l'utilisant, le modèle de code que j'ai fourni dans ma réponse initiale peut être simplifié comme suit:

try (final Connection cnx = dataSource.getConnection()) {
    // Operations using the instance
}
KevinLH
la source
1
J'utilise quelque chose de similaire. J'ai la fonction doInTransaction (tâche DbTask), où DbTask est mon interface avec la méthode avec le paramètre de connexion. doInTransaction obtient la connexion, appelle la tâche et la validation (ou l'annulation s'il y avait une exception) et ferme cette connexion.
user470365
à en juger par votre exemple, cela signifierait que l'objet DataSource est un singleton?
ipohfly
@ipohfly En fait, j'aurais dû nommer cet objet dataSourceplutôt que DataSource(je vais corriger ma réponse concernant ce point). Le type exact de cet objet serait javax.sql.DataSource. Dans l'ancien code, j'avais l'habitude d'avoir un singleton pour gérer toutes les sources de données disponibles dans mes applications. Mes DAO n'avaient pas besoin de le savoir, car l' DataSourceinstance est fournie via l'injection de dépendance.
KevinLH
Si vous utilisez ce schéma, utilisez mieux try-with-resources
user1156544
À l'époque où j'ai répondu, je n'utilisais pas encore Java 7. Mais vous avez raison, ce devrait être la voie préférée de nos jours. Je mettrai à jour ma réponse pour inclure votre suggestion.
KevinLH
0

il y a un compromis à faire les choses de cette façon plutôt que d'utiliser un singleton que vous pouvez obtenir au besoin. J'ai fait les choses dans les deux sens dans le passé.

En général, vous devez réfléchir aux conséquences de la gestion de la connexion à la base de données, et cela peut être orthogonal ou non à l'utilisation des requêtes de base de données. Par exemple, si vous avez une connexion db pour une instance d'application donnée et qu'elle se ferme lorsqu'elle n'est pas utilisée, ce serait orthogonal. Mettez la gestion dans une classe singleton et ne la passez pas. Cela vous permet de gérer la connexion db selon vos besoins. Par exemple, si vous souhaitez fermer une connexion à chaque validation (et la rouvrir lors du prochain appel), cela est plus facile à faire sur un singleton car l'API pour cela peut être centralisée.

Par contre, supposons que vous ayez besoin de gérer un pool de connexions où un appel donné peut avoir besoin d'utiliser n'importe quelle connexion arbitraire. Cela peut se produire lors de l'exécution de transactions distribuées sur plusieurs serveurs, par exemple. Dans ce cas, il vaut généralement mieux passer l'objet de connexion db que vous ne travaillez avec des singletons. Je pense que c'est généralement le cas le plus rare, mais il n'y a rien de mal à le faire quand vous en avez besoin.

Chris Travers
la source