什么是光池?
BlueSky 上的一篇帖子中的这个简单问题让我得到了一个我认为非常酷的解释。我来这里是为了完成它。
在具体的上下文中,正在谈论 Hikari 连接池。但是,如果 Hikari 是一个连接池,那么“池”会是什么?
在解释什么是 HikariCP 之前,我们需要先解释一下连接池是什么。为了解释连接池,我们需要解释池。
我们可以用经济学来类比吗?这是一个与现实世界充满缺陷和不准确的历史经济类比,但是来吧,为了解释而迅速停止你的怀疑!它是独立的。
想象一下您是中世纪时代的领主/女士。你拿着工具来进行农民的工作。并且您希望它们能够工作。那么如何保证这一点呢?如果工具是你的?你需要给农民提供工具,很简单。
所以想象一下这种情况:你的农民需要一把锄头来除草,所以他去那里向你要一把锄头。你会给他锄头并继续生活。但如果他不归还,他的锄头怎么办?总有一天会结束的……
交出锄头的另一种选择是制作锄头。您是这些土地的领主/女士,因此您可以联系铁匠,将金属熔炼成锄头的形状并将其安装到手柄中。但这不是你可以在没有农民坐在候诊室的情况下立即生产的东西。为了实现这个新功能,您需要大量的时间和精力。
现在,如果农民在一天结束时归还锄头,第二天它就可以供其他农民使用。
在这里,您正在控制锄头池。 池是一种设计模式,指示您可以执行以下操作:
对象池中的其他常见内容:
那么,让我们离HikariCP更近一些吧。这里我们来谈谈Java中的数据库连接。
在java中,我们要求建立与数据库的连接。有直接连接选项,您需要直接了解要调用哪些类以及一些细节,或者只是享受服务发现选项。
先验地,为了使用服务发现,服务提供者会提供一种方法来注册他所提供的内容,然后“服务发现”会跟踪它以查看谁可以服务该请求。
我曾经遇到过需要建立 JDBC 连接来与数据库通信的情况。但是,我的 JDBC 驱动程序不接受使用 null 作为值,只接受直接在查询中使用 null。那么我做了什么?司机顶司机!
总体思路如下。想象一下,我有一个查询,我想将值插入到:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?)
现在想象一下我正在处理这个值第一次插入到银行中。为此,我需要将其保留为 ID=1、CONTENT=first 和 PARENT=null,因为毕竟没有这样的父记录(毕竟它是第一个)。
自然会做什么:
try (final var pstmt = conn.prepareStatement( """ INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?) """)) { pstmt.setInt(1, 1); pstmt.setString(2, "first"); pstmt.setNull(3, Types.INTGEGER); // java.sql.Types pstmt.executeUpdate(); // de fato altere o valor }
我想继续这样使用它,毕竟这是惯用的使用方式。根据丘比特的说法,“I”来自“惯用语”。拥有惯用代码的想法正是为了“减少不必要的心理负担”。
为了解决这个问题,我的选择是:将prepareStatement留到executeUpdate之前的最后一刻。因此,我存储了要应用的所有空值,当我意识到我实际上需要一个空值时,我运行字符串替换并生成一个新查询,并且这个新查询将实际执行。
在这种情况下,我从:
开始
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?)
所以,我必须输入这些值:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?) -- 1, 'first', NULL
但我实际上无法使用 null,所以我创建了一个键来标识第三位是 null:
-- (value, value, NULL) INSERT INTO some_table (id, content, parent) VALUES (?, ?, NULL) -- 1, 'first'
在这种情况下,我准备这个新字符串并根据要求放置参数。
好吧,也就是说,我如何向我的应用程序表明我需要使用 JDBC 驱动程序?我如何注册?
相关项目是 Pstmt Null Safe。基本上,Java 类加载器有一个神奇之处,即加载 jar 时,它会查找名为 META-INF 的元数据文件夹。对于 JDBC 驱动程序,META-INF/services/java.sql.Driver,我用实现 java.sql.Driver 的类记录了它:br.com.softsite.pstmtnullsafe.jdbc.PstmtNullSafeDriver。
根据 java.sql.Driver 文档,每个驱动程序都应该创建自己的实例并向 DriverManager 注册。我是这样实现的:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?)
静态块自行加载。我们如何知道哪个连接应该由我的驱动程序管理?该调用是通过 DriverManager#getConnection(String url) 进行的。我们有 URL 来询问驱动程序是否接受连接。约定(这里又是惯用的使用方式)是将其作为 URL 方案的前缀。因为我希望我的驱动程序连接到另一个驱动程序之上,所以我使用了以下方案:
try (final var pstmt = conn.prepareStatement( """ INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?) """)) { pstmt.setInt(1, 1); pstmt.setString(2, "first"); pstmt.setNull(3, Types.INTGEGER); // java.sql.Types pstmt.executeUpdate(); // de fato altere o valor }
因此,为了执行测试,我连接了 SQLite,并使用 Xerial 指示器通过连接 URI 请求内存中连接:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?)
为了“封装”连接,我的约定表明我不重复 jdbc:,所以:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?) -- 1, 'first', NULL
剖析上面的 URI:
-- (value, value, NULL) INSERT INTO some_table (id, content, parent) VALUES (?, ?, NULL) -- 1, 'first'
好的,你如何表明这一点?如果我可以打开连接,Driver#acceptsURL 必须返回 true。我可以这样做:
public static final PstmtNullSafeDriver instance; static { instance = new PstmtNullSafeDriver(); try { DriverManager.registerDriver(instance); } catch (SQLException e) { e.printStackTrace(); } }
但是如果我尝试加载不存在的驱动程序,这表明什么?没什么,下次会出问题的。这并不好,理想的情况是从一开始就崩溃。因此,为此,我将尝试从下面加载驱动程序,如果不能,我将返回 false:
jdbc:pstmt-nullsafe:<url de conexão sem jdbc:> \__/ \____________/ | | | Nome do meu driver Padrão para indicar JDBC
实际的驱动程序代码还有一些与此处讨论的 HikariCP、DataSource、JDBC 或本文讨论的主题无关的要点。
因此,当请求与 DriverManager 的“空安全”连接时,它首先找到我的驱动程序,然后我的驱动程序递归地尝试检查是否有可能在后台建立连接。确认有一个驱动程序能够处理这个问题,我说是的,这是可能的。
Connection 接口实现了 AutoCloseable 接口。这意味着您获取连接,根据需要使用该连接,然后关闭该连接。对此使用一些间接连接是非常标准的,或者,如果您直接使用连接,请在 try-with-resources:
块中使用它
jdbc:sqlite::memory:
现在,创建连接的过程是一个昂贵的过程。而且服务发现过程并不是完全免费的。因此,理想的情况是保存驱动程序,然后生成连接。让我们一点一点地开发它。
首先,我们需要有一个可以通过驱动程序启动的对象。它可以很容易地是一个全局对象、一个注入的 Spring 组件或类似的东西。我们称之为 JdbcConnector:
jdbc:pstmt-nullsafe:sqlite::memory:
getJdbcConnection() 的一种可能实现是依赖于该函数包含的状态:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?)
到目前为止一切都很顺利。但是……还记得最初的例子吗?农民在工具池中索要一把锄头?那么……我们要考虑到这一点吗?我们可以将连接返回到池,而不是实际关闭连接。为了正确性,我会防止多个同时访问,但我不会担心这里的效率。
我们假设我有一个名为 ConnectionDelegator 的类。它实现了所有 Connection 方法,但它自己不执行任何操作,它仅委托给作为构造函数传递给它的连接。例如,对于 isClosed():
方法
try (final var pstmt = conn.prepareStatement( """ INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?) """)) { pstmt.setInt(1, 1); pstmt.setString(2, "first"); pstmt.setNull(3, Types.INTGEGER); // java.sql.Types pstmt.executeUpdate(); // de fato altere o valor }
其他方法依此类推。它是抽象的,因为一个简单的事实是,当我使用它时,我想强迫自己做一些除了简单委托之外的事情。
好吧,我们走吧。这个想法是,将请求连接,该连接可能存在也可能不存在。如果存在,我将其包装在这个新类中,以便在关闭连接时将其返回到 pool。嗯,所以我要在 close() 方法中做一些事情...好吧,我们先包装一下。让我们将 getConnection() 保留为同步以避免并发问题:
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?)
好的,如果我的连接池中有元素,我会使用它们直到它为空。但它永远不会被填满!那么我们要解决这个问题吗?当它关闭时,我们可以将其返回池!
INSERT INTO some_table (id, content, parent) VALUES (?, ?, ?) -- 1, 'first', NULL
好的,现在当您使用完连接后,它会被发送回
泳池。这不满足 Connection#close() 方法的文档,因为在文档中它提到它释放与此连接相关的所有 JDBC 资源。这意味着我需要保留所有语句、结果集、PreparedStatements 等的记录。我们可以通过在 ConnectionDelegator 上创建一个名为 closeAllInnerResources() 的受保护方法来处理此问题。并在 close() 中调用它:
-- (value, value, NULL) INSERT INTO some_table (id, content, parent) VALUES (?, ?, NULL) -- 1, 'first'
这样我们就可以根据需要向我返回连接,并且能够形成资源池。
你知道Java给提供连接的对象起什么名字吗?数据源。您知道 Java 对 DataSource 还说了什么吗?从概念上讲,有一些类型。在这些类型中,最相关的 2 种是:
这里我们经历了始终创建连接(基本类型)以及演变为数据源的过程
合并。
HikariCP 是一个数据源。具体来说,是一个池化数据源。但他有一个特点:他是所有人中速度最快的。为了保证这个速度,在应用程序生命周期中使用的连接池中,HikariCP 制定了一个秘密:它已经创建了所有可用的连接。因此,当 getConnection 到达时,HikariCP 只需要检查连接池。
如果你想更深入地研究这个主题,你可以查看 Baeldung 上有关该主题的这篇文章,也可以查看 github 上的存储库。
以上是什么是光池?的详细内容。更多信息请关注PHP中文网其他相关文章!