Answers:
是的,绕过连接是安全的。您可以在外部控制块中处理连接。没有什么不安全的。
不安全的是编写不能保证及时正确处理连接的代码。忘记清理资源与传递资源无关。您可以轻松地编写留下悬挂连接而无需传递到任何地方的代码。
在C ++中,如果在堆栈上分配或使用智能指针,则将受到RAII的保护。在C#中,硬性规定所有一次性对象(例如连接)都必须在“使用”块中声明。在Java中,使用try-finally逻辑进行清理。对所有数据层代码进行代码审查以确保这一点。
最常见的用例是当您具有可以组合成许多排列的多个操作时。这些排列中的每一个都必须是原子事务(全部成功或回滚)。那么您必须将事务(以及相应的连接)传递给所有方法。
假设我们有许多foobar()操作,可以将它们以各种方式组合为原子事务。
//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
顺便说一句,您将希望尽快打开连接,并尽快处理它们。如果您将连接视为对象成员,将它们介绍为不必要的状态,并使连接打开的时间比必要的时间长得多,则您的队友可能是正确的。但是,将连接或事务作为参数传递的行为并不是天生的错误。
顺便说一句。根据您的语言对一流函数的支持,您可以列出foobar()操作列表。因此,一个函数可以处理动作的所有排列。消除每个排列的外部控制块的重复。
听起来您好像在依赖注入之后。也就是说,池化连接仅创建一次,并在需要时注入。当然,通过方法参数传递连接是依赖关系注入的一种方法,但是IoC容器(例如Guice,PicoContainer或Spring)是另一种(更安全)的方法。
使用DI意味着您可以围绕连接的创建,打开,使用和关闭整齐地包装逻辑-远离核心业务逻辑。
Spring JDBC等是为您执行这种行为的其他示例
传递数据库事物而不是数据事物会导致问题。在这种情况下,只要可行,除非可以保证适当的数据库卫生,否则不要传递数据库内容。
传递数据库事物的问题在于它可能草率。我已经看到一个人在数据库连接周围传递代码的一个以上错误,即有人随后将结果集抓取并存储在本地对象(结果集,仍连接到数据库)中,然后将游标绑在数据库的时间很长。另一个实例有人将结果集传递给其他人(然后保存),然后传递结果集的方法将其关闭(和该语句),当其他方法尝试不再使用该结果集时会导致错误。
所有这些都源于不尊重数据库,连接,语句,结果集及其生命周期。
为了避免这种情况,现有的模式和结构可以更好地与数据库配合使用,并且不需要从受限的类中脱离数据库内容。数据输入,数据输出,数据库保持原状。
Root
从Dao 上获得一个。但是随后您意识到,您还希望获得一种方法,Node
而不用将其拉出整个Root
对象。如何使Root
Dao调用Node
Dao代码(即重用),但是要确保Node
Dao仅在Node
直接调用Dao 时关闭连接,而在Dao调用时保持连接打开Root
?
Connection
尽管在大多数情况下,只有DAO实现应该与它们有任何关系,但是传递实例通常不是问题。现在,您的问题在于连接在使用后没有关闭,因此实际上很容易修复:Connection
需要以与打开对象相同的级别(即,使用相同的方法)关闭对象。我个人使用以下代码模式:
final Connection cnx = dataSource.getConnection();
try {
// Operations using the instance
} finally {
cnx.close();
}
这样,即使在块中引发异常,我也确保所有连接始终关闭。实际上,只要为Statement
和ResultSet
实例使用完全相同的模式,到目前为止,一切都进展顺利。
编辑2018-03-29:如以下注释中的user1156544所示,从Java 7开始,应优先使用try-with-resources构造。使用它,我在初始答案中提供的代码模式可以简化为:
try (final Connection cnx = dataSource.getConnection()) {
// Operations using the instance
}
dataSource
而不是DataSource
(我将针对这一点解决问题)。该对象的确切类型为javax.sql.DataSource
。在以前的代码中,我曾经让一个人管理我的应用程序中的所有可用数据源。我的DAO不必完全了解这一点,因为DataSource
实例是通过依赖项注入提供的。
要以这种方式做事要折衷,而不是使用可以根据需要获取的单例。我过去做过两种事情。
通常,您需要考虑数据库连接管理的后果,这可能与数据库查询用法正交,也可能不正交。例如,如果给定应用程序实例具有一个数据库连接,并且在不使用该数据库时将其关闭,则该连接将是正交的。将管理置于单例课程中,不要忽略它。这使您可以根据需要管理数据库连接。例如,如果您想在每次提交时都关闭连接(并在下一次调用时重新打开),那么在单例上就更容易完成,因为该API可以集中化。
另一方面,假设您需要管理一个连接池,其中给定的调用可能需要使用任何任意连接。例如,在多个服务器之间进行分布式事务时,可能会发生这种情况。在这种情况下,与使用单例相比,传递数据库连接对象通常要好得多。我认为这通常是很少见的情况,但是在需要时这样做没有任何错误。