zoukankan      html  css  js  c++  java
  • JDBC:数据库连接技术

    JDBC :带它再爱你一次

    (一) JDBC 入门

    (1) 概述

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

    简单解释: 通过Java语言执行sql语句,从而操作数据库

    (2) 来由

    想要通过Java操作不同的数据库,应该根据数据库的不同而执行特定的API,而出于简化的想法,Sun公司,定义了一套面向所有关系型数据库的 API 即 JDBC ,其只提供接口,而具体实现去交给数据库厂商实现,而我们作为开发者,我们针对数据数据库的操作,只需要基于JDBC即可

    (二) 简单使用 JDBC

    我们简单的使用JDBC去查询数据库中的数据,并且输出到控制台中

    为了快速演示,我们新建一张非常简单的表

    CREATE TABLE student(
    	id INT PRIMARY KEY AUTO_INCREMENT,
    	NAME VARCHAR(20),
    	score DOUBLE(4,1)
    );
    
    INSERT student(id,NAME,score) VALUES (1,'张三',98);
    
    INSERT student(id,NAME,score) VALUES (2,'李四',96);
    
    INSERT student(id,NAME,score) VALUES (3,'王五',100);
    

    我们根据数据库中的信息写一个对应的学生类

    public class Student {
        private int id;
        private String name;
        private double score;
    	//省略构造、Get、Set、toString方法
        ...... 
    }
    

    下面是对 JDBC 查询功能的简单使用

    package cn.ideal.jdbc;
    
    import cn.ideal.domain.Student;
    
    import java.sql.*;
    
    public class JdbcDemo {
        public static void main(String[] args) {
            //导入数据库驱动包
    
            Connection connection = null;
            Statement statement = null;
            ResultSet resultSet = null;
    
            try {
                //加载驱动
                Class.forName("com.mysql.jdbc.Driver");
                //获取与数据库的连接对象
                connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1", "root", "root99");
                //定义sql语句
                String sql = "SELECT * FROM student";
                //获取执行sql语句的对象statement
                statement = connection.createStatement();
                //执行sql语句,获取结果集
                resultSet = statement.executeQuery(sql);
    
                //遍历获取到的结果集
                while (resultSet.next()) {
                    int id = resultSet.getInt(1);
                    String name = resultSet.getString(2);
                    Double score = resultSet.getDouble(3);
    
                    Student student = new Student();
                    student.setId(id);
                    student.setName(name);
                    student.setScore(score);
    
                    System.out.println(student.toString());
                }
    
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                //释放资源,后调用的先释放
                if (resultSet != null) {
                    try {
                        resultSet.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if (statement != null) {
                    try {
                        statement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    //运行结果
    Student{id=1, name='张三', score=98.0}
    Student{id=2, name='李四', score=96.0}
    Student{id=3, name='王五', score=100.0}
    

    下面我们开始详细的解释一下上面所用到的各个对象

    (三) JDBC 对象详解

    (1) DriverManager

    A:加载驱动 --> 注册驱动

    首先我们要知道加载驱动和注册驱动这两个词是什么意思,刚刚接触的时候,会有人总有朋友将Class.forName(com.mysql.jdbc.Driver) 当做注册数据库驱动的语句,但实际不然,它的作用是将参数表示的类加载到内存中,并且初始化,同时其中的静态变量也会被初始化,静态代码块也会被执行

    • 疑惑:能否使用ClassLoader 类中的loadClass()方法呢?

      • 答案是否定的,这个方法的特点是加载但不对该类初始化
    //Class类源码节选 -jdk8
    * A call to {@code forName("X")} causes the class named
    * {@code X} to be initialized.
    

    关于初始化问题这里简单提及一下,我们还是先回到我们主线来

    为什么不对类进行初始化,就不能选择了呢?

    这是因为真正实现注册驱动(告诉程序使用哪一个数据库驱动jar)的是:

    static void registerDriver(Driver driver)
    

    我们在jar包中找到Driver这个类,查看其源码

    //com.mysql.jdbc.Driver类中的静态代码块
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
    

    类被加载后,执行了类中的静态方法DriverManager进行了注册驱动

    我们也可能有见过下面2中的代码,但是实际上驱动会被加载两次,因为执行

    new com.mysql.jdbc.Driver() 已经加载了一次驱动

    //1.推荐
    Class.forName("com.mysql.jdbc.Driver");
    //2.不推荐
    DriverManager.registerDriver(new com.mysql.jdbc.Driver())
    

    那么何必这么麻烦呢?new com.mysql.jdbc.Driver() 直接这样写不就挺好了吗?

    但我们还是选择 拒绝!为什么呢?

    如果我们这样写,对于jar包的依赖就比较重了,我们如果面临多个项目,或者需要修改数据库,就需要修改代码,重新编译,但是如果使用Class类加载的方式,既保证了静态代码块中所包含的注册驱动方法会被执行 ,而又将参数变成了字符串形式,我们之后便可以通过修改配置文件 “ ” 内的内容 + 添加jar包 的方式更灵活的处理问题,并且不需要重新编译!

    注意:mysql5之后的驱动jar包可以省略注册驱动这一步,原因查看jar包中META-INF/services/java.sql.Driver文件

    com.mysql.jdbc.Driver
    com.mysql.fabric.jdbc.FabricMySQLDriver
    

    B:获取数据库连接

    static Connection getConnection(String url, String user, String password) 
    /*
    	jdbc:mysql://ip地址(域名):端口号/数据库名称
    	Eg:jdbc:mysql://localhost:3306/db1
    	本地mysql,且端口为默认3306,则可简写:jdbc:mysql:///数据库名称
    */
    

    (2) Connection (数据库连接对象)

    A:获取执行sql的对象

    //创建向数据库发送sql语句的statement对象
    Statement createStatement()
    
    //创建向数据库发送预编译sql语句的PrepareStement对象
    PreparedStatement prepareStatement(String sql)  
    

    B:管理事务

    //开启事务:设置参数为false,即开启事务
    setAutoCommit(boolean autoCommit) 
    
    //提交事务
    commit() 
    
    //回滚事务
    rollback() 
    

    (3) Statement (执行sql语句的对象)

    //执行DQL(查询数据库中表的记录(数据))
    ResultSet executeQuery(String sql)
    
    //执行DML(对数据库中表的数据进行增删改)
    int executeUpdate(String sql)
    
    //执行任意sql语句,但是目标不够明确,较少使用
    boolean execute(String sql)
    
    //把多条sql的语句放到同一个批处理中
    addBatch(String sql)
    
    //向数据库总发送一批sql语句执行
    executeBatch()
    

    代码演示(以增加一条数据为例)

    package cn.ideal.jdbc;
    
    import java.sql.*;
    
    public class StatementDemo {
        public static void main(String[] args) {
    
            Connection connection = null;
            Statement statement = null;
            try {
                //加载驱动
                Class.forName("com.mysql.jdbc.Driver");
    
                //获取数据库连接对象
                connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1", "root", "root99");
    
                //定义sql语句
                String sql = "INSERT student(id,NAME,score) VALUES (NULL,'马六',88);";
    
                //获取执行sql语句的对象
                statement = connection.createStatement();
    
                //执行sql语句
                int count = statement.executeUpdate(sql);
                System.out.println(count);
                if (count > 0) {
                    System.out.println("添加成功");
                } else {
                    System.out.println("添加失败");
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                if(statement != null){
                    try {
                        statement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
    
                if (connection != null){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    (4) ResultSet(结果集对象,封装查询结果)

    ResultSet所代表的的是sql语句的结果集——执行结果,当Statement对象执行excuteQuery()后,会返回一个ResultSet对象

    //游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据)
    //如果是,则返回false,如果不是则返回true
    boolean next()
    
    //获取数据,Xxx代表数据类型  
    getXxx(参数)
    
    Eg:int getInt() ,	String getString()
        
    1. int:代表列的编号,从1开始   如: getString(1)
    2. String:代表列名称。 如: getDouble("name")
    

    案例可参考开头快速使用部分,自行尝试读取数据库中数据后用集合框架装载

    (四) 事半功倍——工具类

    通过封装一些方法,使得出现一个更加通用的工具类,我们可以通过properties配置文件 ,使得信息更加直观且容易维护

    package cn.ideal.jdbc;
    
    import java.io.FileReader;
    import java.io.IOException;
    import java.net.URL;
    import java.sql.*;
    import java.util.Properties;
    
    public class JDBCUtils {
        private static String url;
        private static String user;
        private static String password;
        private static String driver;
    
        /**
         * 文件读取
         */
        static {
    
            try {
                //创建Properties集合类
                Properties pro = new Properties();
                //获取src路径下的文件
                ClassLoader classLoader = JDBCUtils.class.getClassLoader();
                URL res = classLoader.getResource("jdbc.properties");
                String path = res.getPath();
    
                //加载文件
                pro.load(new FileReader(path));
                //获取数据
                url = pro.getProperty("url");
                user = pro.getProperty("user");
                password = pro.getProperty("password");
                driver = pro.getProperty("driver");
    
                //注册驱动
                Class.forName(driver);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
        }
    
        /**
         * 获取连接
         *
         * @return 连接对象
         */
        public static Connection getConnection() throws SQLException {
            return DriverManager.getConnection(url, user, password);
        }
    
        /**
         * 释放资源
         *
         * @param statement
         * @param connection
         */
        public static void close(Statement statement, Connection connection) {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
    
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 释放资源
         *
         * @param resultSet
         * @param statement
         * @param connection
         */
        public static void close(ResultSet resultSet, Statement statement, Connection connection) {
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
    
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
    
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    
    }
    

    工具类测试类

    package cn.ideal.jdbc;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class JDBCUtilsTest {
        public static void main(String[] args) {
    
            Connection connection = null;
            Statement statement = null;
            try {
    
                connection = JDBCUtils.getConnection();
    
                //定义sql语句
                String sql = "INSERT student(id,NAME,score) VALUES (NULL,'马六',88)";
    
                //获取执行sql语句的对象
                statement = connection.createStatement();
    
                //执行sql语句
                int count = statement.executeUpdate(sql);
                System.out.println(count);
                if (count > 0) {
                    System.out.println("添加成功");
                } else {
                    System.out.println("添加失败");
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                JDBCUtils.close(statement,connection);
            }
        }
    }
    

    之前的文章中分别通过集合实现、IO实现、而学习数据库后,我们可以试着通过数据库存储数据,写一个简单的登录注册小案例!在第五大点中有提到吼

    (五) 补充:PreparedStatment

    //创建向数据库发送预编译sql语句的prepareStatement
    PreparedStatement prepareStatement(String sql) 
    

    prepareStatement继承自Statement,总而言之,它相较于其父类,更强更简单!

    (1) 优点

    A:效率

    Statement 直接编译 SQL 语句,直接送到数据库去执行,而且其多次重复执行sql语句,PreparedStatement 会对SQL进行预编译,再填充参数,这样效率会比较高(预编译的SQL存储在PreparedStatement中

    B:可读性

    定义 SQL 语句的时候,常常需要使用到 Java 中的变量,在一些复杂的情况下,需要频繁的使用到引号和单引号的问题,变量越多,越复杂,而PreparedStatement可以使用占位符 ‘ ?’ 代替参数,接下来再进行参数的赋值,这样有利于代码的可读性

    C:安全性

    PreparedStatement 由于预编译,可以避免Statement中可能需要采取字符串与变量的拼接而导致SQL注入攻击【编写永等式,绕过密码登录】

    我们先按照我们之前的做法,写一个简单的登录Demo,先创一张表!

    CREATE TABLE USER(
    	id INT PRIMARY KEY AUTO_INCREMENT,
    	username VARCHAR(32),
    	PASSWORD VARCHAR(32)
    );
    
    SELECT * FROM USER;
    
    INSERT INTO USER VALUES(NULL,'admin','admin888');
    INSERT INTO USER VALUES(NULL,'zhangsan','123456');
    

    接着编写代码

    package cn.ideal.login;
    
    import cn.ideal.jdbc.JDBCUtils;
    
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Scanner;
    
    public class LoginDemo {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入用户名");
            String username = sc.nextLine();
            System.out.println("请输入密码");
            String password = sc.nextLine();
            
            boolean flag = new LoginDemo().login(username, password);
    
            if (flag) {
                System.out.println("登录成功");
            } else {
                System.out.println("用户名或密码错误");
            }
        }
    
        /**
         * 登录方法
         */
        public boolean login(String username, String password) {
            if (username == null || password == null) {
                return false;
            }
    
            Connection connection = null;
            Statement statement = null;
            ResultSet resultSet = null;
    
            try {
                connection = JDBCUtils.getConnection();
                //定义sql
                String sql = "SELECT * FROM USER WHERE username = '" + username + "' AND password = '" + password + "' ";
                //获取执行sql的对象
                statement = connection.createStatement();
                //执行查询
                resultSet = statement.executeQuery(sql);
    
                return resultSet.next();
    
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                JDBCUtils.close(resultSet,statement, connection);
            }
            return false;
        }
    }
    

    简单的来说,这样一个简单的登录Demo就写好了,但是这个时候,SQL注入问题中的一种情况就出现了,或许你听过,在早些年的时候,漏洞还是蛮常见的,一些黑客或者脚本小子们常常使用一些SQL注入的手段进行目标网站后台的入侵,我们今天所讲的这一种,就是其中一种,叫做SQL万能注入(SQL万能密码)

    我们先来观察一下上述代码中关于SQL语句的部分

     String sql = "SELECT * FROM USER WHERE username = '" + username + "' AND password = '" + password + "' ";
    

    也就是说它将我们所输入的 usernamepassword合成为SQL查询语句, 当数据库中不存在这样的字段就代表输入错误,但是对于存在SQL注入漏洞的程序,则可以通过构造一些特殊的字符串,达到登录的目的,先贴出来测试结果

    //运行结果
    请输入用户名
    admin
    请输入密码
    1' or '1' = '1
    登录成功
    

    如果我们将上述代码中密码 (username) 部分用我们的这些内容代替是怎么样的呢

     String sql = "SELECT * FROM USER WHERE username = 'admin' AND PASSWORD = '1' or '1' = '1' ";
    

    补充:在SQL语句中逻辑运算符具有优先级,= 优先于 and ,and 优先于 or

    所以上面的式子中 AND先被执行,当然返回错,接着执行or部分,对于一个永等式 ‘1’ = ‘1‘ 来说返回值永远是true,所以SQL查询结果为true,即可以登录成功

    //使用PrepareStemen替代主要部分
    
    //定义sql
    String sql = "SELECT * FROM USER WHERE username = ? AND password = ?";
    //获取执行sql的对象
    preparedStatement = connection.prepareStatement(sql);
    //给?赋值
    preparedStatement.setString(1, username);
    preparedStatement.setString(2, password);
    
    //执行查询
    resultSet = preparedStatement.executeQuery();
    
    //运行结果
    请输入用户名
    admin
    请输入密码
    1' or '1' = '1
    用户名或密码错误
    

    结尾:

    如果内容中有什么不足,或者错误的地方,欢迎大家给我留言提出意见, 蟹蟹大家 !_

    如果能帮到你的话,那就来关注我吧!(系列文章均会在公众号第一时间更新)

    在这里的我们素不相识,却都在为了自己的梦而努力 ❤

    一个坚持推送原创Java技术的公众号:理想二旬不止

  • 相关阅读:
    nmon系统监控
    Spring框架DataSource
    Spring框架的AOP
    jdk -version could not open jvm.cfg 的解决办法
    Spring框架annotation实现IOC介绍
    Junit4 单元测试框架的常用方法介绍
    页面静态化3 --- 使用PHP页面缓存机制来完成页面静态化(下)操作一个案例(新闻管理系统)
    页面静态化2 --- 使用PHP缓存机制来完成页面静态化(上)(ob_flush和flush函数区别用法)
    页面静态化1 --- 概念(Apache内置压力测试工具使用方法)
    memcached学习笔记6--浅谈memcached的机制 以及 memcached细节讨论
  • 原文地址:https://www.cnblogs.com/ideal-20/p/11297637.html
Copyright © 2011-2022 走看看