【面试一点通】面试被问:JDBC 底层是如何连接数据库的?

  原文链接:面试一点通http://www.msedt.com/infoflow/details/1097


  背景

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

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

由于小伙伴是面试高级开发,问这种问题倒也不能说面试官过分,如果是初级或者中级,那问着问题就确实有些过分了。

但是如果你在初级或者中级的阶段,就知道了答案,岂不是爽歪歪么?

估计大部分人都不知道这个问题该怎么回答,稍微发散一下思维,倒是可以猜测一下,今天我们就来搞清楚JDBC底层到底是如何连接数据库的。往后别再猜了。

反过来,如果面试官问你JDBC的时候,你能知道底层是怎么连接数据库的,估计,很多相对较水的面试官也会一脸懵逼。

何为 JDBC ?

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

JDBC 架构

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

双层

作用:此架构中,Java Applet 或应用直接访问数据源。

条件:要求 Driver 能与访问的数据库交互。

机制:用户命令传给数据库或其他数据源,随之结果被返回。

部署:数据源可以在另一台机器上,用户通过网络连接,称为 C/S配置(可以是内联网或互联网)。

三层

侧架构特殊之处在于,引入中间层服务。

流程:命令和结构都会经过该层。

吸引:可以增加企业数据的访问控制,以及多种类型的更新;另外,也可简化应用的部署,并在多数情况下有性能优势。

历史趋势:以往,因性能问题,中间层都用 C 或 C++ 编写,随着优化编译器(将 Java 字节码 转为 高效的 特定机器码)和技术的发展,如EJB,Java 开始用于中间层的开发这也让 Java 的优势突显出现出来,使用 Java 作为服务器代码语言,JDBC随之被重视。

入门案例

下面给出一个JDBC入门级案例:

publicclassJdbcDemo{publicstaticfinalString URL ="jdbc:mysql://localhost:3306/mblog";publicstaticfinalString USER ="root";publicstaticfinalString PASSWORD ="123456";publicstaticvoidmain(String[] args)throwsException{ 
        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文件。

img

Driver接口

java.sql.Driver此接口是提供给数据库厂商实现的。比如说MySQL的,需要依赖对应的jar包。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>复制代码

MySQL数据库对应的实现驱动实现类:

packagecom.mysql.cj.jdbc;importjava.sql.SQLException;publicclassDriverextendsNonRegisteringDriverimplementsjava.sql.Driver{static{try{//注册驱动java.sql.DriverManager.registerDriver(newDriver());
        }catch(SQLException E) {thrownewRuntimeException("Can't register driver!");
        }
    }publicDriver()throwsSQLException{ 
    }
}复制代码

DriverManager是rt.jar包下的类,(rt=runtime),把我们需要驱动类注册进去。

//DriverManager类中的方法publicstaticsynchronizedvoidregisterDriver(java.sql.Driver driver, DriverAction da)throwsSQLException{/* Register the driver if it has not already been added to our list */if(driver !=null) {
          registeredDrivers.addIfAbsent(newDriverInfo(driver, da));
      }else{// This is for compatibility with the original DriverManagerthrownewNullPointerException();
      }
      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方法三个参数:链接地址,用户名和密码。

publicstaticConnectiongetConnection(String url,
     String user, String password)throwsSQLException{
     java.util.Properties info =newjava.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的子类。

publicclassPropertiesextendsHashtable<Object,Object>{//.....}复制代码

再看getConnection方法:

//  Worker method called by the public getConnection() methods.privatestaticConnectiongetConnection(
        String url, java.util.Properties info, Class<?> caller)throwsSQLException{
  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()方法是每个数据库驱动自己的实现的。

packagecom.mysql.cj.jdbc;publicclassNonRegisteringDriverimplementsjava.sql.Driver{@Overridepublicjava.sql.Connectionconnect(String url, Properties info)throwsSQLException{//部分无关键要的代码省略//下面是重点ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);switch(conStr.getType()) {//SINGLE_CONNECTION("jdbc:mysql:", HostsCardinality.SINGLE), //caseSINGLE_CONNECTION:returncom.mysql.cj.jdbc.ConnectionImpl.getInstance(conStr.getMainHost());caseLOADBALANCE_CONNECTION:returnLoadBalancedConnectionProxy.createProxyInstance((LoadbalanceConnectionUrl) conStr);caseFAILOVER_CONNECTION:returnFailoverConnectionProxy.createProxyInstance(conStr);caseREPLICATION_CONNECTION:returnReplicationConnectionProxy.createProxyInstance((ReplicationConnectionUrl) conStr);default:returnnull;
        } 
    }
}复制代码

ConnectionUrl从这个类名应该能猜到还不到真正连接的,只是创建一个连接Url相关信息封装。

publicabstractclassConnectionUrlimplementsDatabaseUrlContainer{privatestaticfinalString DEFAULT_HOST ="localhost";privatestaticfinalintDEFAULT_PORT =3306;//...}复制代码

熟悉的身影,MySQL数据库默认端口。我们继续看下一行重要的代码:

ConnectionImpl.getInstance(conStr.getMainHost());

这里就是获取一个实例,不出意外,连接就在这里面产生的。继续:

//ConnectionImplpublicstaticJdbcConnectiongetInstance(HostInfo hostInfo)throwsSQLException{returnnewConnectionImpl(hostInfo);
}复制代码

ConnectionImpl构造方法里有调用createNewIO方法:

@OverridepublicvoidcreateNewIO(booleanisForReconnect){synchronized(getConnectionMutex()) {try{if(!this.autoReconnect.getValue()) {
                    connectOneTryOnly(isForReconnect);return;
                }
                connectWithRetries(isForReconnect);
            }catch(SQLException ex) { 
            }
        }
    }privatevoidconnectOneTryOnly(booleanisForReconnect)throwsSQLException{
        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。

publicvoidconnect(HostInfo hi, String user, String password, String database,intloginTimeout, TransactionEventHandler transactionManager)throwsIOException{  
    SocketConnection socketConnection =newNativeSocketConnection();
    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@Overridepublicvoidconnect(String hostName,intportNumber, PropertySet propSet, ExceptionInterceptor excInterceptor, Log log,intloginTimeout){this.mysqlSocket =this.socketFactory.connect(this.host,this.port, propSet, loginTimeout);//...}复制代码

这里的socketFactory是StandardSocketFactory。所以也就是调用的是StandardSocketFactory的connect方法:

//StandardSocketFactorypublic<T extends Closeable>Tconnect(String hostname,intportNumber, PropertySet pset,intloginTimeout)throwsIOException{this.rawSocket = createSocket(pset);this.rawSocket.connect(sockAddr, getRealTimeout(connectTimeout));
}protectedSocketcreateSocket(PropertySet props){returnnewSocket();
}复制代码

这里就算到底了,说白JDBC的底层就是使用**「Socket」**进行连接数据库的。

常用方法

获取Statement

三种类型

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

具体获取方式

Statement stmt = con.createStatement() ;   
PreparedStatement pstmt = con.prepareStatement(sql) ;   
CallableStatement cstmt =  con.prepareCall("{CALL demoSp(? , ?)}") ;复制代码

常用方法Statement和PreparedStatement的异同及优缺点

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

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

PreparedStatement的优点:

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

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

3、其能够有效防止SQL注入攻击。

execute和executeUpdate的区别

相同点:二者都能够执行增加、删除、修改等操作。

不同点:

1、execute可以执行查询语句,然后通过getResult把结果取出来。executeUpdate不能执行查询语句。

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

ResultSet结果集处理

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

img

ResultSetImpl类图

img

常用获取值方法

常用获取行方法

常用数据类型转换

以上便是结果集的处理,就这么多了。

资源关闭

资源关闭不在业务代码这一块,主要是针对一些资源进行关闭,免得一直持有资源。另外我们处理的资源关闭一般都是在finally中处理。

总结

本文主要讲了如下内容:

 

  • 什么是JDBC?
  • 数据库驱动的加载和注册是如何处理的?
  • 精彩点是我们通常说的JDBC连接数据库,讲了到了底层是怎么连接数据库的?
  • 结果集处理的常用方法
  • next():移动到下一行
  • Previous():移动到前一行
  • absolute(int row):移动到指定行
  • beforeFirst():移动resultSet的最前面。
  • afterLast() :移动到resultSet的最后面。
  • 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):获取在数据库里任意类型的数据。
  • 执行静态SQL语句。通常通过Statement实例实现。
  • 执行动态SQL语句。通常通过PreparedStatement实例实现。
  • 执行数据库存储过程。通常通过CallableStatement实例实现。
  • 数据库驱动的加载和注册是如何处理的?
  • 精彩点是我们通常说的JDBC连接数据库,讲了到了底层是怎么连接数据库的?
  • 结果集处理的常用方法
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值