Conexión de base de datos: ¿deben pasarse como parámetro?

11

Tenemos un sistema mediante el cual la conexión de la base de datos se obtiene una vez usando un método común y se pasa a través de la clase relevante que se utilizará. Hay dudas de que pasar la conexión de la base de datos como un parámetro a diferentes clases podría causar problemas, así que verifico aquí para ver si esto es realmente viable, y ¿hay algún patrón mejor para hacerlo?

Sé que hay algunas herramientas ORM para hacer la persistencia, pero aún no podemos entrar en eso ...

Cualquier comentario es bienvenido, gracias.

ipohfly
fuente
¿A qué tipo de problemas te refieres? ¿Quién tiene estas dudas? (No, supongo.)
Greg Hewgill
1
Problemas como hacer que el desarrollador se olvide de cerrar la conexión, generalmente solo estoy tratando de ver si es una buena práctica pasar la conexión de la base de datos a varios métodos como parámetro. Ya las dudas provienen de otro desarrollador.
ipohfly

Respuestas:

8

Sí, es seguro pasar una conexión. Maneja la conexión en un bloque de control externo. No hay nada inseguro al respecto.

Lo que no es seguro es escribir código que no garantice que la conexión se elimine de manera oportuna. Olvidar limpiar un recurso no está relacionado con pasarlo. Podrías escribir fácilmente un código que deja una conexión suspendida sin pasarlo a ningún lado.

En C ++, está protegido por RAII si asigna en la pila o utiliza punteros inteligentes. En C # establezca una regla estricta para que todos los objetos desechables (como las conexiones) se declaren en un bloque "en uso". En Java, limpie con la lógica try-finally. Tenga revisiones de código en todo el código de capa de datos para garantizar esto.

El caso de uso más común es cuando tiene varias operaciones que se pueden combinar en muchas permutaciones. Y cada una de estas permutaciones debe ser una transacción atómica (todas tienen éxito o retroceden). entonces debe pasar la transacción (y por lo tanto la conexión correspondiente) a todos los métodos.

Supongamos que tenemos muchas acciones foobar () que se pueden combinar de varias maneras como transacciones atómicas.

//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

Por cierto, querrá abrir las conexiones lo más tarde posible, deséchelas lo antes posible. Sus compañeros de equipo podrían tener razón si está tratando las conexiones como miembros del objeto, presentándolas como un estado innecesario y dejando las conexiones abiertas mucho más tiempo del necesario. Pero el acto de pasar una conexión o transacción como parámetro no es inherentemente incorrecto.

Por cierto. Dependiendo del soporte de su idioma para funciones de primera clase, puede realizar una lista de acciones foobar (). Entonces, una función podría manejar todas las permutaciones de las acciones. Eliminando la duplicación del bloque de control externo para cada permutación.

mike30
fuente
marcar esto como la respuesta ya que me da más idea de cómo es la situación
ipohfly
6

Parece que buscas la inyección de dependencia . Es decir, la conexión agrupada se crea una vez y se inyecta donde sea necesario. Ciertamente, pasar la conexión a través de un parámetro de método es una forma de inyectar dependencia, pero un contenedor de IoC como Guice, PicoContainer o Spring es otra forma (más segura) de hacerlo.

El uso de DI significa que puede resumir perfectamente la lógica en torno a la creación, apertura, uso y cierre de la conexión, lejos de su lógica comercial central.

Spring JDBC y otros son otros ejemplos de cómo realizar este tipo de comportamiento para usted

Martijn Verburg
fuente
Emm realmente no mira la inyección de dependencia. Solo tratando de averiguar si, en general, es una buena práctica hacerlo, y si no es así, ¿cuál es la mejor manera de administrar la conexión de la base de datos?
ipohfly
-1. Una conexión no se ajusta a un sistema multiusuario. Puede parecer que funciona debido al bajo volumen de usuarios y la ejecución rápida. Con la agrupación, es mejor crear una instancia de un objeto de conexión por acción, incluso en un solo sistema de usuario.
mike30
2

Pasar cosas de la base de datos en lugar de cosas de datos puede generar problemas. En ese sentido, siempre que sea práctico, no pase una cosa de la base de datos a menos que se pueda garantizar la higiene adecuada de la base de datos.

El problema con pasar cosas de la base de datos es que puede ser descuidado. He visto más de un error en el código con alguien que pasa por una conexión de base de datos, que alguien luego toma un conjunto de resultados y lo guarda en un objeto local (el conjunto de resultados, todavía conectado a la base de datos) y luego ata un cursor en el base de datos durante un tiempo significativo. En otra instancia, alguien pasó un conjunto de resultados a otra persona (que luego se escondió) y luego el método que pasó el conjunto de resultados lo cerró (y la declaración) lo que provocó errores cuando otros métodos intentaron funcionar con el conjunto de resultados que ya no existía.

Todo esto se deriva de no respetar la base de datos, la conexión, la declaración, el conjunto de resultados y sus ciclos de vida.

Para evitar esto, existen patrones y estructuras existentes que funcionan mejor con las bases de datos y no tienen elementos de base de datos que necesitan salir de las clases en las que están confinados. Los datos entran, los datos salen, la base de datos permanece en su lugar.


fuente
1
Las conexiones de +1 db deben tener el menor tiempo posible. Ábralo, úselo, ciérrelo lo más rápido posible. Hoy en día hay muchas implementaciones de agrupaciones de conexiones, por lo que usar una conexión para múltiples operaciones es una economía falsa. Y una invitación para errores o problemas de rendimiento (mantener bloqueos en las tablas, usar recursos de conexión)
jqa
¿Cuáles son los nombres de algunos de estos patrones y estructuras existentes?
Daniel Kaplan
@tieTYT Los principales que se destacan son el objeto de acceso a datos que sirve para ocultar la base de datos del resto de la aplicación. Consulte también Capa de acceso a datos y mapeo relacional de objetos
Cuando pienso en esos patrones, siento que están en un nivel de abstracción más alto de lo que está preguntando. Digamos que tienes una manera de obtener Rootun Dao. Pero luego te das cuenta de que también quieres una forma de obtener un Nodesin sacar todo el Rootobjeto con él. ¿Cómo se hace que el RootDao llame al Nodecódigo de Dao (es decir: reutilizar), pero asegúrese de que el NodeDao solo cierre la conexión cuando Nodese llama directamente al Dao y mantenga la conexión abierta cuando Rootse llama al Dao?
Daniel Kaplan
1
Solo quería agregar que si no está en modo de confirmación automática, pasar una conexión podría llevar a una situación en la que un objeto actualiza la base de datos, luego otro objeto (posiblemente no relacionado) obtiene la conexión, tiene un error y termina revertir los cambios del primer objeto. Este tipo de errores puede ser muy difícil de depurar.
TMN
2

Pasar Connectioninstancias no suele ser un problema, aunque en la mayoría de las situaciones solo las implementaciones de DAO deberían tener algo que ver con ellas. Ahora, con el problema de que las conexiones no se cierran después de su uso, en realidad es fácil de solucionar: el Connectionobjeto debe cerrarse al mismo nivel que se abre, es decir, con el mismo método. Yo personalmente uso el siguiente patrón de código:

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

De esa manera me aseguro de que todas las conexiones estén siempre cerradas, incluso si se produce una excepción dentro del bloque. De hecho, uso el mismo patrón Statemente ResultSetinstancias exactamente , y todo ha sido fácil hasta ahora.

Editar 2018-03-29: Como lo indica el usuario 1156544 en los comentarios a continuación, comenzando con Java 7, se debe favorecer el uso de la construcción de prueba con recursos. Al usarlo, el patrón de código que proporcioné en mi respuesta inicial se puede simplificar así:

try (final Connection cnx = dataSource.getConnection()) {
    // Operations using the instance
}
KevinLH
fuente
1
Yo uso algo similar. Tengo la función doInTransaction (tarea DbTask), donde DbTask es mi interfaz con método con parámetro de conexión. doInTransaction obtiene la conexión, llama a la tarea y se compromete (o revierte si hubo una excepción) y cierra esa conexión.
user470365
a juzgar por su ejemplo, ¿significaría que el objeto DataSource es un singleton?
ipohfly
@ipohfly En realidad, debería haber nombrado ese objeto en dataSourcelugar de DataSource(arreglaré mi respuesta con respecto a ese punto). El tipo exacto de ese objeto sería javax.sql.DataSource. En el código anterior, solía tener un singleton que administrara todas las fuentes de datos disponibles dentro de mis aplicaciones. Mis DAO no tenían que saberlo, ya que la DataSourceinstancia se proporciona mediante inyección de dependencia.
KevinLH
Si usa este esquema, use mejor probar con recursos
user1156544
Cuando respondí, todavía no estaba usando Java 7. Pero tienes razón en que esta debería ser la forma preferida en estos días. Actualizaré mi respuesta para incluir su sugerencia.
KevinLH
0

Hay una compensación para hacer las cosas de esta manera en lugar de usar un singleton que puede obtener según sea necesario. He hecho cosas en ambos sentidos en el pasado.

En general, debe pensar en las consecuencias de la administración de la conexión de la base de datos, y esto puede o no ser ortogonal al uso de la consulta de la base de datos. Por ejemplo, si tiene una conexión db para una instancia de aplicación determinada y se cierra cuando no está en uso, eso sería ortogonal. Ponga la gestión en una clase singleton y no la pase. Esto le permite administrar la conexión db según lo necesite. Por ejemplo, si desea cerrar una conexión en cada confirmación (y volver a abrir en la próxima llamada), esto es más fácil de hacer en un singleton porque la API para esto se puede centralizar.

Por otro lado, suponga que necesita administrar un grupo de conexiones donde una llamada dada puede necesitar usar cualquier conexión arbitraria. Esto puede suceder cuando se realizan transacciones distribuidas en varios servidores, por ejemplo. En este caso, es mejor pasar el objeto de conexión db que trabajar con singletons. Creo que este suele ser el caso más raro, pero no hay nada de malo en hacerlo cuando sea necesario.

Chris Travers
fuente