一个不安分的JDBC驱动
连接, 连接, 总是连接!
生活中肯定有比数据库连接更有趣的事情。
1
数据库连接
又到了数据库连接的时间!
那些码农把数据库参数送过来, Oracle , Db2, Sybase, SQL Server这些JDBC Driver 懒洋洋起来去干活赚钱。
小东也是其中之一, 每天的工作就是连接Mysql 数据库, 发出SQL查询, 获取结果集。
工作稳定, 收入不菲, 只是日复一日,年复一年, 枯燥的工作实在是太令人厌烦了。
有时候小东会和其他JDBC Driver 聊天, 谈起有些不着调的码农, 创建一个Connection, 发出一个查询, 处理完ResultSet后 , 立刻就把Connection给关掉了。
“他们简直不知道我们建立一个数据连接有多么辛苦, 先通过Socket 建立TCP连接, 然后还要有应用层的握手协议, 唉, 不知道要干多少脏活累活, 这帮码农用完就关, 真是浪费啊。 ”
“还有更可气的, 有些家伙使用了PreparedStatement , 我通知数据库做了预编译, 制定了查询计划, 为此我还花费了不菲的小费。 但是只执行了一次查询, Connection就关掉了, PreparedStatement 也就不可用了, 现在数据库都懒的给我做预编译了 !”
“你们这都是好的, 有些极品根本就不关闭Connection, 最后让这个Connection 进入了不可控状态。 ”
“我们啊, 都把宝贵的生命都献给了数据库连接事业...... ”
抱怨归抱怨, 大部分人都安于现状,逆来顺受了。
2
向Tomcat取经
但是不安分的小东决心改变, 他四处拜访取经, 但是一无所获。
这一天在Tomcat村遇到了Tomcat 村长, 看到了村长处理Http请求的方式, 突然间看到了曙光。
村长说: 我们本来是一个线程处理一个Http请求 , 一个Http请求来到我们这里以后, 我并不会新建一个线程来处理, 而是从一个小黑屋叫来一个线程直接干活, 干完活以后再回到小黑屋待着。
小东问: 小黑屋是什么?
(码农翻身注: 参见文章《我是一个线程》)
村长说: “学名是线程池, 为了充分利用资源, 我在启动时就建立了一批线程, 放到线程池里, 需要线程时直接去取就可以了。 ”
“那要是线程池里的线程都被派出去了怎么办 ? ”
"要么新创建线程, 要么新来的Http请求就要等待了。 实际上,线程也不是无限可用的资源, 也得复用。"
小东心想, 我们JDBC也可以这么搞啊, 把数据库连接给缓存下来, 随用随取, 一来正好可以控制码农们无限制的连接数据库; 二来可以减少数据库连接时间; 第三还可以复用Connection上的PreparedStatement, 不用老是找数据库预编译了。
3
数据库连接池
建立数据库连接池不是那么一帆风顺的, 小东的第一次尝试是创建了一个ConnectionPool这个接口:
里边有两个重要的方法, getConnection(), 用于从池中取出一个让用户使用;
releaseConnection() 把数 46 32696 46 15288 0 0 3149 0 0:00:10 0:00:04 0:00:06 3149据库连接放回池中去。
小东想, 只要我写一个ConnectionPool的实现类, 里边可以维护一个管理数据库连接的数据结构就行了, 码农们用起来也很方便, 他们肯定会喜欢的。
可是事与愿违, 几乎没有人用这样的接口。
小东经过多方打探才明白, 码农们要么是用DriverManager来获得Connection, 要么是使用DataSource来得到Connection;关闭的时候,只需要调用Connection.close() 就可以了。
这样的代码已经有很多了, 而小东的新方案相当于新的接口, 需要改代码才能用, 话说回来, 谁愿意没事改代码玩? 尤其是正在运行的系统。
再做一次改进吧, 小东 去找Java 这个设计高手求教。
Java 说:“虽然ConnectionPool概念不错, 但是具体的实现最好隐藏起来, 对码农来说,还是通过DataSource 来获取Connection, 至于这个Connection 是新建的还是从连接池中来的, 码农不应该关心, 所以应该加一个代理Proxy,把物理的Connection给封装起来, 然后把这个Proxy返回给码农。”
“那这个Proxy是不是得和您定义的接口Connection 接口保持一致? 要不然码农还得面对新东西。”
“是的, 这个Proxy 应该也实现JDBC的Connection 接口, 像这样: ”
(点击看大图)
小东说: ”奥, 我明白了, 当码农从DataSource中获得Connection的时候, 我返回的其实是一个ConnectionProxy , 其中封装了一个从ConnectionPool来的Connection , 然后把各种调用转发到这个实际的physicalConn的方法去, 关键点在close, 并不会真的关闭Connection, 而是放回到ConnectionPool “
“哈哈, 看来你已经get了, 这就是面向接口编程的好处啊, 你给码农返回了一个ConnectionProxy, 但是码农们一无所知, 仍然以为是在使用Connection , 这是一次成功的‘欺骗’啊”
“但是你定义的Connection 接口中的方法实在是太多了, 足足有50多个, 我这个Proxy类实际上只关注那么几个, 例如close方法, 其他的都是转发而已,这么多无聊的转发代码是在是太烦人了”
Java说: “还有一种办法,可以用动态代理啊”
小东问:“什么是动态代理?”
"刚才我们提供的那个Proxy可以称为静态代理, 我的动态代理不用你写一个类去实现Connection, 完全可以在运行期来做, 还是先来看代码吧"
(点击看大图)
“代码有点难懂, 你看,这里没有声明一个实际的类来实现Connection 接口 , 而是用动态代理在运行时创建了一个类Proxy.newProxyInstance(....) , 重点部分就是InvocationHandler, 在他的invoke方法中, 我们判断下被调用的方法是不是close, 如果是, 就把Connection 放回连接池, 如果不是,就调用实际Connection的方法。” Java 解释了一通。
小东惊叹到:“代码虽然难懂, 但是精简了好多,我对Java 反射不太熟, 回头我再仔细研究下。”
(码农翻身注: 不熟悉Java动态代理的也可以研究下, 这是一项重要的技术)
经过一番折腾, 数据库连接池终于隐藏起来了, 码农们可以使用原有的方式去获取Connection, 只是不知道背后其实发生了巨变。
当然也不可能让码农完全意识不到连接池, 毕竟他们还得去设置一些参数, 小东定义了一些:
数据库连接池获得了巨大的成功, 几乎成了每一个Java Web项目的标配, 不一样的JDBC驱动小东也获得了极高的荣誉, 后面等着他的还会有哪些挑战呢?
(完)
码农翻身相关历史文章推荐:
Java EE
计算机网络
数据库
你看到的只是冰山一角, 更多精彩文章,尽在“码农翻身” 微信公众号, 回复消息"m"或"目录" 查看更多文章
有心得想和大家分享? 欢迎投稿 ! 我的联系方式:微信:liuxinlehan QQ: 3340792577
公众号:码农翻身
“码农翻身”公众号由工作15年的前IBM架构师创建,分享编程和职场的经验教训。