• Java-JDBC


    JDBC

    JDBC英文名为:Java Data Base Connectivity(Java数据库连接),官方解释它是Java编程语言和广泛的数据库之间独立于数据库的连接标准的Java API

    根本上说JDBC是一种规范,它提供的接口,一套完整的,允许便捷式访问底层数据库。

    不同的可执行文件都能通过JDBC访问数据库,又兼备存储的优势。

    它就是Java与数据库的连接的桥梁或者插件,用Java代码就能操作数据库的增删改查、存储过程、事务等。

    我们可以发现,JDK自带了一个java.sql包,而这里面就定义了大量的接口,不同类型的数据库,都可以通过实现此接口,编写适用于自己数据库的实现类。而不同的数据库厂商实现的这套标准,我们称为数据库驱动

    使用JDBC连接数据库

    6.0版本以上,不用手动加载驱动,我们直接使用即可

    DriverManager就是管理我们的数据库驱动的

    //1. 通过DriverManager来获得数据库连接
    try (Connection connection = DriverManager.getConnection("连接URL","用户名","密码");
         //2. 创建一个用于执行SQL的Statement对象
         Statement statement = connection.createStatement()){   //注意前两步都放在try()中,因为在最后需要释放资源!
        //3. 执行SQL语句,并得到结果集
        ResultSet set = statement.executeQuery("select * from 表名");
        //4. 查看结果
        while (set.next()){
            ...
        }
    }catch (SQLException e){
        e.printStackTrace();
    }
    //5. 释放资源,try-with-resource语法会自动帮助我们close
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    了解Connection

    Connection是数据库的连接对象,可以通过连接对象来创建一个Statement用于执行SQL语句:

    Statement createStatement() throws SQLException;
    
    • 1

    PreparedStatement能够有效地预防SQL注入式攻击:

    PreparedStatement prepareStatement(String sql)
        throws SQLException;
    
    • 1
    • 2

    了解Statement

    使用了executeQuery()方法来执行select语句,此方法返回给我们一个ResultSet对象,查询得到的数据,就存放在ResultSet中

    Statement除了执行这样的DQL语句外,我们还可以使用executeUpdate()方法来执行一个DML或是DDL语句,它会返回一个int类型,表示执行后受影响的行数,可以通过它来判断DML语句是否执行成功

    也可以通过excute()来执行任意的SQL语句,它会返回一个boolean来表示执行结果是一个ResultSet还是一个int,我们可以通过使用getResultSet()或是getUpdateCount()来获取

    执行DQL操作

    执行DQL操作会返回一个ResultSet对象,我们来看看如何从ResultSet中去获取数据:

    //首先要明确,select返回的数据类似于一个excel表格
    while (set.next()){
        //每调用一次next()就会向下移动一行,首次调用会移动到第一行
    }
    
    • 1
    • 2
    • 3
    • 4

    移动行数后,就可以通过set中提供的方法,来获取每一列的数据。

    执行批处理操作

    要执行很多条语句时,可以不用一次一次地提交,而是一口气全部交给数据库处理,这样会节省很多的时间。

    public static void main(String[] args) throws ClassNotFoundException {
        try (Connection connection = DriverManager.getConnection();
             Statement statement = connection.createStatement()){
    
            statement.addBatch("insert into user values ('f', 1234)");
            statement.addBatch("insert into user values ('e', 1234)");   //添加每一条批处理语句
            statement.executeBatch();   //一起执行
    
        }catch (SQLException e){
            e.printStackTrace();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    将查询结果映射为对象

    既然可以从数据库中获取数据了,那么现在就可以将这些数据转换为一个类来进行操作

    首先定义我们的实体类:

    public class Student {
        Integer sid;
        String name;
        String sex;
    
        public Student(Integer sid, String name, String sex) {
            this.sid = sid;
            this.name = name;
            this.sex = sex;
        }
    
        public void say(){
            System.out.println("我叫:"+name+",学号为:"+sid+",我的性别是:"+sex);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    进行一个转换:列的下标是从1开始的

    while (set.next()){
        Student student = new Student(set.getInt(1), set.getString(2), set.getString(3));
        student.say();
    }
    
    • 1
    • 2
    • 3
    • 4

    也可以利用反射机制来将查询结果映射为对象,使用反射的好处是,无论什么类型都可以通过我们的方法来进行实体类型映射:

    private static <T> T convert(ResultSet set, Class<T> clazz){
        try {
            Constructor<T> constructor = clazz.getConstructor(clazz.getConstructors()[0].getParameterTypes());   //默认获取第一个构造方法
            Class<?>[] param = constructor.getParameterTypes();  //获取参数列表
            Object[] object = new Object[param.length];  //存放参数
            for (int i = 0; i < param.length; i++) {   //是从1开始的
                object[i] = set.getObject(i+1);
                if(object[i].getClass() != param[i])
                    throw new SQLException("错误的类型转换:"+object[i].getClass()+" -> "+param[i]);
            }
            return constructor.newInstance(object);
        } catch (ReflectiveOperationException | SQLException e) {
            e.printStackTrace();
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    while (set.next()){
        Student student = convert(set, Student.class);
        if(student != null) student.say();
    }
    
    • 1
    • 2
    • 3
    • 4

    SQL注入攻击

    模拟登陆一个用户:

    try (Connection connection = DriverManager.getConnection("URL","用户名","密码");
         Statement statement = connection.createStatement();
         Scanner scanner = new Scanner(System.in)){
        ResultSet res = statement.executeQuery("select * from user where username='"+scanner.nextLine()+"'and pwd='"+scanner.nextLine()+"';");
        while (res.next()){
            String username = res.getString(1);
            System.out.println(username+" 登陆成功!");
        }
    }catch (SQLException e){
        e.printStackTrace();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果我输入的是以下内容:

    Test
    1111' or 1=1; -- 
    # Test 登陆成功!
    
    • 1
    • 2
    • 3

    原本的SQL语句会变为:

    select * from user where username='Test' and pwd='1111' or 1=1; -- '
    
    • 1

    如果允许这样的数据插入,那么我们原有的SQL语句结构就遭到了破坏,使得用户能够随意登陆别人的账号。

    因此我们可能需要限制用户的输入来防止用户输入一些SQL语句关键字,但是关键字非常多,这并不是解决问题的最好办法。

    PreparedStatement

    可以使用PreparedStatement来解决SQL注入攻击漏洞

    public static void main(String[] args) throws ClassNotFoundException {
        try (Connection connection = DriverManager.getConnection("URL","用户名","密码");
             PreparedStatement statement = connection.prepareStatement("select * from user where username= ? and pwd=?;");
             Scanner scanner = new Scanner(System.in)){
    
            statement.setString(1, scanner.nextLine());
            statement.setString(2, scanner.nextLine());
            System.out.println(statement);    //打印查看一下最终执行的
            ResultSet res = statement.executeQuery();
            while (res.next()){
                String username = res.getString(1);
                System.out.println(username+" 登陆成功!");
            }
        }catch (SQLException e){
            e.printStackTrace();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    要提前给到PreparedStatement一个SQL语句,并且使用?作为占位符,它会预编译一个SQL语句,通过直接将我们的内容进行替换的方式来填写数据。

    实际执行的SQL语句是什么:

    com.mysql.cj.jdbc.ClientPreparedStatement: select * from user where username= 'Test' and pwd='123456'' or 1=1; -- ';
    
    • 1

    输入的参数一旦出现'时,会被变为转义形式\',而最外层有一个真正的'来将我们输入的内容进行包裹,因此它能够有效地防止SQL注入攻击

    管理事务

    JDBC默认的事务处理行为是自动提交;JDBC需要进行事务管理时,首先要通过Connection对象调用setAutoCommit(false) 方法, 将SQL语句的提交(commit)由驱动程序转交给应用程序负责

    一旦关闭自动提交,那么现在执行所有的操作如果在最后不进行commit()来提交事务的话,那么所有的操作都会丢失

    也可以使用rollback()来手动回滚之前的全部操作

    con.setAutoCommit();   //关闭自动提交后相当于开启事务。
    // SQL语句
    // SQL语句
    // SQL语句
    con.commit();或 con.rollback();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    public static void main(String[] args) throws ClassNotFoundException {
        try (Connection connection = DriverManager.getConnection("URL","用户名","密码");
             Statement statement = connection.createStatement()){
    
            connection.setAutoCommit(false);  //关闭自动提交,现在将变为我们手动提交
            statement.executeUpdate("insert into user values ('a', 1234)");
            statement.executeUpdate("insert into user values ('b', 1234)");
            statement.executeUpdate("insert into user values ('c', 1234)");
    
            connection.commit();   //如果前面任何操作出现异常,将不会执行commit(),之前的操作也就不会生效
        }catch (SQLException e){
            e.printStackTrace();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    创建一个回滚点来实现定点回滚:

    public static void main(String[] args) throws ClassNotFoundException {
        try (Connection connection = DriverManager.getConnection("URL","用户名","密码");
             Statement statement = connection.createStatement()){
    
            connection.setAutoCommit(false);  //关闭自动提交,现在将变为我们手动提交
            statement.executeUpdate("insert into user values ('a', 1234)");
            
            Savepoint savepoint = connection.setSavepoint();   //创建回滚点
            statement.executeUpdate("insert into user values ('b', 1234)");
    
            connection.rollback(savepoint);   //回滚到回滚点,撤销前面全部操作
    
            statement.executeUpdate("insert into user values ('c', 1234)");
    
            connection.commit();   //提交事务(注意,回滚之前的内容都没了)
    
        }catch (SQLException e){
            e.printStackTrace();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    Go 之常用并发学习
    Django源码学习——配置文件解析
    新版TCGAbiolinks包的可视化功能
    Go的优雅退出
    全国职业技能大赛云计算--高职组赛题卷②(私有云)
    高级人工智能复习 中科大
    Dapr 官方文档中文翻译 v1.5 版本正式发布
    JavaScript数组的扁平化:将 2D JavaScript 二维数组转换为 1D 一维数组(多种方法)
    [免费专栏] Android安全之剪贴板+键盘缓存+UI界面+自动截屏敏感信息挖掘
    Go 使用Viper处理Go应用程序的配置
  • 原文地址:https://blog.csdn.net/CS_z_jun/article/details/133843556