创作不易,如果觉得这篇文章对你有帮助,欢迎各位老铁点个赞呗,您的支持是我创作的最大动力!

1 前言

项目开发中,操作数据库是必不可少的,常用的操作数据库的框架,如:MyBatisJdbcTemplate等有很多。但是,无论使用哪种框架操作数据库,最底层的api实现都是JDBC,就是说,在开发中,JDBC有着举足轻重的地位,是最基础也是最核心的。

2 JDBC概述

2.1 JDBC定义

Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。我们通常说的JDBC是面向关系型数据库的。(百度百科

JDBC是Java语言连接数据库系统,JDBC接口是一套class文件,由SUN公司负责制定JDBC规范。

2.2 JDBC接口调用方和实现方

  • JDBC接口调用方
    JDBC一般都是由程序员调用,用来连接数据库并且操作数据库。

  • JDBC接口实现方
    数据库的厂商有很多,对应的数据库产品也很多,例如:MySQLOracleDB2、SQLServer 等。各数据库厂商的程序员,负责编写SUN公司制定的JDBC接口的实现类

2.3 接口的作用

接口是一套规范,程序员应该面向接口去调用,而不需要关心接口底层的具体实现

接口的作用:

  • 面向抽象编程,面向接口编程,尽量使用多态机制
  • 可以提高程序的扩展力,降低程序的耦合度
  • 让程序变得具有很强的可接插特性(可插拔)

2.4 连接数据库驱动

  • SUN公司负责制定JDBC规范,各数据库厂商编写的JDBC接口的实现类,编译之后将这些实现类打成 jar 包并且发布。
  • 所有需要连接数据库的程序员,都需要从官网上下载这些jar再使用,这些专用驱动jar包通常被我们称为连接数据库的驱动

2.5 JDBC原理

在这里插入图片描述

3 使用JDBC编程之前,先介绍几个概念

因为数据库连接需要使用到APIURL,下面简单介绍下API和URL的概念,老铁们有个简单的认识。

3.1 API

我们通常,会使用官网的API进行编程,那么,什么是API呢?

定义: Application Programming Interface应用程序编程接口,就是一套类库

Java中的API包括三个元素: API字节码API源码API帮助文档
以上三个元素的版本要保持一致

例如:JDK1.8源码、JDK1.8字节码 和JDK1.8帮助文档,三者的版本要保持一致

3.2 URL

连接数据库时,需要使用URL连接,那么,什么是URL?

URl定义:
统一资源定位系统(uniform resource locator;URL)是因特网的万维网服务程序上用于指定信息位置的表示方法。它最初是由蒂姆·伯纳斯·李发明用来作为万维网的地址。现在它已经被万维网联盟编制为互联网标准RFC1738。(百度百科)

  • URL是统一资源定位符,代表网络中的某个资源的绝对路径

  • 通过URL可以定位网络中的资源

  • URL主要包含四部分:协议IP端口号资源名称
    协议是: 提前制定好的通信数据格式、规范,按照这种特定格式发送数据包,对方收到数据包之后,也按照这种规范解析这个数据包,获取有价值数据
    IP作用: 定位计算机
    端口号作用: 定位服务

数据库中的URL:
如:MySQLOracle

  • MySQL数据库:jdbc:mysql://localhost:3366/databaseName
    a、jdbc:mysql://表示协议
    b、localhost表示IP
    c、3366表示端口号
    d、databaseName表示数据库名称
  • Oracle 数据库:jdbc:oracle:thin:@localhost:1521:databaseName
    a、jdbc:oracle:thin:@表示协议
    b、locahost表示IP
    c、1521表示端口号
    d、databaseName表示数据库名称

3.3 SQL的分类

  • 数据库查询语言(DQL
    简称:DQL,Data Query Language
    代表关键字:select

    DQL主要是查询表中的数据

  • 数据库操作语言(DML
    简称:DML,Data Manipulation Language
    代表关键字:insertdeleteupdate

    DML主要是增、删、改表中的数据

  • 数据库定义语言(DDL
    简称:DDL,Data Denifition Language
    代表关键字:createdropalter

    DDL主要是创建、删除、修改表的结构

  • 事务控制语言(TCL
    简称:TCL,Trasactional Control Language
    代表关键字:commitrollback

    TCL主要是对jdbc中的事务进行提交和回滚操作

  • 数据控制语言(DCL
    简称:DCL,Data Control Language
    代表关键字:grantrevoke

  • DMLDDL的区别是什么
    DML是数据库操作语言,主要是修改数据库表中的数据,而DDL是数据库定义语言,主要修改数据中表的结构

下面,是时候表演真正的技术啦。

4 JDBC编程六步曲

下边,笔者以MySQL数据库为例

需要mysql驱动依赖:

	<dependency>
	   <groupId>mysql</groupId>
	   <artifactId>mysql-connector-java</artifactId>
	   <version>8.0.15</version>
	</dependency>

4.1 第一步:注册驱动

  • 创建驱动对象,告知JDBC我们即将连接哪个数据库
  • 通过DriverManager.registerDriver(driver)注册驱动

【代码示例】

	/**
     * <p>
     * 第一步:注册驱动
     * 通过创建驱动对象告知JDBC,我们即将连接哪个厂商的数据库
     * <p/>
     *
     * @param
     * @return void
     * @Date 2020/6/7 11:23
     */
    public static void registerDriver() {
        try {
            //注册驱动(new com.mysql.jdbc.Driver()也是可以的)
            Driver driver = new com.mysql.cj.jdbc.Driver();
            DriverManager.registerDriver(driver);

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

4.2 第二步:获取数据库连接

  • 通过DriverManager.getConnection(url,user,pwd)获取连接
  • Connection连接对象不能随意创建,最后使用完要手动关闭

【代码示例】

	/**
     * <p>
     * 第二步:获取数据库连接
     * 注意:Connection连接对象不能随意创建,最后使用完需要手动关闭
     * <p/>
     *
     * @param
     * @return java.sql.Connection
     * @Date 2020/6/7 11:24
     */
    public static Connection getConnection() {
        try {
            //注册驱动
            registerDriver();

            //获取数据库连接
            //url统一资源定位
            String url = "jdbc:mysql://ip:3306/test";
            String userName = "zhangsan";
            String passWord = "zhangsan@";
            Connection connection = DriverManager.getConnection(url, userName, passWord);

            //打印Connection对象的内存地址:com.mysql.cj.jdbc.ConnectionImpl@69d9c55
            System.out.println("MySql数据库,对java.sql.Connection接口的实现类的完整类名:" + connection);
            return connection;
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return null;
    }
  • MySQL数据库默认支持的最多连接数量为100
    我们大家都知道MySQL最大连接数的默认值是100,这个数值对于高并发下的应用是远远不够的,当连接请求大于默认连接数后,就会出现无法连接数据库的错误,因此我们需要把它适当调大一些。

    在使用MySQL数据库的时候,经常会遇到这么一个问题,就是“Can not connect to MySQL server. Too many connections”-mysql 1040错误,这是因为访问MySQL且还未释放的连接数目已经达到MySQL的上限。通常,mysql的最大连接数默认是100,最大可以达到16384。

    MySQL查看最大连接数和修改最大连接数:

    • 查看最大连接数
      show variables like '%max_connections%';

    • 修改最大连接数
      set GLOBAL max_connections = 2000;

      也可以通过修改配置文件来修改mysql最大连接数(max_connections),这种方式说来很简单,只要修改MySQL配置文件my.inimy.cnf的参数max_connections,将其改为max_connections=2000,然后重启MySQL即可。

4.3 第三步:获取数据库操作对象

  • 一个数据库连接对象可以创建多个数据库操作对象
  • 通过conn.createStatement()获取数据库操作对象

【代码示例】

	/**
     * <p>
     * 第三步:获取数据库操作对象
     * 拿到Statement数据库操作对象,就可以执行sql语句
     * <p/>
     *
     * @param
     * @return java.sql.Statement
     * @Date 2020/6/7 11:42
     */
    public static Statement getStatement() {
        try {
            //获取数据库连接
            Connection connection = getConnection();

            //获取数据库操作对象
            //一个连接对象,可以获取多个数据库操作对象
            Statement statementOne = connection.createStatement();
            //打印Statement对象的内存地址:com.mysql.cj.jdbc.StatementImpl@23f7d05d
            System.out.println("MySql数据库,对java.sql.Statement接口的实现类的完整类名:" + statementOne);

            //打印Statement对象的内存地址:com.mysql.cj.jdbc.StatementImpl@1e730495
            //Statement statementTwo = connection.createStatement();
            //System.out.println("MySql数据库,对java.sql.Statement接口的实现类的完整类名:" + statementTwo);

            return statementOne;
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return null;
    }

4.4 第四步:执行SQL语句

  • 通过数据库操作对象statement.executeUpdate(sql)编译执行SQL
  • JDBC编写SQL语句不需要以分号结尾
  • 数据库管理系统会将编写好的SQL语句编译并执行
    【代码示例】
	/**
     * <p>
     * 第四步:执行SQL语句
     * <p/>
     *
     * @param
     * @return void
     * @Date 2020/6/7 11:51
     */
    public static void executeSql() {
        try {
            //获取数据库操作对象
            Statement statement = getStatement();
            //执行SQL语句
            //注意:jdbc中,sql语句不需要以分好;结尾,当然,你写了也不会报错

            //定义DDL语句,这里可以创建表,但是,一般不这么玩,jdbc中,通常是写DML语句和DQL语句
            /*String sql = "CREATE TABLE `user` (\n" +
                    "  `id` bigint(32) NOT NULL auto_increment,\n" +
                    "  `userName` varchar(20) DEFAULT NULL COMMENT '用户名称',\n" +
                    "  `email` varchar(128) DEFAULT NULL COMMENT '邮箱|登录帐号',\n" +
                    "  `passWord` varchar(64) DEFAULT NULL COMMENT '密码',\n" +
                    "  `createTime` datetime DEFAULT NULL COMMENT '创建时间',\n" +
                    "  `lastLoginTime` datetime DEFAULT NULL COMMENT '最后登录时间',\n" +
                    "  `status` bigint(1) DEFAULT '1' COMMENT '1:有效,0:禁止登录',\n" +
                    "  PRIMARY KEY (`id`)\n" +
                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8";*/

            //定义DML语句
            String sql = StringUtils.join("insert into `user`(`userName`,`passWord`,`status`) values(", "'lisi','123','1')");

            //将sql语句发送给数据库管理系统,数据库管理系统(DBMS)会编译并执行该sql语句
            int count = statement.executeUpdate(sql);

            System.out.println(count);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

4.5 第五步:处理查询结果集

处理查询结果集,获取当前光标指向行中的数据,有三种方式:

  • 第一种方式:根据字段下标获取,不管数据库表中的字段是什么类型,都以字符串方式取出,JDBC所有下标都是以1开始

  • 第二种方式:通过结果集中字段名称获取数据,该方式的程序更加健壮,建议使用

  • 第三种方式:通过特定类型获取数据,该方式,明确知道字段的类型,可以节省类型转换花费的性能,该方式的程序更加健壮,性能更高,推荐使用

【代码示例】

/**
     * <p>
     * 第五步:处理查询结果集
     * <p/>
     *
     * @param
     * @return void
     * @Date 2020/6/7 12:48
     */
    public static void dealResultSet() {
        try {
            //获取数据库操作对象
            Statement statement = getStatement();
            //定义DQL语句
            String sql = StringUtils.join("select * from `user` where userName = 'lisi'");

            //将sql语句发送给数据库管理系统,数据库管理系统(DBMS)会编译并执行该sql语句,把查询结果集放到ResultSet结果集对象中
            ResultSet resultSet = statement.executeQuery(sql);

            //处理查询结果集,一个Statement可以得出多个ResultSet
            //resultSet.next()方法作用:if the new current row is valid, return true. if there are no more rows, return false
            //将光标向前移动一行,如果指向当前行有记录,则返回true,若指向当前行无记录,返回false
            while (resultSet.next()) {
                //获取当前光标指向行中的数据,有三种方式:

                //第一种方式:根据字段下标获取,不管数据库表中的字段是什么类型,都以字符串方式取出
                //JDBC所有下标都是以1开始
                String id1 = resultSet.getString(1);
                String userName1 = resultSet.getString(2);
                String status1 = resultSet.getString(7);
                System.out.println("id1:" + id1 + ",用户名1:" + userName1 + ",有效状态1:" + status1);

                //第二种方式:通过结果集中字段名称获取数据,该方式的程序更加健壮,建议使用
                String id2 = resultSet.getString("id");
                String userName2 = resultSet.getString("userName");
                String status2 = resultSet.getString("status");
                System.out.println("id2:" + id2 + ",用户名2:" + userName2 + ",有效状态2:" + status2);

                //第三种方式:通过特定类型获取数据,该方式,明确知道字段的类型,可以节省类型转换花费的性能,该方式的程序更加健壮,性能更高,推荐使用
                int id3 = resultSet.getInt("id");
                String userName3 = resultSet.getString("userName");
                String status3 = resultSet.getString("status");
                System.out.println("id3:" + id3 + ",用户名3:" + userName3 + ",有效状态3:" + status3);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

4.6 第六步:释放资源

  • 一个Connection可以创建多个Statement一个Statement可以得出多个ResultSet,所以先关闭ResultSet,再关闭Statement,最后关闭Connection
  • 需要关闭ResultSetStatementConnection
  • 为了保障能够释放资源,将释放代码编写到finally语句中
  • 分别进行try catch关闭资源

JDBC驱动对象的创建、连接、获取数据库操作对象,执行sql、处理结果集等,都需要消耗时间,其中,这里面涉及到的ResultSet、Statement、Connection对象,使用完了需要释放,否则,造成资源浪费,严重的,服务器宕机

在编程时,由于用MySQL语句调用数据库时,在每次执行语句前,会做一个临时的变量用来打开数据库,所以你在使用MySQL语句的时候,记得在每次调用完MySQL之后就关闭MySQL临时变量。

【代码示例】

 	//释放资源部分代码片段
    catch (SQLException e){
        e.printStackTrace();
    } finally{
        //第六步:释放资源,分别进行try catch
        //从小的开始关闭
        try {
            if (resultSet != null) {
                resultSet.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if (statement != null) {
                statement.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

4.7 JDBC编程六步曲小结

【代码示例】

	/**
     * <p>
     * JDBC编程六部曲
     * 第一步:注册驱动
     * 第二步:获取数据库连接
     * 第三步:获取数据库操作对象
     * 第四步:执行SQL语句
     * 第五步:处理查询结果集
     * 第六步:释放资源
     * <p/>
     *
     * @param
     * @return void
     * @Date 2020/6/7 13:32
     */
    public static void jdbcProgram() {
        //定义数据库连接对象
        Connection connection = null;
        //定义数据库操作对象
        Statement statement = null;
        //定义处理结果集对象
        ResultSet resultSet = null;

        try {
            //第一步:注册驱动,通过创建驱动对象告知JDBC,我们即将连接哪个厂商的数据库
            //注册驱动(new com.mysql.jdbc.Driver()也是可以的)
            Driver driver = new com.mysql.cj.jdbc.Driver();
            DriverManager.registerDriver(driver);

            //第二步:获取数据库连接
            //注意:Connection连接对象不能随意创建,最后使用完需要手动关闭
            //url统一资源定位
            String url = "jdbc:mysql://ip:3306/test";
            String userName = "zhangsan";
            String passWord = "zhangsan@";
            connection = DriverManager.getConnection(url, userName, passWord);

            //第三步:获取数据库操作对象
            //一个连接对象,可以创建多个数据库操作对象
            statement = connection.createStatement();

            //第四步:执行SQL语句
            //注意:jdbc中,sql语句不需要以分好`;`结尾,当然,你写了也不会报错
            //定义DQL语句,jdbc中,通常是写DML语句和DQL语句
            String sql = StringUtils.join("select * from `user` where userName = 'lisi'");

            //将sql语句发送给数据库管理系统,数据库管理系统(DBMS)会编译并执行该sql语句,把查询结果集放到ResultSet结果集对象中
            resultSet = statement.executeQuery(sql);

            //第五步:处理查询结果集
            //处理查询结果集,一个Statement可以得出多个ResultSet
            //resultSet.next()方法作用:if the new current row is valid, return true. if there are no more rows, return false
            //将光标向前移动一行,如果指向当前行有记录,则返回true,若指向当前行无记录,返回false
            while (resultSet.next()) {
                //获取当前光标指向行中的数据(通过特定类型获取数据)
                //该方式,明确知道字段的类型,可以节省类型转换花费的性能,该方式的程序更加健壮,性能更高,推荐使用
                int id = resultSet.getInt("id");
                String name = resultSet.getString("userName");
                String status = resultSet.getString("status");
                System.out.println("id:" + id + ",用户名:" + name + ",有效状态:" + status);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //第六步:释放资源,分别进行try catch
            //从小的开始关闭
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if (statement != null) {
                    statement.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }

5 JDBC编程六步曲优化

连接数据库的信息可在配置文件中进行配置,而不是硬编码到代码中

JDBC获取数据库连接有三种方式:

  • 通过键盘输入的方式(实际开发中也不会这样用)
  • 通过配置文件的方式(推荐使用)
  • 直接在数据库连接时,硬编码定义在程序中(不推荐)

5.1 通过键盘输入的方式(不推荐)

【代码示例】

	/**
     * <p>
     * 通过键盘输入的方式,获取数据库连接(实际开发中也不会这样用)
     * <p/>
     *
     * @param
     * @return java.sql.Connection
     * @Date 2020/6/7 14:14
     */
    public static Connection getConnectionByStream() {
        try {
            System.out.println("----方式1-从控制台,通过键盘输入获取连接数据库的相关信息-----");

            //键盘输入
            /*InputStream inputStream = System.in;
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader reader = new BufferedReader(inputStreamReader);*/
            //以上三行代码可以合为一行
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

            System.out.println("请输入数据库连接时注册驱动");
            String driver = reader.readLine();

            System.out.println("请输入数据库连接时的url");
            String url = reader.readLine();

            System.out.println("请输入数据库连接时的用户名user");
            String userName = reader.readLine();

            System.out.println("请输入数据库连接时的用户名密码");
            String passWord = reader.readLine();

            //关闭流
            reader.close();

            System.out.println("-----------------连接数据库--------------------");
            //注册驱动,这里必须是完整的类名(包名+类名)
            Class.forName(driver);
            //创建数据库连接
            Connection connection = DriverManager.getConnection(url, userName, passWord);
            return connection;
        } catch (IOException | ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }

        return null;
    }

5.2 通过配置文件的方式(推荐使用)

步骤:
1、创建db.properties配置文件,内容如下:

	#数据库连接配置
	db.driver=com.mysql.cj.jdbc.Driver
	db.url=jdbc:mysql://ip:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=PRC&useSSL=false
	db.username=zhangsan
	db.password=zhangsan@

2、通过FileReader fileReader = new FileReader(“db.properties”);读取配置文件
3、创建属性对象Properties pro = new Properties();
4、通过属性对象pro.load(reader)方法将配置信息读取到Map集合对象中(内存中)
5、关闭流 reader.close();
6、通过属性对象的getProperty(String key)方法获取配置文件中的value属性值
7、通过获取到的连接信息,创建数据库连接

【代码示例】

	/**
     * <p>
     * 通过配置文件的方式,获取数据库连接(推荐使用)
     * <p/>
     *
     * @param
     * @return java.sql.Connection
     * @Date 2020/6/7 14:14
     */
    public static Connection getConnectionByConfiguration() {
        try {
            System.out.println("-----------方式2-读取配置文件---------------");

            //1.读取配置文件
            InputStream inputStream = JDBCTest002.class.getClassLoader().getResourceAsStream("db.properties");
            //2.创建属性对象
            Properties pro = new Properties();
            //3.通过属性对象的load()方法将配置文件读取到流中(Map集合对象中(内存中))
            pro.load(inputStream);
            //4.关闭流
            inputStream.close();
            //5.通过属性对象的getProperty(String key)方法获取配置文件中的value属性值
            String driver = pro.getProperty("db.driver");
            String url = pro.getProperty("db.url");
            String userName = pro.getProperty("db.username");
            String passWord = pro.getProperty("db.password");

            System.out.println("-----------------连接数据库--------------------");
            //注册驱动,这里必须是完整的类名(包名+类名)
            Class.forName(driver);
            //创建数据库连接
            Connection connection = DriverManager.getConnection(url, userName, passWord);
            return connection;
        } catch (IOException | ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }

        return null;
    }

5.3 数据库连接时,硬编码在程序中(不推荐)

	 //第二步:获取数据库连接
	 //注意:Connection连接对象不能随意创建,最后使用完需要手动关闭
	 //url统一资源定位
	 String url = "jdbc:mysql://ip:3306/test";
	 String userName = "zhangsan";
	 String passWord = "zhangsan@";
	 connection = DriverManager.getConnection(url, userName, passWord);

6 Statement 和 PreparedStatement

JDBC中,Statement 和 PreparedStatement这两个都是数据库操作对象,都可以操作sql获取到想要的结果,但是,这两个还是有一些差别。

6.1 SQL注入现象

6.1.1 SQL注入定义

  • 定义
    SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息

    SQL是操作数据库数据的结构化查询语言,网页的应用数据和后台数据库中的数据进行交互时会采用SQL。而SQL注入是将Web页面的原URL、表单域或数据包输入的参数,修改拼接成SQL语句,传递给Web服务器,进而传给数据库服务器以执行数据库命令。如Web应用程序的开发人员对用户所输入的数据或cookie等内容不进行过滤或验证(即存在注入点)就直接传输给数据库,就可能导致拼接的SQL被执行,获取对数据库的信息以及提权,发生SQL注入攻击。 (百度百科

6.1.2 SQL注入实例

假设有一个场景: 用户通过进行用户名称,进行精确查询,用户输入的信息中有SQL关键字,并且参与了SQL语句的编译,导致SQL语句含义扭曲,最终导致了SQL注入。用户本来只应该查询到他自己的信息,但是却查出来了所有的用户信息。

SQL注入,简单来说,就是用一些SQL关键字,来导致一些不正常的现象,下面的这条sql,由于and优先级高于or,在用户录入userName = 'lisi' or 'hello' = 'hello'信息进行查询时,执行时sql就变成了select * from user where status = 1 and userName = 'lisi' or 'hello' = 'hello',导致SQL语句条件永远为真,会查出所有的用户信息数据。

select * from `user` where status = 1 and userName = {userName }

以上SQL注入原因分析: 由于and优先级高于or,该条sql最终执行时,查询条件被分为两部分(status = 1 and userName = 'lisi') or 'hello' = 'hello',导致SQL语句条件永远为真,会查出所有的用户信息数据。

6.1.3 如何解决SQL注入呢

定义SQL语句框架的时候,使用PreparedStatement数据库操作对象,这个是预编译对象,先将SQL语句框架进行了编译,然后给参数?动态赋值

  • 先定义 SQL 语句构架,然后对SQL语句进行预先编译,select * from user where status = ? and userName = ?

    ps = conn.prepareStatement(sql);
    在这里插入图片描述

  • 然后再接收用户提供的信息,即使用户提供的信息中包含SQL关键字,这些关键字也不参
    与编译,是不起作用的。

    主要修改的部分:
    在这里插入图片描述

    通过日志也可以看出,即使用户提供的信息中包含SQL关键字,这些关键字也不参
    与编译,是不起作用的
    预编译SQL:select * from user where status = ? and userName = ?
    赋值后,编译的SQL: select * from user where status = ‘1’ and userName = ‘\'lisi\' or \'hello\' = \'hello\'’,其实编译后的sql,是把用户录入的'lisi' or 'hello' = 'hello',整体作为了一个字符串,即执行的sql是:select * from user where status = ‘1’ and userName = “'lisi\' or \'hello\' = \'hello\',这样就不会发生SQL注入问题了。

6.2 Statement 和 PreparedStatement 对比

  • PreparedStatement可以防止 SQL 注入,执行效率高
  • SQL语句对于Statement来说是:编译一次执行一次
  • SQL语句对于PreparedStatement来说,是编译一次执行N次
    原因:数据库管理系统(DBMS)厂商实现了JDBC接口,DBMS将编译后的SQL语句保存在DBMS中,由于DBMS中有很多编译好的SQL语句,这时通过同一个PreparedStatement对象进行赋值,便会找到其对应的PreparedStatement对象,从而实现其参数的赋值,即:一次编译多次执行。
  • PreparedStatement是类型安全的,编译期可以检查传入参数类型

7 JDBC事务

7.1 事务的定义

事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。(百度百科

事务是恢复和并发控制的基本单位

7.2 事务的四个特性

事务应该具有4个属性:原子性一致性隔离性持久性,这四个属性通常称为ACID特性。

  • 原子性(atomicity)
    一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。

  • 一致性(consistency)
    事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

  • 隔离性(isolation)
    一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  • 持久性(durability)
    持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

7.3 JDBC中的事务

操作数据库时,肯定会有事务的存在,那么,JDBC中的事务是怎么使用的呢?

  • JDBC默认情况下,事务是自动提交的:即在JDBC中执行一条DML语句就执行了一次事务
  • 将事务的自动提交,修改为手动提交即可避免自动提交
  • 在事务执行的过程中,任何一步出现异常都要进行回滚

JDBC中使用事务只有三行代码:

  • 设置手动提交事务:conn.setAutoCommit(false);
  • 事务提交:conn.commit();
  • 事务回滚:conn.rollback();

【代码示例】
在获取数据库连接后面开启事务,在 catch 语句块中进行事务回滚
在这里插入图片描述

8 JDBC编程六步曲最终版

经过以上详细的介绍,相信童鞋们也对JDBC编程有了详细的认识,下面,给出最终版的JDBC编程六部曲代码,以后使用的时候,可以直接copy,进行简单修改即可。

【代码示例】

	/**
     * <p>
     * JDBC编程六部曲
     * 第一步:注册驱动
     * 第二步:获取数据库连接
     * 第三步:获取数据库操作对象
     * 第四步:执行SQL语句
     * 第五步:处理查询结果集
     * 第六步:释放资源
     * <p/>
     *
     * @param
     * @return void
     * @Date 2020/6/7 15:50
     */
    public static void jdbcProgramFinal() {
        //定义数据库连接对象
        Connection connection = null;
        //定义数据库操作对象
        PreparedStatement preparedStatement = null;
        //定义处理结果集对象
        ResultSet resultSet = null;

        try {
            //第一步:注册驱动,通过创建驱动对象告知JDBC,我们即将连接哪个厂商的数据库
            //注册驱动(new com.mysql.jdbc.Driver()也是可以的)
           /* Driver driver = new com.mysql.cj.jdbc.Driver();
            DriverManager.registerDriver(driver);*/

            //第二步:获取数据库连接(这里面已经注册过驱动了,如果使用这种方式,上面就不用注册驱动了)
            //注意:Connection连接对象不能随意创建,最后使用完需要手动关闭
            //通过配置文件的方式(推荐使用)
            connection = getConnectionByConfiguration();

            //第三步:获取数据库操作对象(一个连接对象,可以创建多个数据库操作对象)
            if (connection == null) {
                System.out.println("获取数据库连接失败!");
                return;
            }

            //定义DQL语句,jdbc中,通常是写DML语句和DQL语句
            //定义SQL语句框架
            String sql = StringUtils.join("select * from `user` where status = ? and userName = ?");
            //执行到此,先将SQL语句框架进行了编译
            preparedStatement = connection.prepareStatement(sql);
            //这里假设是前台传的参数值
            String statusParam = "1";
            String userNameParam = "'lisi' or 'hello' = 'hello'";
            //给参数?动态赋值
            preparedStatement.setString(1, statusParam);
            preparedStatement.setString(2, userNameParam);

			//打印预编译SQL
            //System.out.println("预编译SQL:" + ((ClientPreparedStatement) preparedStatement).getPreparedSql());
            //打印赋值后,编译的SQL
            //System.out.println("赋值后,编译的SQL:" + ((ClientPreparedStatement) preparedStatement).asSql());

            //第四步:执行SQL语句
            //将sql语句发送给数据库管理系统,数据库管理系统(DBMS)会编译并执行该sql语句,把查询结果集放到ResultSet结果集对象中
            resultSet = preparedStatement.executeQuery();

            //第五步:处理查询结果集
            //处理查询结果集,一个Statement可以得出多个ResultSet
            //resultSet.next()方法作用:if the new current row is valid, return true. if there are no more rows, return false
            //将光标向前移动一行,如果指向当前行有记录,则返回true,若指向当前行无记录,返回false
            while (resultSet.next()) {
                //获取当前光标指向行中的数据(通过特定类型获取数据),该方式,明确知道字段的类型,可以节省类型转换花费的性能,该方式的程序更加健壮,性能更高,推荐使用
                int id = resultSet.getInt("id");
                String name = resultSet.getString("userName");
                String status = resultSet.getString("status");
                System.out.println("id:" + id + ",用户名:" + name + ",有效状态:" + status);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //第六步:释放资源,从小的开始关闭,分别进行try catch
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * <p>
     * 通过配置文件的方式,获取数据库连接(推荐使用)
     * <p/>
     *
     * @param
     * @return java.sql.Connection
     * @Date 2020/6/7 14:14
     */
    public static Connection getConnectionByConfiguration() {
        try {
            //1.读取配置文件
            InputStream inputStream = JDBCTest003.class.getClassLoader().getResourceAsStream("db.properties");
            //2.创建属性对象
            Properties pro = new Properties();
            //3.通过属性对象的load()方法将配置文件读取到流中(Map集合对象中(内存中))
            pro.load(inputStream);
            //4.关闭流
            inputStream.close();
            //5.通过属性对象的getProperty(String key)方法获取配置文件中的value属性值
            String driver = pro.getProperty("db.driver");
            String url = pro.getProperty("db.url");
            String userName = pro.getProperty("db.username");
            String passWord = pro.getProperty("db.password");

            //注册驱动,这里必须是完整的类名(包名+类名)
            Class.forName(driver);
            //创建数据库连接
            Connection connection = DriverManager.getConnection(url, userName, passWord);
            return connection;
        } catch (IOException | ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }

        return null;
    }

创作不易,博主费尽心血把自己这几年使用JDBC的心得做了总结,如果觉得这篇文章对你有帮助,欢迎各位老铁点个赞鼓励下,您的支持是我创作的最大动力!

写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步!

如果有什么问题,欢迎大家评论,一起探讨,代码如有问题,欢迎各位大神指正!

给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔!

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐