什么是JDBC?一百个实习生和应届生一百个都说是连接和操作数据库的方法。
JDBC,也就是 Java DataBase Connectivity (Java 数据库连接),其实是由Java语言编写的一堆接口和一些class类组成的一套工具类的程序。它里面的一堆接口,都是给不同的数据库服务商定义的一套Java连接数据库的一套规则,一套规范。也就是说JDBC并不能给我们直接连接和操作数据库,真正操作数据库的,是各大数据库服务商按照JDBC规范写出来的驱动Jar包,JDBC和驱动包的关系,就是很单纯很清纯的接口与实现类的关系,修电脑与不过夜的关系。
简单看下JDBC怎么连接我本地测试数据库的:
不难看出,JDBC主要有四步操作:
1:加载数据库驱动
2:获取数据库连接
3:创建Statement/PreparedStatement
4:执行CRUD操作,封装返回结果ResultSet
下面开始讲大道理:
一:加载数据库驱动
都说了驱动Jar包,那就可以通过全限定类名通过反射方式来创建和加载。加载进来之后,在DriverManager驱动管理器中就会有一个方法registerDriver的,就会悄咪咪找上加载进来的驱动。既然是规范,那些各大数据库服务商驱动也不能乱来,都要老老实实实现一个java.sql.Driver的接口。
二:获取数据库连接
这一步会返回一个链接Connection,创建链接主要在getConnection方法,进去看。
那个被循环的是一个装满了已注册的驱动的ArrayList,以前老版本好像也是个vector
这都不是重点,我们还要继续往driver.connect方法里面看:
然后可以看到这里那么多种连接类型,各自都有什么用,创建过程是怎样,就不每个进去看了,只看最简单的ConnectionImpl通过什么创建链接
public ConnectionImpl(HostInfo hostInfo) throws SQLException {
try {
this.origHostInfo = hostInfo;
this.origHostToConnectTo = hostInfo.getHost();
this.origPortToConnectTo = hostInfo.getPort();
this.database = hostInfo.getDatabase();
this.user = StringUtils.isNullOrEmpty(hostInfo.getUser()) ? "" : hostInfo.getUser();
this.password = StringUtils.isNullOrEmpty(hostInfo.getPassword()) ? "" : hostInfo.getPassword();
this.props = hostInfo.exposeAsProperties();
this.propertySet = new JdbcPropertySetImpl();
this.propertySet.initializeProperties(this.props);
this.nullStatementResultSetFactory = new ResultSetFactory(this, (StatementImpl)null);
this.session = new NativeSession(hostInfo, this.propertySet);
this.session.addListener(this);
this.autoReconnectForPools = this.propertySet.getBooleanProperty(PropertyKey.autoReconnectForPools);
this.cachePrepStmts = this.propertySet.getBooleanProperty(PropertyKey.cachePrepStmts);
this.autoReconnect = this.propertySet.getBooleanProperty(PropertyKey.autoReconnect);
this.useUsageAdvisor = this.propertySet.getBooleanProperty(PropertyKey.useUsageAdvisor);
this.reconnectAtTxEnd = this.propertySet.getBooleanProperty(PropertyKey.reconnectAtTxEnd);
this.emulateUnsupportedPstmts = this.propertySet.getBooleanProperty(PropertyKey.emulateUnsupportedPstmts);
this.ignoreNonTxTables = this.propertySet.getBooleanProperty(PropertyKey.ignoreNonTxTables);
this.pedantic = this.propertySet.getBooleanProperty(PropertyKey.pedantic);
this.prepStmtCacheSqlLimit = this.propertySet.getIntegerProperty(PropertyKey.prepStmtCacheSqlLimit);
this.useLocalSessionState = this.propertySet.getBooleanProperty(PropertyKey.useLocalSessionState);
this.useServerPrepStmts = this.propertySet.getBooleanProperty(PropertyKey.useServerPrepStmts);
this.processEscapeCodesForPrepStmts = this.propertySet.getBooleanProperty(PropertyKey.processEscapeCodesForPrepStmts);
this.useLocalTransactionState = this.propertySet.getBooleanProperty(PropertyKey.useLocalTransactionState);
this.disconnectOnExpiredPasswords = this.propertySet.getBooleanProperty(PropertyKey.disconnectOnExpiredPasswords);
this.readOnlyPropagatesToServer = this.propertySet.getBooleanProperty(PropertyKey.readOnlyPropagatesToServer);
String exceptionInterceptorClasses = this.propertySet.getStringProperty(PropertyKey.exceptionInterceptors).getStringValue();
if (exceptionInterceptorClasses != null && !"".equals(exceptionInterceptorClasses)) {
this.exceptionInterceptor = new ExceptionInterceptorChain(exceptionInterceptorClasses, this.props, this.session.getLog());
}
if ((Boolean)this.cachePrepStmts.getValue()) {
this.createPreparedStatementCaches();
}
if ((Boolean)this.propertySet.getBooleanProperty(PropertyKey.cacheCallableStmts).getValue()) {
this.parsedCallableStatementCache = new LRUCache((Integer)this.propertySet.getIntegerProperty(PropertyKey.callableStmtCacheSize).getValue());
}
if ((Boolean)this.propertySet.getBooleanProperty(PropertyKey.allowMultiQueries).getValue()) {
this.propertySet.getProperty(PropertyKey.cacheResultSetMetadata).setValue(false);
}
if ((Boolean)this.propertySet.getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) {
this.resultSetMetadataCache = new LRUCache((Integer)this.propertySet.getIntegerProperty(PropertyKey.metadataCacheSize).getValue());
}
if (this.propertySet.getStringProperty(PropertyKey.socksProxyHost).getStringValue() != null) {
this.propertySet.getProperty(PropertyKey.socketFactory).setValue(SocksProxySocketFactory.class.getName());
}
this.dbmd = this.getMetaData(false, false);
this.initializeSafeQueryInterceptors();
} catch (CJException var5) {
throw SQLExceptionsMapping.translateException(var5, this.getExceptionInterceptor());
}
try {
this.createNewIO(false);
this.unSafeQueryInterceptors();
AbandonedConnectionCleanupThread.trackConnection(this, this.getSession().getNetworkResources());
} catch (SQLException var3) {
this.cleanup(var3);
throw var3;
} catch (Exception var4) {
this.cleanup(var4);
throw SQLError.createSQLException((Boolean)this.propertySet.getBooleanProperty(PropertyKey.paranoid).getValue() ? Messages.getString("Connection.0") : Messages.getString("Connection.1", new Object[]{
this.session.getHostInfo().getHost(), this.session.getHostInfo().getPort()}), "08S01", var4, this.getExceptionInterceptor());
}
}
虽然又臭又长,但是还是看到了通过socketFactory创建出socket进行TCP长链接,数据之间的交互就是通过socketInputStream输入流和socketOutPutStream输出流控制入参和出参,参数包括用户名密码,SQL语句,查询结果集等等,个人能力有限就只了解到这里,再深入的背不了,开发也用不上,就暂时没去管了。
三:通过第二步创建好的connection链接,就可以创建SQL执行环境Statement或者PreparedStatement
关于未预编译和预编译的区别,这里暂时不深究了
同理Statement的我们也看一下,比预编译的会简单很多
在这里他们都会返回一个stmt对象,也就是statement, 它到底是什么?为什么有了它就可以执行SQL?
带着疑问进去看,发现都实现了或者继承了JdbcStatement,我们就很自然想到了是JDBC一种规范
再进来看,发现它继承了一个Statement接口一个Query接口,其中Statement接口果然就是提供了操作数据库的API,也就是上面说的执行SQL的环境,这些接口起到的就是一个规范化的作用,不管增删改,都对应着executeUpdate方法,查方法就对应着executeQuery方法。
它的一些实现类,就是我们进来的入口,很清楚看到实现类都是在com.mysql.cj.jdbc驱动包里面的。而关于Query,更多的是一些自定义的内容,例如拿到当前statement ID,检查是否超时,设置过期时间,设置执行时间,设置返回集类型…太多了就不一个个点开看了
创建执行环境是Statement,执行是最后一步干的事情,我们继续看~
四:通过上面几步创建好了长链接,创建好了执行环境,这一步终于开始执行查询和封装ResultSet返回结果集对象
显而易见,这里调用的executeQuery就是上面我们看的JDBC定的规范,返回是一个ResultSet(只有查询操作才是ResultSet,增删改操作都是返回一个int)。我们要看具体的执行,就要进去到对应的实现类也就是自己导入的那个驱动jar包里面看
public ResultSet executeQuery(String sql) throws SQLException {
try {
synchronized(this.checkClosed().getConnectionMutex()) {
JdbcConnection locallyScopedConn = this.connection;
this.retrieveGeneratedKeys = false;
this.checkNullOrEmptyQuery(sql);
this.resetCancelledState();
this.implicitlyCloseAllOpenResults();
if (sql.charAt(0) == '/' && sql.startsWith("/* ping */")) {
this.doPingInstead();
return this.results;
} else {
this.setupStreamingTimeout(locallyScopedConn);
if (this.doEscapeProcessing) {
Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, this.session.getServerSession().getServerTimeZone(), this.session.getServerSession().getCapabilities().serverSupportsFracSecs(), this.session.getServerSession().isServerTruncatesFracSecs(), this.getExceptionInterceptor());
sql = escapedSqlResult instanceof String ? (String)escapedSqlResult : ((EscapeProcessorResult)escapedSqlResult).escapedSql;
}
char firstStatementChar = StringUtils.firstAlphaCharUc(sql, findStartOfStatement(sql));
this.checkForDml(sql, firstStatementChar);
CachedResultSetMetaData cachedMetaData = null;
if (this.useServerFetch()) {
this.results = this.createResultSetUsingServerFetch(sql);
return this.results;
} else {
CancelQueryTask timeoutTask = null;
String oldDb = null;
try {
timeoutTask = this.startQueryTimer(this, this.getTimeoutInMillis());
if (!locallyScopedConn.getDatabase().equals(this.getCurrentDatabase())) {
oldDb = locallyScopedConn.getDatabase();
locallyScopedConn.setDatabase(this.getCurrentDatabase());
}
if ((Boolean)locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) {
cachedMetaData = locallyScopedConn.getCachedMetaData(sql);
}
locallyScopedConn.setSessionMaxRows(this.maxRows);
this.statementBegins();
this.results = (ResultSetInternalMethods)((NativeSession)locallyScopedConn.getSession()).execSQL(this, sql, this.maxRows, (NativePacketPayload)null, this.createStreamingResultSet(), this.getResultSetFactory(), cachedMetaData, false);
if (timeoutTask != null) {
this.stopQueryTimer(timeoutTask, true, true);
timeoutTask = null;
}
} catch (OperationCancelledException | CJTimeoutException var16) {
throw SQLExceptionsMapping.translateException(var16, this.exceptionInterceptor);
} finally {
this.query.getStatementExecuting().set(false);
this.stopQueryTimer(timeoutTask, false, false);
if (oldDb != null) {
locallyScopedConn.setDatabase(oldDb);
}
}
this.lastInsertId = this.results.getUpdateID();
if (cachedMetaData != null) {
locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData, this.results);
} else if ((Boolean)this.connection.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) {
locallyScopedConn.initializeResultsMetadataFromCache(sql, (CachedResultSetMetaData)null, this.results);
}
return this.results;
}
}
}
} catch (CJException var19) {
throw SQLExceptionsMapping.translateException(var19, this.getExceptionInterceptor());
}
}
获取要操作的database
execSQL,一看这名字,肯定就是它负责执行的没错了。进去看看
返回的var21,就是ResultSet类型的
再上一层的的result,就一样是一个Wrapper包裹,实现了IO.序列化接口,方便把对象转换为有序字节流,在网络上传输或者保存,返回的时候,也是通过反序列化,一层层包成resultSet对象返回,提供给我们一个.next方法返回的Boolean值判断是否还有下一个元素,然后根据Key(参数名)读取它的value(查到的结果)