面试被问:JDBC底层是如何链接数据库的?

关注“Java后端技术全栈”java

回复“面试”获取全套面试资料mysql

背景

前两天一个小伙伴面试的时候,被问JDBC底层是如何链接数据库的?面试

他顿时一脸懵逼,由于大部分人只知道JDBC的几个步骤,至于底层究竟是怎么链接数据库的,还真不知道。sql

因为小伙伴是面试高级开发,问这种问题倒也不能说面试官过度,若是是初级或者中级,那问着问题就确实有些过度了。数据库

可是若是你在初级或者中级的阶段,就知道了答案,岂不是爽歪歪么?后端

估计大部分人都不知道这个问题该怎么回答,稍微发散一下思惟,却是能够猜想一下,今天咱们就来搞清楚JDBC底层究竟是如何链接数据库的。日后别再猜了。服务器

反过来,若是面试官问你JDBC的时候,你能知道底层是怎么链接数据库的,估计,不少相对较水的面试官也会一脸懵逼。网络

何为 JDBC ?

JDBC(Java DataBase Connectivity)是Java和数据库之间的一个桥梁,是一个「规范」而不是一个实现,可以执行SQL语句。JDBC由一组用Java语言编写的类和接口组成。各类不一样类型的数据库都有相应的实现,注意:本文中的代码都是针对MySQL数据库实现的。session

JDBC 架构

分为双层架构和三层架构。架构

双层

做用:此架构中,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"));
        }
    }
}

JDBC 步骤

数据库驱动:

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文件。

Driver接口

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()

在此连接上回滚事务。

获取Statement

三种类型

要执行SQL语句,必须得到java.sql.Statement实例,Statement实例分为如下3 种类型:

  • 执行静态SQL语句。一般经过Statement实例实现。
  • 执行动态SQL语句。一般经过PreparedStatement实例实现。
  • 执行数据库存储过程。一般经过CallableStatement实例实现。
具体获取方式
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语句执行。

Statement和PreparedStatement的异同及优缺点

同:二者都是用来执SQL语句的

异:PreparedStatement须要根据SQL语句来建立,它可以经过设置参数,指定相应的值,不是像Statement那样使用字符串拼接的方式。

PreparedStatement的优势:

一、其使用参数设置,可读性好,不易记错。在statement中使用字符串拼接,可读性和维护性比较差。

二、其具备预编译机制,性能比statement更快。

三、其可以有效防止SQL注入攻击。

execute和executeUpdate的区别

相同点:两者都可以执行增长、删除、修改等操做。

不一样点:

一、execute能够执行查询语句,而后经过getResult把结果取出来。executeUpdate不能执行查询语句。

二、execute返回Boolean类型,true表示执行的是查询语句,false表示执行的insert、delete、update等。executeUpdate的返回值是int,表示有多少条数据受到了影响。

ResultSet结果集处理

前面的入门案例中这里返回的结果集是ResultSetImpl

图片

ResultSetImpl类图

图片

经常使用获取值方法

  • getString(int index)、getString(String columnName):得到在数据库里是varchar、char等类型的数据对象。
  • getFloat(int index)、getFloat(String columnName):得到在数据库里是Float类型的数据对象。
  • getDate(int index)、getDate(String columnName):得到在数据库里是Date类型的数据。
  • getBoolean(int index)、getBoolean(String columnName):得到在数据库里是Boolean类型的数据。
  • getObject(int index)、getObject(String columnName):获取在数据库里任意类型的数据。

经常使用获取行方法

  • next():移动到下一行
  • Previous():移动到前一行
  • absolute(int row):移动到指定行
  • beforeFirst():移动resultSet的最前面。
  • afterLast() :移动到resultSet的最后面。

经常使用数据类型转换

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中处理。

总结

本文主要讲了以下内容:

  • 什么是JDBC?
  • 数据库驱动的加载和注册是如何处理的?
  • 精彩点是咱们一般说的JDBC链接数据库,讲了到了底层是怎么链接数据库的?
  • 结果集处理的经常使用方法
相关文章
相关标签/搜索