关注“Java后端技术全栈”java
回复“面试”获取全套面试资料mysql
前两天一个小伙伴面试的时候,被问JDBC底层是如何链接数据库的?面试
他顿时一脸懵逼,由于大部分人只知道JDBC的几个步骤,至于底层究竟是怎么链接数据库的,还真不知道。sql
因为小伙伴是面试高级开发,问这种问题倒也不能说面试官过度,若是是初级或者中级,那问着问题就确实有些过度了。数据库
可是若是你在初级或者中级的阶段,就知道了答案,岂不是爽歪歪么?后端
估计大部分人都不知道这个问题该怎么回答,稍微发散一下思惟,却是能够猜想一下,今天咱们就来搞清楚JDBC底层究竟是如何链接数据库的。日后别再猜了。服务器
反过来,若是面试官问你JDBC的时候,你能知道底层是怎么链接数据库的,估计,不少相对较水的面试官也会一脸懵逼。网络
JDBC(Java DataBase Connectivity)是Java和数据库之间的一个桥梁,是一个「规范」而不是一个实现,可以执行SQL语句。JDBC由一组用Java语言编写的类和接口组成。各类不一样类型的数据库都有相应的实现,注意:本文中的代码都是针对MySQL数据库实现的。session
分为双层架构和三层架构。架构
做用:此架构中,Java Applet 或应用直接访问数据源。
条件:要求 Driver 能与访问的数据库交互。
机制:用户命令传给数据库或其余数据源,随之结果被返回。
部署:数据源能够在另外一台机器上,用户经过网络链接,称为 C/S配置(能够是内联网或互联网)。
侧架构特殊之处在于,引入中间层服务。
流程:命令和结构都会通过该层。
吸引:能够增长企业数据的访问控制,以及多种类型的更新;另外,也可简化应用的部署,并在多数状况下有性能优点。
历史趋势:以往,因性能问题,中间层都用 C 或 C++ 编写,随着优化编译器(将 Java 字节码 转为 高效的 特定机器码)和技术的发展,如EJB,Java 开始用于中间层的开发这也让 Java 的优点突显出现出来,使用 Java 做为服务器代码语言,JDBC随之被重视。
下面给出一个JDBC
入门级案例:
public class JdbcDemo { public static final String URL = "jdbc:mysql://localhost:3306/mblog"; public static final String USER = "root"; public static final String PASSWORD = "123456"; public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT id, name, age FROM m_user where id =1"); while(rs.next()){ System.out.println("name: "+rs.getString("name")+" 年龄:"+rs.getInt("age")); } } }
数据库驱动:
Class.forName("com.mysql.jdbc.Driver");
获取链接:
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
建立Statement
或者PreparedStatement
对象:
Statement stmt = conn.createStatement();
执行sql数据库查询:
ResultSet rs = stmt.executeQuery("SELECT id, name, age FROM m_user where id =1");
解析结果集:
System.out.println("name: "+rs.getString("name")+" 年龄:"+rs.getInt("age"));
最后就是各类资源的关闭。
加载MySql的驱动类 :
Class.forName("com.mysql.jdbc.Driver");
咱们安装好数据库以后,咱们的应用程序也是不能直接使用数据库的,必需要经过相应的数据库驱动程序,经过驱动程序去和数据库打交道。其实也就是数据库厂商的JDBC接口实现,即对Connection等接口的实现类的jar文件。
java.sql.Driver
此接口是提供给数据库厂商实现的。好比说MySQL的,须要依赖对应的jar包。
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency>
MySQL数据库对应的实现驱动实现类:
package com.mysql.cj.jdbc; import java.sql.SQLException; public class Driver extends NonRegisteringDriver implements java.sql.Driver { static { try { //注册驱动 java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } public Driver() throws SQLException { } }
DriverManager是rt.jar包下的类,(rt=runtime),把咱们须要驱动类注册进去。
//DriverManager类中的方法 public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException { /* Register the driver if it has not already been added to our list */ if(driver != null) { registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); } else { // This is for compatibility with the original DriverManager throw new NullPointerException(); } println("registerDriver: " + driver); }
相应装载Oracle驱动:
Class.forName("oracle.jdbc.driver.OracleDriver");
Sql Server驱动:
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
给咱们看起来就这一行代码:
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
下面咱们进行深刻聊聊这行代码,到底底层是怎么链接数据库的?
getConnection
方法三个参数:连接地址,用户名和密码。
public static Connection getConnection(String url, String user, String password) throws SQLException { java.util.Properties info = new java.util.Properties(); if (user != null) { info.put("user", user); } if (password != null) { info.put("password", password); } return (getConnection(url, info, Reflection.getCallerClass())); }
建立一个Properties对象,Properties是HashTable
的子类。
public class Properties extends Hashtable<Object,Object> { //..... }
再看getConnection
方法:
// Worker method called by the public getConnection() methods. private static Connection getConnection( String url, java.util.Properties info, Class<?> caller) throws SQLException { ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; SQLException reason = null; //遍历气门注册的数据库驱动 for(DriverInfo aDriver : registeredDrivers) { try { //获取链接 Connection con = aDriver.driver.connect(url, info); if (con != null) { // Success! println("getConnection returning " + aDriver.driver.getClass().getName()); return (con); } } catch (SQLException ex) { if (reason == null) { reason = ex; } } } }
这段代码的关键是这一句代码:
Connection con = aDriver.driver.connect(url, info);
connet()
方法是每一个数据库驱动本身的实现的。
package com.mysql.cj.jdbc; public class NonRegisteringDriver implements java.sql.Driver { @Override public java.sql.Connection connect(String url, Properties info) throws SQLException { //部分无关键要的代码省略 //下面是重点 ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info); switch (conStr.getType()) { //SINGLE_CONNECTION("jdbc:mysql:", HostsCardinality.SINGLE), // case SINGLE_CONNECTION: return com.mysql.cj.jdbc.ConnectionImpl.getInstance(conStr.getMainHost()); case LOADBALANCE_CONNECTION: return LoadBalancedConnectionProxy.createProxyInstance((LoadbalanceConnectionUrl) conStr); case FAILOVER_CONNECTION: return FailoverConnectionProxy.createProxyInstance(conStr); case REPLICATION_CONNECTION: return ReplicationConnectionProxy.createProxyInstance((ReplicationConnectionUrl) conStr); default: return null; } } }
ConnectionUrl从这个类名应该能猜到还不到真正链接的,只是建立一个链接Url相关信息封装。
public abstract class ConnectionUrl implements DatabaseUrlContainer { private static final String DEFAULT_HOST = "localhost"; private static final int DEFAULT_PORT = 3306; //... }
熟悉的身影,MySQL数据库默认端口。咱们继续看下一行重要的代码:
ConnectionImpl.getInstance(conStr.getMainHost());
这里就是获取一个实例,不出意外,链接就在这里面产生的。继续:
//ConnectionImpl public static JdbcConnection getInstance(HostInfo hostInfo) throws SQLException { return new ConnectionImpl(hostInfo); }
ConnectionImpl构造方法里有调用createNewIO方法:
@Override public void createNewIO(boolean isForReconnect) { synchronized (getConnectionMutex()) { try { if (!this.autoReconnect.getValue()) { connectOneTryOnly(isForReconnect); return; } connectWithRetries(isForReconnect); } catch (SQLException ex) { } } } private void connectOneTryOnly(boolean isForReconnect) throws SQLException { Exception connectionNotEstablishedBecause = null; JdbcConnection c = getProxy(); //又看到熟悉的connet方法, this.session.connect(this.origHostInfo, this.user, this.password, this.database, DriverManager.getLoginTimeout() * 1000, c); this.session.setQueryInterceptors(this.queryInterceptors); }
其中,这里的session是NativeSession。
public void connect(HostInfo hi, String user, String password, String database, int loginTimeout, TransactionEventHandler transactionManager) throws IOException { SocketConnection socketConnection = new NativeSocketConnection(); socketConnection.connect(this.hostInfo.getHost(), this.hostInfo.getPort(), this.propertySet, getExceptionInterceptor(), this.log, loginTimeout); this.protocol.connect(user, password, database); this.protocol.getServerSession().setErrorMessageEncoding(this.protocol.getAuthenticationProvider().getEncodingForHandshake()); }
在这个方法里,咱们看到了Socket的命名开头的类,哈哈,是否是就是使用Socket进行通讯的呢?
精彩继续:
socketConnection.connect(this.hostInfo.getHost(), this.hostInfo.getPort(), ...);
来到NativeSocketConnection类中方法:
//com.mysql.cj.protocol.a.NativeSocketConnection @Override public void connect(String hostName, int portNumber, PropertySet propSet, ExceptionInterceptor excInterceptor, Log log, int loginTimeout) { this.mysqlSocket = this.socketFactory.connect(this.host, this.port, propSet, loginTimeout); //... }
这里的socketFactory是StandardSocketFactory。因此也就是调用的是StandardSocketFactory的connect方法:
//StandardSocketFactory public <T extends Closeable> T connect(String hostname, int portNumber, PropertySet pset, int loginTimeout) throws IOException { this.rawSocket = createSocket(pset); this.rawSocket.connect(sockAddr, getRealTimeout(connectTimeout)); } protected Socket createSocket(PropertySet props) { return new Socket(); }
这里就算到底了,说白JDBC
的底层就是使用「Socket」进行链接数据库的。
方法
描述
createStatement()
建立向数据库发送sql的statement对象。
prepareStatement(sql)
建立向数据库发送预编译sql的PrepareSatement对象。
prepareCall(sql)
建立执行存储过程的callableStatement对象。
setAutoCommit(boolean autoCommit)
设置事务是否自动提交。
commit()
在连接上提交事务。
rollback()
在此连接上回滚事务。
要执行SQL语句,必须得到java.sql.Statement实例,Statement实例分为如下3 种类型:
Statement stmt = con.createStatement() ; PreparedStatement pstmt = con.prepareStatement(sql) ; CallableStatement cstmt = con.prepareCall("{CALL demoSp(? , ?)}") ;
方法
含义
executeQuery(String sql)
用于向数据发送查询语句。
executeUpdate(String sql)
用于向数据库发送insert、update或delete语句
execute(String sql)
用于向数据库发送任意sql语句
addBatch(String sql)
把多条sql语句放到一个批处理中。
executeBatch()
向数据库发送一批sql语句执行。
同:二者都是用来执SQL语句的
异:PreparedStatement须要根据SQL语句来建立,它可以经过设置参数,指定相应的值,不是像Statement那样使用字符串拼接的方式。
PreparedStatement的优势:
一、其使用参数设置,可读性好,不易记错。在statement中使用字符串拼接,可读性和维护性比较差。
二、其具备预编译机制,性能比statement更快。
三、其可以有效防止SQL注入攻击。
相同点:两者都可以执行增长、删除、修改等操做。
不一样点:
一、execute能够执行查询语句,而后经过getResult把结果取出来。executeUpdate不能执行查询语句。
二、execute返回Boolean类型,true表示执行的是查询语句,false表示执行的insert、delete、update等。executeUpdate的返回值是int,表示有多少条数据受到了影响。
前面的入门案例中这里返回的结果集是ResultSetImpl
ResultSetImpl类图
SQL类型
Jdbc对应方法
返回类型
bit(1),bit(n)
getBoolean,getBytes()
Boolean,byte[]
tinyint
getByte()
Byte
smallint
getShort()
Short
int
getInt
Int
bigint
getLong()
Long
char,varchar,longvarchar
getString
String
text(clob) blob
getClob(),getblob()
Clob,blob
date
getDate()
java.sql.Date
time
getTime()
java.sql.Time
timestamp
getTimestamp
java.sql.Timestamp
以上即是结果集的处理,就这么多了。
资源关闭不在业务代码这一块,主要是针对一些资源进行关闭,省得一直持有资源。另外咱们处理的资源关闭通常都是在finally中处理。
本文主要讲了以下内容: