一、背景
MySQL是一个中小型关系型数据库管理系统,目前我们淘宝也使用的也非常广泛。为了对开发中间DAO持久层的问题能有更深的理解以及最近在使用的phoenix on Hbase的SQL也是实现的JDBC规范,在遇到问题的时候能够有更多的思路,于是研究了一下MySQL_JDBC驱动的源码,大家都知道JDBC是Java访问数据库的一套规范,具体访问数据库的细节有各个数据库厂商自己实现,看驱动实现也有助有我们更好的理解JDBC规范,并且在这过程中也发现了一直以来对于PreparedStatement常识理解上的错误,与大家分享(MySQl版本5.1.39,JDBC驱动版本5.1.7,JDK版本1.6)。
二、JDBC典型应用
下面是个最简单的使用JDBC取得数据的应用。主要能分成几个步骤,分别是①加载数据库驱动,②获取数据库连接,③创建PreparedStatement,并且设置参数 ④ 执行查询 ,来分步分析这个过程。基本上每个步骤的源码分析我都画了时序图,如果不想看文字的话,可以对着时序图看。最后我还会分析关于PreparedStatement预编译的话题,有兴趣的同学可以仔细看下。
1. public class PreparedStatement_Select {
2. private Connection conn = null;
3. private PreparedStatement pstmt = null;
4. private ResultSet rs = null;
5. private String sql = "SELECT * FROM user WHERE id = ?";
7. public void selectStudent(int id) {
8. try {
9. // step1:加载数据库厂商提供的驱动程序
10. Class.forName(“ com.mysql.jdbc.Driver ”);
11. } catch (ClassNotFoundException e) {
12. e.printStackTrace();
13. }
15. String url = "jdbc:mysql://localhost:3306/studb";
16. try {
17. // step2:提供数据库连接的URL,通过DriverManager获得数据库的一个连接对象
18. conn = DriverManager.getConnection(url, "root", "root");
19. } catch (SQLException e) {
20. e.printStackTrace();
21. }
23. try {
24. // step3:创建Statement(SQL的执行环境)
25. pstmt = conn.prepareStatement(sql);
26. pstmt.setInt(1, id);
28. // step4: 执行SQL语句
29. rs = pstmt.executeQuery();
31. // step5: 处理结果
32. while (rs.next()) {
33. int i = 1;
34. System.out.print(" 学员编号: " + rs.getInt(i++));
35. System.out.print(", 学员用户名: " + rs.getString(i++));
36. System.out.print(", 学员密码: " + rs.getString(i++));
37. System.out.println(", 学员年龄: " + rs.getInt(i++));
38. }
39. } catch (SQLException e) {
40. e.printStackTrace();
41. } finally {
42. // step6: 关闭数据库连接
43. DbClose.close(rs, pstmt, conn);
44. }
45. }
46.}
三、JDBC驱动源码解析
Java数据库连接(JDBC)由一组用 Java 编程语言编写的类和接口组成。JDBC 为工具/数据库开发人员提供了一个标准的 API,使他们能够用纯Java API 来编写数据库应用程序。说白了一套Java访问数据库的统一规范,如下图,具体与数据库交互的还是由驱动实现,JDBC规范之于驱动的关系,也类似于Servlet规范与Servlet容器(Tomcat)的关系,本质就是一套接口和一套实现的关系。如下类图所示,我们平时开发JDBC时熟悉的Connection接口在Mysql驱动中的实现类是com.mysql.jdbc.JDBC4Connection类,PreparedStatement接口在Mysql驱动中的实现类是com.mysql.jdbc.JDBC4Connection, ResultSet接口在Mysql驱动中的实现类是 com.mysql.jdbc.JDBC4ResultSet,下面的源码解析也是通过这几个类展开。
1:加载数据库厂商提供的驱动程序
首先我们通过Class.forName("com.mysql.jdbc.Driver")来加载mysql的jdbc驱动。 Mysql的com.mysql.jdbc.Driver类实现了java.sql.Driver接口,任何数据库提供商的驱动类都必须实现这个接口。在DriverManager类中使用的都是接口Driver类型的驱动,也就是说驱动的使用不依赖于具体的实现,这无疑给我们的使用带来很大的方便。如果需要换用其他的数据库的话,只需要把Class.forName()中的参数换掉就可以了,可以说是非常方便的,com.mysql.jdbc.Driver类也是驱动实现JDBC规范的第一步。
1. public class Driver extends NonRegisteringDriver implements java.sql.Driver {
2. static {
3. try {
4. //往DriverManager中注册自身驱动
5. java.sql.DriverManager.registerDriver(new Driver());
6. } catch (SQLException E) {
7. throw new RuntimeException("Can't register driver!");
8. }
9. }
10. public Driver() throws SQLException {
11. }
12. }
在com.mysql.jdbc.Driver类的静态初始化块中会向java.sql.DriverManager注册它自己 ,注册驱动首先就是初始化,然后把驱动的信息封装一下放进一个叫做DriverInfo的驱动信息类中,最后放入一个驱动的集合中, 到此Mysql的驱动类com.mysql.jdbc.Driver也就已经注册到DriverManager中了。
1. public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {
2. if (!initialized) {
3. initialize();
4. }
6. DriverInfo di = new DriverInfo();
8. //把driver的信息封装一下,组成一个DriverInfo对象
9. di.driver = driver;
10. di.driverClass = driver.getClass();
11. di.driverClassName = di.driverClass.getName();
13. writeDrivers.addElement(di);
14. println("registerDriver: " + di);
16. readDrivers = (java.util.Vector) writeDrivers.clone();
17. }
注册驱动的具体过程序列图如下:
2.获取数据库连接
数据库连接的本质其实就是客户端维持了一个和远程MySQL服务器的一个TCP长连接,并且在此连接上维护了一些信息。
通过 DriverManager.getConnection(url, "root", "root")获取数据库连接对象时,由于之前已经在 DriverManager中注册了驱动类 ,所有会找到那个驱动类来连接数据库com.mysql.jdbc.Driver.connect
Java代码
1. private static Connection getConnection(
2. String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {
3. java.util.Vector drivers = null;
5. if (!initialized) {
6. initialize();
7. }
8. //取得连接使用的driver从readDrivers中取
9. synchronized (DriverManager.class){
10. drivers = readDrivers;
11. }
13. SQLException reason = null;
14. for (int i = 0; i < drivers.size(); i++) {
15. DriverInfo di = (DriverInfo)drivers.elementAt(i);
17. if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {
18. continue;
19. }
20. try {
21. // 找到可供使用的驱动,连接数据库server
22. Connection result = di.driver.connect(url, info);
23. if (result != null) {
24. return (result);
25. }