1. 为什么需要JDBC?
在JDBC(Java DataBase Connectivity)没有出现的时候,我们是如何连接数据库的,遇到了什么困境?
- 首先要理解数据库是运行在电脑上的一个独立的应用程序;
- 所以应用程序是需要通过网络通信协议与数据库进行命令交换的,eg:如应用程序发出了一个增删改查的命令,那么这个命令就需要通过一次网络连接将该命令传送给数据库,让数据库进行相应的操作,并返回相应的结果。
- 为了简便开发,将程序员从繁琐的与数据库命令交互底层的操作解放出来,所以数据库提供商一般都会将其封装成API对外暴露功能:eg:
//根据ip地址,用户名与密码与数据库建立连接,这里没有端口是因为每个数据库提供商都会有自己的一套程序,所以每套程序中自然已经写好对应的
端口
SqlConnection connection = new SqlConnection("localhost", "root", "123456");
//获取到对应的表空间
connection.selectDB("user");
//将应用程序的命令通过API传递给数据库
SqlQuery query = connection.query("SELECT * FROM userTable");
可以看到,一切繁琐的功能,如建立与数据库的连接只需要new一个SqlConnection对象即可,传递命令只需要调用query方法即可;
- 但这样的命令交换自然会遇到一个困境,即数据库有很多种,Oracle,MySQL等,不同的数据库通常会有不同的通信协议,那么当一个应用程序涉及到几种库的时候,就不得不按照不同产商的API写出几套连接程序来;而API的不同又导致了另一个问题,即每个API的实现方式不同,因为涉及到网络连接,API可能会依赖与操作系统的相关功能,那么需要换操作系统时,可能需要更换支持对应操作系统的API;这个是绝对无法容忍的!
- 问题的关键在于: 程序员无法通过一致的API去操作数据库,而JDBC的出现解决了这个问题,它提供了一致的编码规范,无论是哪一种数据库,在应用程序层面,都是通过一致的API去访问:
JDBC驱动程序即为JDBC Driver,而JDBC驱动程序则是由相应的数据库厂商实现,每个数据库都会有自己的Driver,如下图所示:
下面的连接方法我想大家都已经很熟悉了: public void connect() {
//用于确定DriverManager会加载哪一个驱动程序,DriverManager是用于管理驱动程序的类
String jdbcUrl = "jdbc:h2:tcp://localhost";
String username = "root";
String password = "123456";
try {
//加载h2数据库驱动程序,这里会调用org.h2.Driver的static方法,将该驱动程序实例注册到DriverManager
Class.forName("org.h2.Driver");
//通过驱动程序连接到数据库
Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
//这个是sql语句代表对象
Statement statement = connection.createStatement();
ResultSet result = statement.executeQuery("SELECT * FROM userTable");
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
} catch (SQLException e2) {
e2.printStackTrace();
}
}
可能会有疑问,为什么没有把连接关闭呢?是因为从JDK7之后,Connection等接口都是AutoCloseable的子接口,所以无需手动关闭。
2. 为什么需要数据库连接池?
思考这个问题不妨从JDBC有什么问题出发,其中最明显也最致命的一点就是每次连接都需要建立一次Connection,而如果在一个大型系统中,并且对数据库的访问比较频繁的情况下,多次建立TCP连接的代价还是比较高的,那能不能不那么频繁的建立TCP连接呢?最简洁明了的解决方法就是复用,显然数据库连接池是通过这个方法解决了JDBC的问题;
2.1 数据库连接池:
2.1.1 基本原理:
维护一个资源池,池中存储着一定数量的数据库连接,并通过方法对外暴露对于连接的获取(getConnection())与释放(releaseConnection(),释放连接并不是关闭连接,只是将这个连接重新放入资源池中)操作。
2.1.2 连接池带来的好处(与线程池的好处有很多相像的地方):
- 连接资源的重用,不再需要每次对数据库进行指令交换时,便会进行一次建立连接与关闭连接的操作;
- 连接的创建与连接的使用解耦合,使用者只需要调用getConnection()即可,而不需要再关心这个连接是怎么建立的(如之后改变了连接建立的参数,比如说数据库的ip地址变化了,就不再需要改变业务代码了);
- 更快的响应速度,因为连接在系统初始化的时候便放入连接池中,应用执行SQL语句时不再需要等待建立连接;
- 更好的对连接进行管理,对连接状态进行监控,及时收回被占用时间过长的连接,避免数据库连接泄露。
2.1.3 使一个Connection可以对应多个Statement
这一个点需要单独讲一下,传统的JDBC连接方法中,一般拿到一个连接后,便设置其Statement,即在一个连接上只运行一或几个Statement,而这远远没有达到Connection的瓶顶,而ConnectionPool可以解决这个问题,在通过getConnection()获取连接时,可以返回没有到达运行Statement数量瓶顶的连接,从而更好的利用了资源,通过ConnectionPool对资源的有效管理,应用可以获得的Statement总数到达 : (并发物理连接数)×(每个连接可提供的Statement数量) 而这是传统的非资源池连接没有办法做到的。
2.1.4 Java EE连接池的实现样例:使用DataSource取得连接
在Java EE环境中,将取得连接等与数据库来源相关的行为规范在javax.sql.DataSource接口中,之前提到通过DriverManager取得Connection,事实上在Web应用程序中很少会这样做,一般会通过JNDI(Java Naming Director Interface)从服务器上取得设置好的DataSource,再从DataSource的getConnection()取得Connection;在Tomcat9中默认设置的为DBCP(Database Connection Pool),是内置在Tomcat中的连接池机制;
2.2 Durid连接池的使用:
Durid连接池是目前最为流行的连接池框架,就以Druid的使用来印证一下理论(Durid入门最好的学习资料当然是在github上),这个留在下一篇再讲~
3. 总结:
- JDBC的出现是为了对应用程序提供一致的接口,将业务代码与具体的数据库之间解耦合;
- 数据库连接池的出现主要是为了复用数据库连接资源,避免频繁建立连接增加系统负担。