JDBC操作数据库实操

目的是解耦合,降低程序的耦合度,提高程序的扩展力,多态机制就是非常典型的,面向抽象编程。安装启动MySQL:docker run –name

Java Database Connectivity (Java语言连接数据库)

JDBC是Sun公司指定的一套接口(Interface)

目的是解耦合,降低程序的耦合度,提高程序的扩展力,多态机制就是非常典型的,面向抽象编程

为什么Sun要指定一套JAVA SQL的接口?

因为每个数据库都存在差异,实现原理不同,Oracle有自己的原理,MySQL有自己的原理,通过这套实现需要厂家进行实现,开发者无需关系底层逻辑

假设现在有个需求,要将Oracle换成MySQL数据库,此时只有接口能做到切换时只需要更换数据库驱动即可无缝切换,代码层完全不用修改(或小改,可能有些语法是特有的)

所谓的数据库驱动就是Jar包,里面就是一堆JDBC的实现类,使用maven时,只需要在依赖项里添加对应依赖即可,或者手动添加jar包

开始实操

既然是操作数据库,就需要有数据库的支持,这里采用Docker安装Mysql并进行数据插入

  • 安装启动MySQL:docker run –name testDbContainer -v ~/testDb:/root -e MYSQL_ROOT_PASSWORD=12345678 -e MYSQL_DATABASE=testDb -p 3306:3306 -d mysql:8.0.29-oracle
  • 插入数据
drop table if exists t_user; 
create table t_user ( 
    id int primary key auto_increment, 
    username varchar(10) unique,
    age int(3),
    gender int(1) comment '1表示男,0表示女'
);
insert into t_user (username, age, gender) values('Jack', 15, 1),('Mike', 20, 1),('Susan', 22, 0);

Java配置

使用JDBC进行编程,需要先注册驱动,告诉Java程序要连接什么数据库,Oracle还是MySQL,还是H2等等,需要用到java.sql包下的DriverManager类,有一个静态方法registerDriver

public static synchronized void registerDriver(java.sql.Driver driver)
    throws SQLException {

    registerDriver(driver, null);
}

方法要求传入的是接口(面向接口编程),此时就需要去找数据库厂商提供的驱动类,他们会负责Driver接口的实现类

  1. 使用maven管理的项目比较简单,只需要引入依赖即可
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</dependency>
  1. 如果想手动添加jar包依赖,到mysql的官网下载页dev.mysql.com/downloads/c…
JDBC操作数据库实操

这里要选择与平台无关的选项,下载的压缩包解压后里面就有对应的jar包,自行导入即可

这里有一个注意事项,新版的驱动包名已变更,如果用的还是com.mysql.jdbc包中的Driver类,在加载类时就会输出此类不再推荐使用,正确的包名是com.mysql.cj.jdbc.Driver

public class Driver extends com.mysql.cj.jdbc.Driver {
    public Driver() throws SQLException {
    }

    static {
        System.err.println("Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.");
    }
}

再看看真正的使用的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!");
        }
    }
}

在静态代码块中,有一行DriverManager.registerDriver(new Driver());,原本应该开发者去注册的事,加载类的时候就已经做了,所以只需要让JVM去加载类即可实现注册 最简单的便是Class.forName(“”)方法

public class App {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
    }
}

第二步:获取连接

表示JVM进程和数据库之间的通道打开,使用完需要关闭通道释放资源,主要方法为DriverManager.getConnection方法,入参为数据库URL,账号,密码,返回值是Connection对象,不同数据库的URL不一样,用到什么搜什么即可

public class App {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/testDb";
        String username = "root";
        String password = "12345678";
        Connection connection = DriverManager.getConnection(url, username, password);
    }
}

第三-第四步:获取数据库操作对象,执行SQL

这个对象主要用于执行SQL语句,Connection.createStatement/prepareStatement调用这两个方法,这里先演示createStatement,后面会说到区别

第五步:获取结果集

如果执行的是DQL语句,那么将会返回一个ResultSet对象,表示数据表的映射,如果是DML语句,返回的则是int,表示sql语句所影响的行数。

讲讲ResultSet这个对象的next方法,执行DQL语句调用executeQuery方法,假设执行了某张表的select * 语句,想要获取结果集的数据需要调用ResultSet.next()方法,光标会向下移动一行,如果有数据则返回true,此时操作ResultSet对象就可以获取对应的行数据,再次调用next又会将光标向下移动一行,直到没有数据了返回false

JDBC操作数据库实操

ResultSet.getXXX方法可以获取对应的列,使用方式有

  1. 索引,如果返回字段顺序为id,name,age,1则代表id,以此类推(JDBC中的所有操作索引从1开始),不推荐,语义不清晰,没有办法通过代码直观到看出预期
  2. 键名,直接使用字段名作为参数,如果select写了字段的别名,则需要用别名去取值
  3. 提供get数据类型的方法,比如字段id类型为int,则可以用getInt获取id,如果使用getString,不管数据类型是什么都会按照String的形式取出
public class App {    public static void main(String[] args) throws SQLException, ClassNotFoundException {        Class.forName("com.mysql.cj.jdbc.Driver");        String url = "jdbc:mysql://localhost:3306/testDb";        String username = "root";        String password = "12345678";        Connection connection = DriverManager.getConnection(url, username, password);        Statement statement = connection.createStatement();        String sql = "select * from t_user;";        ResultSet resultSet = statement.executeQuery(sql);        // 通过while循环获取全部结果集,返回false则跳出循环        while (resultSet.next()) {            System.out.println(resultSet.getString("username"));        }    }

示例通过列名去除了所有结果集的用户名

JDBC操作数据库实操

第六步:释放资源

当所有操作结束后,需要将ResultSet(如果有),Statement,Connection的close方法从左到右执行,释放掉资源避免不必要的浪费

public class App {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/testDb";
        String username = "root";
        String password = "12345678";
        Connection connection = DriverManager.getConnection(url, username, password);
        Statement statement = connection.createStatement();
        String sql = "select * from t_user;";
        ResultSet resultSet = statement.executeQuery(sql);
        while (resultSet.next()) {
            System.out.println(resultSet.getString("username"));
        }
        resultSet.close();
        statement.close();
        connection.close();
    }

从JDK7开始,便支持了try with resources,只要资源实现了AutoCloseable接口,作用是在块代码执行后,自动释放资源,也就是自动执行close方法。
查看类源码可以看到,ResultSet,Statement,Connection三个类都实现了AutoCloseable接口,所以可以改写让代码更整洁,多个资源用分号进行隔开

public class App {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/testDb";
        String username = "root";
        String password = "12345678";
        String sql = "select * from t_user;";
        try (Connection connection = DriverManager.getConnection(url, username, password);
             Statement statement = connection.createStatement();
             ResultSet resultSet = statement.executeQuery(sql);
        ) {
            while (resultSet.next()) {
                System.out.println("用户名:" + resultSet.getString("username"));
            }
        }
    }
}

可以不使用,但可以作为一个知识点了解

SQL注入问题

假设这是一个提供给外部的接口服务,会接受用户的传参数据,由于creaetStatement方法是先进行sql语句都拼接,再进行sql的编译,所以可以会将用户输入的恶意关键字一同编译,像下面代码中用户传了字符串’ or ‘1’ = ‘1,仿佛猜透了服务端对sql拼接规则,由于加了必定成功的条件,所以即使传的参数不对也会执行成功

    // 其他代码省略
    Statment st = connection.createStatment();
    // 用户传入的值
    String value = "' or '1' = '1";
    String sql = "select * from t_user where username = '" + value +"'";
    ResultSet rs = st.executeQuery(sql);
    if(rs.next()){
        System.out.println(rs.getString("username");
        // 这里会输出
    }          

这种欺骗了服务器令其执行了非预期的查询的行为,便称为SQL注入

要解决有两种方案

  1. 从参数解决,对用户的输入进行特定规则的校验
  2. 让用户的传参不参与SQL语句的编译

更应该使用第二种解决方案

PreparedStatement对象就可以完美处理注入问题,在使用时需要先将SQL语句进行编译,需要用户传参的地方用占位符替代,编译后再将值传到原本的占位符中

public static void main(String[] args) throws SQLException, ClassNotFoundException {
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://localhost:3306/testDb";
    String username = "root";
    String password = "12345678";
    Connection connection = DriverManager.getConnection(url, username, password);
    String sql = "select * from t_user where username = ?";
    PreparedStatement ps = connection.prepareStatement(sql);
    // 将原本会导致SQL注入的值通过索引传到占位符
    ps.setString(1, "' or '1' = '1");
    ResultSet resultSet = ps.executeQuery();
    while (resultSet.next()) {
        System.out.println("用户名:" + resultSet.getString("username"));
    }
    resultSet.close();
    ps.close();
    connection.close();
}

上面这段代码执行后不会有任何输出

JDBC操作数据库实操

PreparedStatement很好,但是有一种情况是无法实现的,比如要求order by字段实现动态的升降序,asc和desc不参与编译就没有办法使用,这时候只能用Statement来主观实现用户的SQL注入

对比总结两种操作对象

  • Statement存在SQL注入问题,PreparedStatement解决了注入问题
  • Statement是执行一次编译一次,而PreparedStatement是编译一次,执行多次,效率更高
  • PreparedStatement拥有静态编译检查,比如占位符要求传入String,就不能传入其他数据类型,编译器会报错,安全性更高
  • 能使用PreparedStatement尽量使用,只有当需要主观行为上的SQL注入时再使用Statement

JDBC中的事务

在JDBC中,事务行为默认自动提交,只要执行任意的DML语句,就会自动提交一次。这种行为肯定不符合实际业务开发, 一个业务中可能包含多条DML语句,必须满足同时成功或者同时失败

可以通过Connection对象的setAutoCommit(false)方法,传入false来关闭自动提交事务,在业务程序中,正常逻辑下使用Connection.commit()方法进行事务提交,用try catch代码块捕获异常后使用Connection.rollback()方法进行事务回滚

事务演示(DQL用executeQuery,DML使用executeUpdate,包含增删改)

    // 其他代码省略
    Connection connection = DriverManager.getConnection(url, username, password);
    connection.setAutoCommit(false);
    String sql = "delete from t_user where id = 3;";
    PreparedStatement ps = connection.prepareStatement(sql);
    int i = ps.executeUpdate();
    if (i == 1) {
        System.out.println("删除成功");
    }
    ps.close();
    connection.close();

尝试删除id为3的用户,代码执行后,控制台会输出删除成功

JDBC操作数据库实操

但是去查看数据库并没有数据删除,说明JDBC的自动提交事务被成功关闭了

JDBC操作数据库实操

正确的处理场景,在所有预期事务执行完毕后进行事务提交,catch异常后进行事务的回滚

    // 其他代码省略
    Connection connection = DriverManager.getConnection(url, username, password);
    connection.setAutoCommit(false);
    PreparedStatement ps = null;
    try {
        String sql = "delete from t_user where id = 3;";
        ps = connection.prepareStatement(sql);
        int i = ps.executeUpdate();
        if (i == 1) {
            System.out.println("删除成功");
        }
        connection.commit();
    } catch (Exception e) {
        connection.rollback();
    }
    if (Objects.nonNull(ps)) {
        ps.close();
    }
    connection.close();

在真实的开发场景中,并不会使用原生的JDBC去实现业务处理,主流的有MyBatis和JPA等ORM框架来帮助快速开发,但是底层都是基于JDBC的封装,只有熟悉JDBC才能更加清晰的掌握框架的使用

JDBC操作数据库实操


链接:https://juejin.cn/post/7099066449456529438

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/5325.html

(0)
上一篇 2022-12-13 21:20
下一篇 2022-12-13 21:21

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信