先给自己两个小拳拳。。。
整理JDBC,结合代码整理一下JDBC的基本使用
基本思路:
前提条件,Java Database Connectivity 是需要引入相关数据库连接的驱动jar包的,本文以 MySQL 为例。
一、首先要加载数据库驱动 Driver
二、然后,获取链接 Connection
三、获取链接得到 statement 接口的实现类,来执行 SQL 语句
四、获取返回值或查询结果,并遍历、操作结果。
五、关闭资源
代码:
直接上代码吧,最基础的JDBC使用步骤,用的话需要将对应位置的参数更换成自己的,比如域名,端口号,数据库名和 SQL 语句
public static void main(String[] args){
//1.加载驱动类
Class.forName("com.mysql.jdbc.Driver");
//2.获取链接
//数据库链接地址 url IP:域名 Port:端口号 Database_Name:数据库名
String url = "jdbc:mysql://IP:Port/Database_Name?characterEncoding=utf8&serverTimeZone=GMT";
String username = "root";//数据库用户名
String password = "root";//数据库密码
Connection conn = DriverManager.getConnection(url,username,password);
//3.获取statement
Statement state = conn.createStatement();
//4. (1)执行Sql,(2)获取返回值,(3)操作返回值
String insertSql = "insert into Table_Name(Colum_name) values(value)";//插入语句
int insertResult = state.updateExcute(insertSql);//返回值为int型,返回执行成功的行数
//查询语句
ResultSet qureyResult = state.queryExcute();//返回值为ResultSet结果集,里面包含了从数据库中查询到的结果的集合
while(queryResult.next()){//通过next()方法,遍历结果集,注意每次调用next()方法都会将指针指向下一个结果,由此机制来遍历结果集直到没有下一个节点
queryResult.getInt("colum1");//获取列名为colum1的值,返回的是int值,选择的方法一定要与列名保存的数据类型一致
queryResult.getString("colum2");//返回值是String,方法还有很多,根据实际的表对应的列名调用
}
//5.关闭资源,先开启的资源最后关闭
resultSet.close();
state.close();
conn.close();
}
代码分析:
既然要做分析,那就尝试从源码角度跟踪,了解一下这简单的五个步骤,都是干嘛的,干了什么?
-
第一步,通过类名加载驱动。这里涉及的知识点较多,包括类加载机制和反射机制。
简单来说 Class.forName(“类的全限定名”) 的类加载,就是通过类名获得这个类的 类对象 ,在这个过程中需要加载这个类,同时会将类中的静态属性,方法,代码块一并加载。获取类对象的源码就不看了,看一眼Driver的源码:
package com.mysql.jdbc; import java.sql.DriverManager; import java.sql.SQLException; public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } }
通过源码可以看到在类加载中,执行了静态代码块中的内容:
将MySQL驱动包中的Driver,通过registerDriver注册给了jdk的java.sql包下的DriverManager中,由于Driver继承了NonRegisteringDriver,那么他就天生的拥有父类中的某些方法,属性等。那么这些属性就可以被java.sql.DriverManager获得到了。即完成了驱动的注册。
也就是说这个步骤告诉 DriverManager 我要使用 MySQL 的驱动操作 MySQL 数据库了,并给予 DriverManager 操作数据库相关的重写方法。
-
第二步,获取链接,通过DriverManager.getConnection()方式获得的。
@CallerSensitive public static Connection getConnection(String url, String user, String password) throws SQLException { Properties info = new Properties(); if (user != null) { info.put("user", user); } if (password != null) { info.put("password", password); } return getConnection(url, info, Reflection.getCallerClass()); }
可以看到,我们调的getConnection(url, username , password)方法中并没有直接返回链接,而是先将用户名和密码放入properties中后,连同url和调用此方法的那个类的类对象,传入它内部的一个私有方法getConnection(url,info,Reflection.getCallerClass())来获取连接的。
下面是Reflection.getCallerClass()这个静态方法的源码。public static native Class<?> getCallerClass();
可以看到这个方法被native修饰了,那么具体的调用是底层有关C或C++函数库的调用。这里是非JAVA代码了。既然咱是学JAVA的,这就不归咱管了。
回头看那个私有的 getConnection() 源码//......省略以上源码 Connection con = aDriver.driver.connect(url, info);//获取链接 if (con != null) {//如果获取到了链接 //这句是内部封装的打印日志信息 println("getConnection returning " + aDriver.driver.getClass().getName()); return con;//返回链接 } //......省略以下源码
在这个方法中可以看到获取了链接并返回。
-
第三步,获取 Statement 接口的实现类,这个接口中有关于SQL执行的方法(源码或在API中查看),这里通过 Connection 链接来创建一个 statement 接口的实现类。
想追这个接口是如何获得的,先从 MySQL 驱动包中的 Driver 类开始说。上方 com.mysql.jdbc.Driver 源码,可以看见,这个类继承了 NonRegisteringDriver 父类中的方法 ,又实现了 java.sql.Driver 接口。这个接口中的方法:Connection connect(String var1, Properties var2) throws SQLException;
通过调用这个接口的方法实际上执行的是 NonRegisteringDriver 父类中的 connect 方法:
public Connection connect(String url, Properties info) throws SQLException { //......省略源码 //通过ConnectionImpl的静态方法创建实现类 com.mysql.jdbc.Connection newConn = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url); return newConn;//返回这个实现类 //......省略源码 }
可以获取到一个链接,这个就是上方 getConnection 获取连接的方法,父接口 Connection 引用的是 ConnectionImpl 这个子类对象,这个方法最终返回到我们在第二步调用的 Connection conn = DriverManager.getConnection();
到这里我们终于得到了 java.sql.Connection 的实现类 java.mysql.jdbc.ConnectionImpl 对象,那么Statement 接口的实现类是如何获得的呢
继续看 conn 的方法createStatement();public java.sql.Statement createStatement() throws SQLException { return this.createStatement(1003, 1007); } public java.sql.Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { this.checkClosed(); StatementImpl stmt = new StatementImpl(this.getMultiHostSafeProxy(), this.database); stmt.setResultSetType(resultSetType); stmt.setResultSetConcurrency(resultSetConcurrency); return stmt; }
级联调了两个方法,最终我们获得了 java.mysql.jdbc.StatementImpl 的对象。
值得注意的是: 自从将 java.mysql.jdbc.Driver 注册给 DriverManager 之后,我们获取到的实现类,子类,执行的方法都是MySQL驱动jar包中提供的。这里充分体现出了 Java 语言的多态。
-
第四步,执行sql,操作返回值。
通过实现类的相关方法执行SQL语句。statement的有关方法举几个例子:Statement state = conn.createStatement(); //插入,更新,删除操作可以通过此方法完成 int result = state.executeUpdate("sql语句");//返回值是 影响行数 //查询操作可以通过此方法完成 ResultSet r = state.executeQuery("select * from t_student");//返回值是 查询结果 //通过next()方法来遍历ResultSet集合 while(r.next()){ //在这里对当前遍历到的 r 对象进行操作 }
简单的跟一下 ResultSet ,原本想从 executeQuery() 方法跟进去看一下如何返回的结果,奈何方法封装的太深,直接看的话该方法返回的是一个 ResultSetInternalMethods 接口的实现类,懒得一步步找,就简单判断了一下。
逻辑为:ResultSet 接口的 next 方法的实现类只有两个,即 ResultSetImpl 和 UpdatableResultSet 那么就可以判断 executeQuery 方法返回的 ResultSetInternalMethods 接口的实现类就是这两只之中的一种。而且,UpdatableResultSet 是 ResultSetImpl 的子类,其中 next 方法直接 调用了父类中的 next 方法,见下图:
上方是 ResultSet 接口的 next 方法 的实现类,其中只有两个类是 MySQL 驱动包的,即,ResultSetImpl 和 UpdatableResultSet 两个类。观察发现 UpdatableResultSet 是 ReultSetImpl 的子类。
先看一下 UpdatableResultSet 的 next 方法 源码:public synchronized boolean next() throws SQLException { return super.next(); }
可以看到,方法直接就调用了父类 ResultSetImpl 中的 next 方法,然后看一下 ResultSetImpl 中的 next 方法源码:
public boolean next() throws SQLException { synchronized(this.checkClosed().getConnectionMutex()) { //......省略源码 if (!this.reallyResult()) { //......省略源码 } else { //.......省略源码 boolean b; if (this.rowData.size() == 0) { //如果数据是空的 next方法就返回 false , //那么外部就会结束遍历 resultSet 的 while(resultSet.next()) 循环 b = false; } else { this.thisRow = this.rowData.next();//将指针指向下一个结点的数据 if (this.thisRow == null) { //如果当前指针的数据是空的,那么就返回 false b = false; } else { this.clearWarnings(); b = true;//不是空的就返回true,外部while 循环继续遍历结果集 } } this.setRowPositionValidity(); return b; } } }
到这里,我们对next方法有了简单的了解。
-
第五步,关闭资源,这没啥好说的了,具体怎么关的就随他去吧,close() 方法调就完了。使用时注意关闭资源的顺序,先打开的资源最后关闭。
总结
在一开始的类加载注册,到获取链接,再到获取statement,遍历 resultSet 结果集,最后关闭资源,从源码角度进行了一定的追踪分析,只能说简单的了解了这五个步骤执行JDBC操作的底层原理大致是个什么路子。而不单单停留在告诉你怎么用你就怎么用。
在之后的学习中。要多了解方法和机制的底层原理,多跟进代码内部观察。