如何使用JDBC操作数据库?一文带你吃透JDBC规范(一)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: 如何使用JDBC操作数据库?一文带你吃透JDBC规范(一)

1. 前言

大家好,我是橙子。最近又肝了几个大夜,总结了 JDBC 完整版的基础教程和实战案例训练。快来看看这些 Java 基础性的代码你有没有忘记?

1.1.png

在 Java 开发中,使用 Java 语言操作数据库是非常重要的一部分,那么 Java 语言是如何操作数据库的呢?

我们需要使用不同厂商的数据库时,例如 MySQL,Oracle 等,显然一套 Java 代码是不能同时操作不同的数据库的,那么怎样实现一套 Java 代码对不同的数据库的操作呢?

1.2.png

JDBC 应运而生,JDBC 是使用 Java 语言操作关系型数据库的一套 API,其中定义了对数据库操作的规范,这里的规范在 Java 中就是指接口,不同的数据库厂商提供了不同的 JDBC 实现类,称为驱动,使用时,只需要导入需要的数据库驱动 jar 包,便可以操作不同的关系型数据库,其实际是使用了 jar 包中的实现类。

本系列文章的学习目标:

什么是 JDBC,如何使用?

使用 JDBC 完成数据的增删改查操作

JDBC API 熟练使用

学会使用Java代码操作数据库,本文是2 万字学习教程,阅读本文需要20 分钟,建议收藏长期学习使用。

2. JDBC 概述

2.1 概念

JDBC , Java DataBase Connectivity , 是使用 Java 语言操作关系型数据库的一套API。本质上来说,JDBC 中定义了一套操作关系型数据库的规范,但是我们不能直接使用这套接口来操作数据库,于是各大数据库厂商提供了 JDBC 不同的实现类,称为驱动,例如 MySQL 驱动,此时,我们只需要操作 JDBC 规范好的代码便可以完成对数据库的操作。在实现对数据库的操作时,其底层是使用了 jar 包中定义的实现类。

1.3.png

当我们使用不同的数据库时,例如测试时使用 MySQL 数据库,部署时使用 Oracle 数据库,只需要编写一套 Java 代码便可以实现对不同关系型数据库的操作。

2.2 优点

使用 JDBC 操作关系型数据库时,各大数据库厂商提供了不同的实现类,我们不需要针对不同的数据库进行单独开发,因此,我们也可以随时替换数据库,而不用大量修改 Java 代码。

我们只需要在使用时导入需要使用的数据库对应的驱动 jar包到项目中,便可以实现对指定数据库的操作,使 Java 操作数据库变得轻松便捷。

下图就是MySQL的驱动jar包:

1.4.png

不同版本的 jar 包可以在官网下载。

3. JDBC 快速入门

使用 Java 语言操作数据库,实际上就是 Java 代码将 sql 语句发送到 MySQL 数据库服务端,MySQL 服务端接收并执行 sql 语句,同时返回一个执行结果,最后该结果会发送到 Java 代码进行处理。

对数据库的操作大致分为以下几个步骤:

1.注册驱动

2.获取连接

3.编写sql

4.获取执行sql对象

5.执行sql

6.处理返回结果

7.释放资源

在进行编码之前,需要先创建工程,导入数据库的驱动 jar 包。这里以 JDBC 操作 MySQL 数据库为例,步骤如下:

创建空项目,定义项目名称及路径:

1.5.png

在File / Project Structure 中进行项目设置,JDK版本,编译版本等:

1.6.png

创建新模块,指定名称及位置:

1.7.png

在 jdbc-demo 模块中新建 Lib 文件夹:

1.8.png

将下载好的 MySQL驱动 jar 包导入 Lib 目录下作为库文件:

1.9.png

右键 MySQL 驱动 jar 包,点击 Add as Library ,并选择模块下有效:

1.10.png

创建好工程以后,就可以编写代码操作数据库啦!

在 idea 中右键 src / new / Java Class ,输入类名 JDBCDemo 创建一个新的类。

第一步:注册驱动

//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");

目的是把 Driver 类加载到内存中。其实,Java 中用于注册驱动的是 registerDriver() 方法,而在 Driver 类中使用了该方法,所以只要把 Driver 类加载到内存中 ,包含 registerDriver 方法的静态代码块就会执行,驱动就会被注册。我们可以查看JDK源码中的Driver 类:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

第二步:获取连接

String url="jdbc:mysql://localhost:3306?useSSL=false";
String username="root";
String pass word="abc123";
Connection conn = DriverManager.getConnection(url, username, password);

目的是通过 getConnection() 方法获取数据库连接 Connection 类对象,这里没有直接把数据传入方法之中,而是定义了三个局部变量,url,数据库用户名和密码,其中 url 参数有固定的语法格式:

jdbc:mysql://ip地址(域名):端口号?参数键值对1&参数键值对2

第三步:定义sql

String sql="update account set money=2000 where name='张三'";

目的是定义操作数据库的 sql 语句。

第四步:获取执行sql的对象

Statement stmt = conn.createStatement();

目的是在执行 sql 语句之前,先获取执行 sql 的对象 statement。

第五步:执行sql

int count = stmt.executeUpdate(sql);

目的是为了将 sql 发送到数据库服务端,让数据库服务端执行完 sql,并且返回一个结果,这里的 executeUpdate() 返回受影响的行数。

第六步:处理返回结果

System.out.println(count);

目的是处理数据库服务端返回的 sq l执行结果,这里我们打印了 executeUpdate() 方法返回的受影响的行数。

第七步:释放资源

stmt.close();
conn.close();

目的是在完成对数据库的操作以后释放资源,否则数据库文件将一直被被程序占用,释放资源时遵循先开后释放的原则。

使用 JDBC 操作 MySQL 数据库完整代码:

public class JDBCDemo {
    public static void main(String[] args) throws Exception {
        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取连接
        String url="jdbc:mysql://localhost:3306/db1?useSSL=false";
        String username="root";
        String pass word="abc123";
        Connection conn = DriverManager.getConnection(url, username, password);
        //3.定义sql
        String sql="update account set money=2000 where name='张三'";
        //4.获取执行sql的对象
        Statement stmt = conn.createStatement();
        //5.执行sql
        int count = stmt.executeUpdate(sql);
        //6.处理结果
        System.out.println(count);
        //7.释放资源
        stmt.close();
        conn.close();
    }
}

4. JDBC API详解

4.1 DriverManager

DriverManager ,驱动管理类。在 JDBC 入门篇中,我们使用了该类的方法来注册驱动和获取连接。

DriverManager 类主要有两个作用:

1.注册驱动

2.获取连接


4.1.1 注册驱动


在 Driver 类静态代码块中 DriverManager 类执行了其 registerDriver() 方法用于注册驱动,当我们把类Driver 加载到内存中后,该静态代码块就会执行,此时就完成了驱动注册,我们就是使用这样的方法注册驱动的。查看 JDK 源码就不难理解:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

需要注意的是,在 MySQL 5 以后的驱动 jar 包可以不用进行此步骤来注册驱动,当我们把 jar 导入到项目以后,程序会自动加载 jar 包中 META-INF/services/java.sql.Driver 文件中的驱动类。

4.1.2 获取连接


我们使用 DriverManager 类的 getConnection() 静态方法来获取数据库连接对象,其方法有三个参数,分别是 url,数据库的用户名和密码。url 有其固定的语法格式:

jdbc:mysql://ip地址(域名):端口号/连接的数据库名?参数键值对1&参数键值对2
jdbc:mysql://连接的数据库名?参数键值对1&参数键值对2 //如果使用的是本地的 MySQL ,并且使用默认的端口号 3306 的,url 的值可以简写

例如:

jdbc:mysql://db1?useSSL=false

这里 useSSL = false 关闭了安全连接方式,解决了 idea 警告的问题。

4.2 Connection


Connection ,数据库连接对象接口。在入门篇中使用了该类获取 sql 的执行对象 statement。

Connection 接口的作用有:

1.获取执行 SQL 的对象

2.管理事务


4.2.1 获取执行sql的对象


Connection 中的 createStatement() 方法可以获取执行 sql 的对象 Statement ,用于把 sql 发送到数据库服务端。使用 preparedStatement() 方法可以获取预编译 sql 的执行 sql 的对象,这个方法可以有效的防止 sql 注入的问题。

4.2.2 事务管理


在JDBC 中使用 Connection 对象进行事务管理,Connection 中定义了三个对应的方法:

开启事务:

void setAutoCommit(boolean autoCommit) //将此连接的自动提交模式设置为给定状态

回滚事务:

void rollback() //撤消当前事务中所做的所有更改,并释放此 Connection对象当前持有的所有数据库锁

提交事务:

void commit() //使自上次提交/回滚以来所做的所有更改成为永久更改,并释放此 Connection对象当前持有的所有数据库锁


示例:

public class JDBCDemo {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
        String url = "jdbc:mysql://db1?useSSL=false";
        String username = "root";
        String ppassword = "abc123";
        Connection conn = DriverManager.getConnection(url, username, password);
        //3. 定义sql
        String sql1 = "update account set money = 3000 where id = 1";
        String sql2 = "update account set money = 3000 where id = 2";
        //4. 获取执行sql的对象 Statement
        Statement stmt = conn.createStatement();
        try {
            // 开启事务
            conn.setAutoCommit(false);
            //5. 执行sql
            int count1 = stmt.executeUpdate(sql1);//受影响的行数
            //6. 处理结果
            System.out.println(count1);
            int i = 3 / 0;
            //5. 执行sql
            int count2 = stmt.executeUpdate(sql2);
            //6. 处理结果
            System.out.println(count2);
            // 提交事务
            conn.commit();
        } catch (Exception e) {
            // 回滚事务
            conn.rollback();
            e.printStackTrace();
        }
        //7. 释放资源
        stmt.close();
        conn.close();
    }
}

我们使用 Java 中的异常捕获机制来进行事务的管理。将执行 sql 的语句放在 try 代码块中,并且使用 setAutoCommit() 方法开启事务,如果代码块中没有出现异常,则使用 commit() 方法提交事务,如果 try 代码块某处出现了异常,则需要回滚事务,所以将回滚事务的 rollback() 方法定义在 catch 语句块中。

4.3 Statement


Statement 类的对象用来执行 sql 语句,即把 sql 发送到数据库服务端,但是使用此类对象执行 sql 会出现 sql 注入的问题。不同的 sql 语句使用不同的方法执行,执行 DDL,DML 语句使用下面的方法:

int executeUpdate(String sql)//执行sql语句完成增删改操作

在执行 DML 语句完成对数据的增删改操作时,该方法返回数据表中受影响的行数,可以使用这个返回值来判断是否成功完成对数据的操作。而使用 DDL 来操作数据库和数据表时,返回值可能为 0,所以不能用作上面的判断,且在开发中 DDL 很少被用到。

示例1,使用 DML 修改数据:

//3.定义sql
String sql="update account set money=2000 where name='张三'";
//5.执行sql
int count = stmt.executeUpdate(sql);
//6.处理结果
if(count>0){
    System.out.println("修改成功!");
}else{
     System.out.println("修改失败!");
}

示例2,使用 DDL 删除数据库:

//3.定义sql
String sql="drop database db2";
//4.获取执行sql的对象
Statement stmt = conn.createStatement();
//5.执行sql
int count = stmt.executeUpdate(sql);
//6.处理结果
System.out.println(count);

此时,控制台返回了 0 ,但是对数据库的删除操作已经完成。

1.11.png

执行 DQL 语句时需要使用下面的方法:

ResultSet excuteQuery(String sql)//执行sql语句完成查询操作,返回单个ResultSet对象

该方法在下面 ResultSet类中讲解。

4.4 ResultSet


ResultSet ,结果集对象类,其作用是封装 sql 查询语句的结果。执行了 DQL 查询语句后就会返回该类的对象,执行 DQL 语句的方法如下:

ResultSet excuteQuery(String sql)//执行sql语句完成查询操作,返回单个ResultSet对象

ResultSet 类提供了操作查询结果数据的方法,如下:

/*
作用:将指针从当前位置移动到下一行,然后这一行是否为有效行
返回值:true 当前行为有效行,false 当前行没有数据
*/
boolean next()
/*
作用:获取数据  
xxx表示数据类型 例如 int getInt() 
参数:int类型的参数表示列的编号,这个编号从0开始;String类型的参数,表示列的名称
*/
xxx getXxx(参数)

 

操作查询结果数据的方法如下图:

1.12.png

一开始指针位于第一行前,如图所示红色箭头指向于表头行。当我们调用了 next() 方法后,指针就下移到第一行数据,并且方法返回 true,此时就可以通过 getInt("id") 获取当前行 id 字段的值,也可以通过 getString("name") 获取当前行 name 字段的值。如果想获取下一行的数据,继续调用 next() 方法,以此类推。获取某个字段的值时,既可以传入 int 类型,即列的编号,也可以传入列对应的字段名。

示例,查询 account 表中数据,并且打印所有结果:

public class JDBCDemo {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
        String url = "jdbc:mysql://localhost:3306/db1?useSSL=false";
        String username = "root";
        String ppassword = "abc123";
        Connection conn = DriverManager.getConnection(url, username, password);
        //3. 定义sql
        String sql = "select * from account";
        //4. 获取执行sql对象
        Statement stmt = conn.createStatement();
        //5. 执行sql
        ResultSet rs = stmt.executeQuery(sql);
        //6. 处理返回结果, 遍历rs中的所有数据
        // 1 指针向下移动一行,并且判断当前行是否有数据
        while (rs.next()) {
            // 2 获取数据  getXxx()
            int id = rs.getInt("id");
            String name = rs.getString("name");
            double money = rs.getDouble("money");
            System.out.println(id);
            System.out.println(name);
            System.out.println(money);
        }
        //7. 释放资源
        rs.close();
        stmt.close();
        conn.close();
    }
}

4.5 PreparedStatement


4.5.1 sql注入问题


前面 statement 类的对象用来执行sql语句,例如 :

int executeUpdate(String sql)//执行sql语句完成增删改操作


但是使用此方法存在 sql 注入的问题,什么是 sql 注入呢?这里做一个大致的讲解。

SQL注入是通过操作输入来修改事先定义好的 sql 语句,用来达到执行代码对服务器进行攻击的方法。例如,在程序的登录操作中,用户输入的用户名和密码会被发送到 Java 代码,然后用于 Java 操作数据库的sql 语句中,只有当用户输入的用户名和密码与数据库中的数据匹配时才能实现登录,但是,只要我们输入事先定义好的语句,便可以进行破解。例如下面拼字符串的方式修改 sql 语句原来的含义:

String name ="lisi";
String ppwd="' or '1' ='1";//事先定义好的输入,拼接到sql语句中后改变其含义
String sql = "Select * from tb_user where name='"+username+"' and ppassword='"+ppwd+"'";


此时,将用户名和密码拼接到 sql 中,如下:

select * from tb_user where username = 'lisi' and ppassword = ''or '1' = '1';


可以看到,sql 中的判断条件永远为 true ,不管输入什么样的用户名,这条 sql 都成立,实现了 sql 注入。

1.13.png

为了解决这个问题,出现了 preparedStatement ,该类用于预编译 sql 语句并执行,其优点是可以防止sql 注入,并且预编译sql提高了性能。其实底层是将特殊字符进行了转义,转义的 sql 如下:

select * from tb_user where username = 'lisi' and ppassword = '\'or \'1\' = \'1'


示例:

public class JDBCDemo {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接
        String url="jdbc:mysql://localhost:3306/db1?useSSL=false";
        String username="root";
        String ppassword="abc123";
        Connection conn = DriverManager.getConnection(url,username,password);
        //Java代码接收到客户端发送的用户名和密码
        String name="lisi";
        String ppwd="' or '1' ='1";
        //3. 定义sql
        String sql="select * from tb_user where username=? and password=?";
        //4. 获取执行sql的对象psmt
        PreparedStatement psmt = conn.prepareStatement(sql);
        //设置问号的值
        psmt.setString(1,name);
        psmt.setString(2,pwd);
        //5. 执行sql
        ResultSet rs = psmt.executeQuery();//此时不再需要传入sql语句
        //6. 处理返回结果
        if(rs.next()){
            System.out.println("yes");
        }else{
            System.out.println("no");
        }
        //7. 释放资源
        rs.close();
        psmt.close();
        conn.close();s
    }
}

1.14.png


4.5.2 preparedStatement 原理


前面使用 preparedStatement 解决了 sql 注入的问题,其实我们还没有开启预编译的功能,JDBC 中是如何通过预编译来提高性能的呢?

要学习 prepareStatement 实现预编译的原理,首先要明白 Java 操作数据库的步骤:

首先 Java 代码将 sql 发送到 MySQL 服务端,MySQL 服务端接收到 sql 语句以后,会对 sql 语句进行检查(检查 sql 语句的语法),编译(编译 sql 语句,将 sql 语句编译成可执行的函数),执行的操作。而检查和编译 sql 语句花费的时间往往较长,如果想要提高 sql 的性能,就可以从这方面入手。在使用预编译的方法时,检查和编译 sql 语句的操作将会在获取执行 sql 的对象时完成,并且不会重复执行,从而提高了性能。

1.15.png

要想打开预编译的功能,就需要在 url 中设置如下的参数:

useServerPrepStmts=true //参数开启预编译功能


示例:

public class JDBCDemo {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接
        String url="jdbc:mysql://localhost:3306/db1?useSSL=false&useServerPrepStmts=true";
        String username="root";
        String ppassword="abc123";
        Connection conn = DriverManager.getConnection(url,username,password);
        //Java代码接收到客户端发送的用户名和密码
        String name="lisi";
        String ppwd="'or '1' = '1";
        //3. 定义sql
        String sql="select * from tb_user where username=? and password=?";
        //4. 获取执行sql的对象psmt
        PreparedStatement psmt = conn.prepareStatement(sql);
        //设置问号的值
        psmt.setString(1,name);
        psmt.setString(2,pwd);
        ResultSet rs=null;
        //5. 执行sql
        rs = psmt.executeQuery();//此时不需要传入sql语句
        psmt.setString(1,"zhangsan");
        psmt.setString(2,"abc123");
        rs=psmt.executeQuery();
        //6. 处理返回结果
        if(rs.next()){
            System.out.println("yes");
        }else{
            System.out.println("no");
        }
        //7. 释放资源
        rs.close();
        psmt.close();
        conn.close();
    }
}


在获取 sql 的执行对象时,sql 会作为参数被发送到 MySQL 服务端进行检查和编译,在执行时,不再进行第二次检查和编译的操作,提高了 sql 执行的性能。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
3月前
|
Java 关系型数据库 数据库连接
JDBC:Java与数据库的“黄金搭档”,为何它如此重要?
JDBC:Java与数据库的“黄金搭档”,为何它如此重要?
47 8
|
3月前
|
Java 数据库连接 API
JDBC:Java数据库连接的“黑科技”大揭秘
JDBC:Java数据库连接的“黑科技”大揭秘
37 7
|
3月前
|
存储 SQL 关系型数据库
数据库开发设计规范(通用)
数据库开发设计规范(通用)
254 0
|
1月前
|
SQL Java 数据库
Springboot+spring-boot-starter-data-jdbc实现数据库的操作
本文介绍了如何使用Spring Boot的spring-boot-starter-data-jdbc依赖来操作数据库,包括添加依赖、配置数据库信息和编写基于JdbcTemplate的数据访问代码。
50 2
|
1月前
|
SQL druid Java
JDBC和数据库连接池-两个工具类-JDBCUtilsByDruid和BasicDAO
JDBC和数据库连接池-两个工具类-JDBCUtilsByDruid和BasicDAO
29 0
|
3月前
|
Java 数据库连接 网络安全
JDBC数据库编程(java实训报告)
这篇文章是关于JDBC数据库编程的实训报告,涵盖了实验要求、实验环境、实验内容和总结。文中详细介绍了如何使用Java JDBC技术连接数据库,并进行增删改查等基本操作。实验内容包括建立数据库连接、查询、添加、删除和修改数据,每个部分都提供了相应的Java代码示例和操作测试结果截图。作者在总结中分享了在实验过程中遇到的问题和解决方案,以及对Java与数据库连接操作的掌握情况。
JDBC数据库编程(java实训报告)
|
3月前
|
SQL Java 数据库连接
JDBC之旅:从陌生到熟悉的Java数据库连接之路
JDBC之旅:从陌生到熟悉的Java数据库连接之路
28 8
|
3月前
|
SQL Java 数据库连接
Java开发者必知:JDBC连接数据库的“三大法宝”
Java开发者必知:JDBC连接数据库的“三大法宝”
34 7
|
3月前
|
SQL 安全 Java
JDBC:Java与数据库的“月老红线”,你真的了解它吗?
JDBC:Java与数据库的“月老红线”,你真的了解它吗?
22 1
|
3月前
|
SQL Java 数据库连接
Hibernate 是一款开源 ORM(对象关系映射)框架,封装了 JDBC,允许以面向对象的方式操作数据库,简化了数据访问层的开发。
Hibernate 是一款开源 ORM(对象关系映射)框架,封装了 JDBC,允许以面向对象的方式操作数据库,简化了数据访问层的开发。通过映射机制,它可以自动处理对象与数据库表之间的转换,支持主流数据库,提高了代码的可移植性和可维护性。其核心接口包括 SessionFactory、Session 和 Transaction 等,通过它们可以执行数据库的 CRUD 操作。配置方面,需在项目中引入 Hibernate 及数据库驱动依赖,并创建 `hibernate.cfg.xml` 配置文件来设置数据库连接和 Hibernate 行为参数。
47 1