Conexão com o banco de dados - eles devem ser passados ​​como parâmetro?

11

Temos um sistema pelo qual a conexão com o banco de dados é obtida uma vez usando um método comum e sendo transmitida por toda a classe relevante a ser usada. Há dúvidas de que a passagem da conexão com o banco de dados como parâmetro para diferentes classes possa causar problemas, por isso estou verificando aqui se isso é realmente viável e se existem padrões melhores para isso?

Eu sei que existem algumas ferramentas ORM para persistir, mas ainda não podemos entrar nisso.

Qualquer feedback é bem-vindo, obrigado.

ipohfly
fonte
A que tipo de problemas você está se referindo? Quem tem essas dúvidas? (Não é você, eu assumo.)
Greg Hewgill
1
Problemas como fazer com que o desenvolvedor se esqueça de fechar a conexão, geralmente apenas estou tentando ver se é uma boa prática passar a conexão com o banco de dados para vários métodos como parâmetro. Ya as dúvidas vêm de outro desenvolvedor.
ipohfly

Respostas:

8

Sim, é seguro passar por uma conexão. Você lida com a conexão em um bloco de controle externo. Não há nada de inseguro nisso.

O que é inseguro é escrever um código que não garanta que a conexão seja descartada adequadamente em tempo hábil. Esquecer-se de limpar um recurso não tem relação com a distribuição. Você poderia escrever com a mesma facilidade o código que deixa uma conexão interrompida sem transmiti-lo a qualquer lugar.

No C ++, você está protegido pelo RAII se alocar na pilha ou usar ponteiros inteligentes. Em C #, faça uma regra rígida de que todos os objetos descartáveis ​​(como conexões) sejam declarados em um bloco "using". Em Java, limpe com a lógica try-finally. Faça revisões de código em todo o código da camada de dados para garantir isso.

O caso de uso mais comum é quando você tem várias operações que podem ser combinadas em várias permutações. E cada uma dessas permutações precisa ser uma transação atômica (todas com êxito ou reversão). você deve passar a transação (e, portanto, a conexão correspondente) para todos os métodos.

Suponha que tenhamos muitas ações foobar () que podem ser combinadas de várias maneiras como transações 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

BTW, você desejará abrir as conexões o mais tarde possível, descartá-las o mais rápido possível. Seus colegas de equipe podem estar certos se você estiver tratando as conexões como membros do objeto, introduzindo-as como um estado desnecessário e deixando as conexões abertas por muito mais tempo do que o necessário. Mas o ato de passar uma conexão ou transação como parâmetro não é inerentemente errado.

Entre. Dependendo do suporte do seu idioma às funções de primeira classe, você poderá executar uma lista de ações foobar (). Portanto, uma função pode lidar com todas as permutações das ações. Eliminando a duplicação do bloco de controle externo para cada permutação.

mike30
fonte
marcando este como a resposta, uma vez que me dá mais idéia de como a situação é
ipohfly
6

Parece que você está após a injeção de dependência . Ou seja, a conexão em pool é criada uma vez e injetada onde for necessário. Certamente, passar a conexão através de um parâmetro de método é uma maneira de injetar dependência, mas um contêiner de IoC, como Guice, PicoContainer ou Spring, é outra maneira (mais segura) de fazer isso.

O uso do DI significa que você pode agrupar perfeitamente a lógica em torno da criação, abertura, uso e fechamento da conexão - longe da lógica de negócios principal.

Spring JDBC et al. São outros exemplos de como executar esse tipo de comportamento para você

Martijn Verburg
fonte
Emm não está realmente olhando para a injeção de dependência. Apenas tentando descobrir se geralmente é uma boa prática fazer isso, e se não for, qual é a melhor maneira de gerenciar a conexão com o banco de dados (o DI é uma maneira de fazê-lo).
Ipohfly #
-1. Uma conexão não se encaixa em um sistema multiusuário. Pode parecer funcionar devido ao baixo volume do usuário e à rápida execução. Com o pool, é melhor instanciar um objeto de conexão por ação, mesmo em um sistema de usuário único.
mike30
2

Passar coisas do banco de dados em vez de dados pode levar a problemas. Nessa medida, sempre que possível, não passe uma coisa do banco de dados, a menos que se possa garantir uma higiene adequada do banco de dados.

O problema de repassar as coisas do banco de dados é que ele pode ser desleixado. Eu vi mais de um bug no código com alguém passando por uma conexão com o banco de dados, que alguém pega um conjunto de resultados e oculta em um objeto local (o conjunto de resultados ainda conectado ao banco de dados) e amarra um cursor no banco de dados por um tempo significativo. Outra instância em que alguém passou um conjunto de resultados para outra pessoa (que foi ocultada) e, em seguida, o método que passou o conjunto de resultados fechou (e a instrução) levando a erros quando outros métodos tentaram trabalhar com o conjunto de resultados que não era mais.

Tudo isso decorre de não respeitar o banco de dados, a conexão, a instrução, o conjunto de resultados e seus ciclos de vida.

Para evitar isso, existem padrões e estruturas existentes que funcionam mais bem com os bancos de dados e não possuem dados necessários para sair das classes em que estão confinados. Os dados entram, os dados saem e o banco de dados permanece em uso.


fonte
1
As conexões +1 db devem ter o menor tempo possível. Abra, use e feche o mais rápido possível. Atualmente, existem muitas implementações de pool de conexões, portanto, usar uma conexão para várias operações e uma economia falsa. E um convite para erros ou problemas de desempenho (mantendo bloqueios em tabelas, utilizando recursos de conexão)
JQA
Quais são os nomes de alguns desses padrões e estruturas existentes?
Daniel Kaplan
@tieTYT O principal que vem à frente é o objeto de acesso a dados, que serve para ocultar o banco de dados do restante do aplicativo. Veja também Camada de Acesso a Dados e Mapeamento Relacional a Objetos
Quando penso nesses padrões, sinto que eles estão em um nível mais alto de abstração do que o que ele está perguntando. Digamos que você tenha uma maneira de obter Rootum Dao. Mas então você percebe que também quer uma maneira de obter uma Nodesem puxar o Rootobjeto inteiro com ela. Como você faz o RootDao chamar o Nodecódigo do Dao (ou seja, reutilizar), mas verifique se o NodeDao fecha a conexão apenas quando o NodeDao é chamado diretamente e mantém a conexão aberta quando o RootDao é chamado?
Daniel Kaplan
1
Só queria acrescentar que, se você não estiver no modo de confirmação automática, passar uma conexão pode levar a uma situação em que um objeto atualiza o banco de dados e outro objeto (possivelmente não relacionado) obtém a conexão, tem um erro e acaba revertendo as alterações do primeiro objeto. Esses tipos de erros podem ser muito difíceis de depurar.
TMN
2

Passar Connectioninstâncias ao redor geralmente não é um problema, embora na maioria das situações apenas as implementações do DAO devam ter algo a ver com elas. Agora, com o problema de as conexões não serem fechadas após o uso, é realmente fácil de corrigir: o Connectionobjeto precisa ser fechado no mesmo nível em que é aberto, ou seja, no mesmo método. Eu pessoalmente uso o seguinte padrão de código:

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

Dessa forma, garanto que todas as conexões estejam sempre fechadas, mesmo que uma exceção seja lançada dentro do bloco. Na verdade, continuo usando o mesmo padrão Statemente ResultSetinstâncias, e tudo tem sido tranquilo até agora.

Edite 29-03-2018: Conforme indicado pelo usuário1156544 nos comentários abaixo, a partir do Java 7, o uso da construção try-with-resources deve ser favorecido. Utilizando-o, o padrão de código que forneci na minha resposta inicial pode ser simplificado da seguinte forma:

try (final Connection cnx = dataSource.getConnection()) {
    // Operations using the instance
}
KevinLH
fonte
1
Eu uso algo semelhante. Eu tenho a função doInTransaction (tarefa DbTask), onde DbTask é minha interface com o método com parâmetro de conexão. doInTransaction obtém conexão, chama tarefa e confirma (ou retrocede se houver exceção) e fecha essa conexão.
user470365
a julgar pelo seu exemplo, isso significaria que o objeto DataSource é um singleton?
Ipohfly
@ipohfly Na verdade, eu deveria ter nomeado esse objeto em dataSourcevez de DataSource(vou corrigir minha resposta sobre esse ponto). O tipo exato desse objeto seria javax.sql.DataSource. No código antigo, eu costumava ter um singleton gerenciar todas as fontes de dados disponíveis em meus aplicativos. Meus DAOs não precisavam saber disso, pois a DataSourceinstância é fornecida por injeção de dependência.
21813 KevinLH
Se você usar esse esquema, use try-with-resources melhor
user1156544
Quando respondi, ainda não estava usando o Java 7. Mas você está certo de que essa deve ser a maneira preferida atualmente. Atualizarei minha resposta para incluir sua sugestão.
21418 KevinLH
0

existe uma desvantagem em fazer as coisas dessa maneira, em vez de usar um singleton que você pode obter conforme necessário. Eu fiz as coisas nos dois sentidos no passado.

Em geral, você precisa pensar nas consequências do gerenciamento de conexões com o banco de dados, e isso pode ou não ser ortogonal ao uso da consulta ao banco de dados. Por exemplo, se você tiver uma conexão db para uma determinada instância do aplicativo e ela for fechada quando não estiver em uso, isso será ortogonal. Coloque o gerenciamento em uma classe única e não o repasse. Isso permite que você gerencie a conexão db conforme necessário. Por exemplo, se você deseja fechar uma conexão em cada confirmação (e reabrir na próxima chamada), isso é mais fácil em um singleton, porque a API para isso pode ser centralizada.

Por outro lado, suponha que você precise gerenciar um conjunto de conexões em que uma determinada chamada possa precisar usar qualquer conexão arbitrária. Isso pode acontecer ao fazer transações distribuídas em vários servidores, por exemplo. Nesse caso, geralmente é melhor passar o objeto de conexão db do que trabalhar com singletons. Eu acho que esse geralmente é o caso mais raro, mas não há nada de errado em fazer isso quando você precisa.

Chris Travers
fonte