您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

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

时间:2020-12-28 10:27:34  来源:  作者:

背景

前两天一个小伙伴面试的时候,被问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入门级案例:

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

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

 

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的优点:

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

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

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

execute和executeUpdate的区别

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

不同点:

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

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



Tags:JDBC   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  Tags: JDBC  点击:(12)  评论:(0)  加入收藏
1. 介绍1.1 介绍今天开始我们来学习Java操作MySQL数据库的技巧,Java操作MySQL是借助JdbcTemplate这个对象来实现的。JdbcTemplate是一个多数据库集中解决方案,而我们今天只讲...【详细内容】
2021-11-05  Tags: JDBC  点击:(30)  评论:(0)  加入收藏
Sharding-JDBC背景:出于工作的需要,非常需要详细了解数据库分表分库的内容,这样便于选择版本,了解原理及性能,做出更好的选择。Sharding-JDBC是ShardingSphere的第一个产品,也是Sh...【详细内容】
2021-05-14  Tags: JDBC  点击:(212)  评论:(0)  加入收藏
本期给大家带来的是JAVA基础中的JDBC,JDBC全称:Java DataBase Connectivity Java 数据库连接, Java语言操作数据库,说白了就是用Java语言来操作数据库。在大数据开发基础中也是...【详细内容】
2021-03-16  Tags: JDBC  点击:(142)  评论:(0)  加入收藏
前言hello我是bigsai,今天咱们进行JDBC+MySQL实战,非常适合初入门的小伙伴打开新的世界。实现一个增删改查(curd)的例子。先点赞再观看、帅哥靓女养成好习惯!在这个案例进行之...【详细内容】
2021-01-15  Tags: JDBC  点击:(187)  评论:(0)  加入收藏
背景前两天一个小伙伴面试的时候,被问JDBC底层是如何连接数据库的?他顿时一脸懵逼,因为大部分人只知道JDBC的几个步骤,至于底层到底是怎么连接数据库的,还真不知道。由于小伙伴是...【详细内容】
2020-12-28  Tags: JDBC  点击:(120)  评论:(0)  加入收藏
作者:scherman来源:segmentfault.com/a/1190000018472572因为项目需要选择数据持久化框架,看了一下主要几个流行的和不流行的框架,对于复杂业务系统,最终的结论是,JOOQ是总体上最...【详细内容】
2020-07-04  Tags: JDBC  点击:(68)  评论:(0)  加入收藏
不少学习过java,或者接触过java的人,对jdbc应该都不陌生。但是,jdbc到底是啥?有些人可能还真不知道,即便一直在用。jdbc是基于java的数据库访问接口。具体的操作步骤如下:1、加载...【详细内容】
2020-03-08  Tags: JDBC  点击:(52)  评论:(0)  加入收藏
当Mysql数据库数据达到一定量后,查询SQL执行会变慢起来,除了建索引、优化程序代码以及SQL语句等常规手段外,利用经典MHA数据库中间件做数据库读写分离是一个不错的选择。但是在读写分离架构中会出现一个共性问题:SQL读取...【详细内容】
2019-09-17  Tags: JDBC  点击:(239)  评论:(0)  加入收藏
更多内容,欢迎关注微信公众号:全菜工程师小辉~前几天笔者发布了博客,手写mybatis彻底搞懂框架原理。为了帮助初学者更好理解mybatis框架,这次讲解一下Java的JDBC的运行过程。JDB...【详细内容】
2019-08-05  Tags: JDBC  点击:(249)  评论:(0)  加入收藏
▌简易百科推荐
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(11)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(12)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(10)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(10)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(14)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(17)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(21)  评论:(0)  加入收藏
一、概述观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监听者,类似监听器的存在,一旦被观察/监听的目标发生的情况,就会被监听者发现,这么想来目标发生情况到观察...【详细内容】
2021-12-13  唯一浩哥    Tags:Java   点击:(16)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条