美文网首页
JDBC-数据库

JDBC-数据库

作者: 努力学习的lfk | 来源:发表于2021-07-21 16:14 被阅读0次

    JDBC(Java DataBases Connectivity):Java语言连接数据库

    JDBC的本质:一套接口

    JDBC简介:
    JDBC时SUN公司指定的一套接口(interface)
    java.sql.*;(这个包下有很多接口)

    为什么SUN要制定一套JDBC接口?
    因为每一个数据库的底层实现原理都不一样。Oracle数据库、MySQL数据库、微软SqlService数据库都有自己独特的实现原理

    JDBC.png

    SUN公司提供JDBC接口,各大数据库厂商负责编写JDBC接口的实现类。我们只需要下载驱动就行。
    驱动:所有的数据库驱动都是以jar包的形式存在,jar包当中有很多.class文件。这些.class文件就是对JDBC接口的实现。驱动不是SUN公司提供的,是各大数据库厂家负责提供,下载驱动jar包需要去数据库官网下载。

    为什么要面向接口编程?
    解耦合:降低程序的耦合度,提高程序的扩展力

    JDBC编程6步概述(重点):


    第一步:注册驱动:告诉Java程序即将连接的是哪种数据库
    第二步:获取连接:表示JVM的进程和数据库进程之间的通道打开了。这属于进程间的通信(重量级),使用完一定要关闭
    第三步:获取数据库操作对象:专门执行sql语句的对象
    第四步:执行sql语句:
    executeQuery(select)专门执行DQL语句、executeUpdate(insert、delete、update)专门执行DML语句
    第五步:处理查询结果集:只有当第四步执行的是select语句时,才能有第五步
    第六步:释放资源:使用完资源之后一定要关闭资源


    第一步:注册驱动

    //注册驱动的第一种写法:
    Driver driver  = new com.mysql.cj.jdbc.Driver();
    DriverManager.registerDriver(driver);
    
    //注册驱动的第二种写法:
    class.forName("com.mysql.cj.jdbc.Driver");
    
    • 第二种写法常用!因为参数是字符串,可以写到配置文件中

    第二步:获取连接:

    url:统一资源定位符(网络中某个资源的绝对路径)
    url包括:
    协议——jdbc:mysql://
    ip——127.0.0.1和localhost都是本机ip地址
    端口号——3306(MySQL数据库的端口号)
    资源名(数据库名)——myjdb

    oracle的url:
    jdbc:oracle:thin:@localhost:1521:数据库名

    url-IP地址
    url-端口号
    url-资源名
    String url = "jdbc:mysql://127.0.0.1:3306/myjdbc";
    String user="root";
    String password="abc1234.";//MySQL登录密码
    
    Connection conn=DriverManager.getConnection(url,user,password);
    System.out.println("数据库连接对象="+conn);
    
    

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

    Statement stmt = conn.createStatement();
    

    Statement专门执行sql语句


    第四步:执行sql语句

    JDBC的sql语句不需要写分号(;)结尾

    String sql="insert into school(name,age)  values('张三',20)";
    int count=stmt.executeUpdate(sql);
    System.out.println(count==1?"保存成功":"保存失败");
    

    executeUpdate专门执行DML语句的(insert、delete、update)
    返回值是单个ResultSet对象

    executeQuery专门执行DQL语句(select)
    返回值是“影响数据库中的记录条数”


    第五步:处理查询结果集

    查询结果集next():将光标从当前位置向前移一行,并返回boolean类型。

    取数据getString():不管数据库中数据类型是什么,都以String的形式取出。
    除了可以以String类型取出之外,还可以以特定的类型取出(得与底层数据类型一致)。如int类型getInt()、double类型的getDouble()。

    JDBC中所有下标从1开始

    executeQuery(select)专门执行DQL语句
    executeUpdate(insert、delete、update)专门执行DML语句

    定义与查询结果有关的变量 执行DQL语句 处理查询结果集-下标写法 处理查询结果集-列名写法
    注意:列名不是表中的列名称,而是查询结果集的列名称

    第六步:释放资源

    为了保证资源一定释放,在finally语句块中关闭资源并且要遵循从小到大(先开后关)依次关闭分别对其try...catch。
    在try语句中定义的对象无法关闭,需要把定义放到try语句外。

                try{
                    if(stmt!=null){
                        stmt.close();
                        System.out.println("关闭stmt");
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
                try{
                    if(conn!=null){
                        conn.close();
                        System.out.println("关闭conn");
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
    

    全过程:


    将连接数据库的所有信息配置到配置文件中

    实际开发中不建议把连接数据库的信息写死到Java程序中

    配置文件存放在src目录下,则不用加路径 配置文件中定义变量 使用资源绑定器绑定属性配置文件(properties)

    项目实战-用户登录功能

    实现功能:
    1.模拟用户登录功能的实现
    2.业务描述:
    程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码。用户输入用户名和密码之后,提交信息,Java收集并验证用户名和密码是否合法。
    合法:显示登陆成功
    不合法:显示登陆失败
    3.数据的准备:
    在实际开发中,表的设计会使用专业的建模工具如:Power Designer来进行数据库表的设计。参见user_login.sql脚本

    1.用户输入界面

        private static Map<String, String> initUI() {
            //获取用户名
            Scanner s =new Scanner(System.in);
            System.out.print("用户名:");
            String loginName=s.nextLine();
            System.out.print("密码:");
            String loginPwd=s.nextLine();
            //创建一个 Map<String,String>类型的哈希表,名为userLoginInfo
            Map<String,String> userLoginInfo = new HashMap<>();
            //利用put方法把用户输入保存在哈希表中
            userLoginInfo.put("loginName",loginName);
            userLoginInfo.put("loginPwd",loginPwd);
            //返回用户输入,用以下一步验证
            return userLoginInfo;
        }
    
    • 知识点小结:
      1.Scanner类可以帮助我们接收从键盘输入的数据
      2.Scanner类next()与nextLine()的区别
      1)next()
      ①一定要读取到有效字符后才可以结束输入。
      ②对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
      ③只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
      ④next() 不能得到带有空格的字符串。

      2)nextLine()
      ①以Enter为结束符,即nextLine()方法返回的是输入回车之前的所有字符。
      ②可以获得空白。

      3.将对象保存在Map<String,Sring>容器中
      Map是存储键和值这样的双列数据集合,但存储的数据是没有顺序的,其键不能重复,但其值是可以重复的,可以通过每一个键找到每一个对应的值
      Map集合的特点:
      1)Map集合一次存储两个对象,一个键对象,一个值对象
      2)键对象在集合中是唯一的,可以通过键来查找值
      HashMap特点:
      1)使[哈希算法对键去重复,效率高,但无序
      2)HashMap是Map接口的主要实现类

    2.验证用户输入

        private static boolean login(Map<String, String> userLoginInfo) {
            //使用资源绑定器绑定属性配置文件
            ResourceBundle bundle = ResourceBundle.getBundle("resource");
            //绑定后传值
            String driver = bundle.getString("driver");
            String url = bundle.getString("url");
            String user = bundle.getString("user");
            String password = bundle.getString("password");
    
            Connection conn = null;
            Statement stmt=null;
            ResultSet rs=null;
    
            //保存登录结果,默认为false
            boolean loginJieGuo = false;
    
            try{
                //1.注册驱动
                Class.forName(driver);
                //2.获取连接
                conn=DriverManager.getConnection(url,user,password);
                //3.获取数据库操作对象
                stmt=conn.createStatement();
                //4.执行sql语句
                String sql="select * from t_user where loginName='"+userLoginInfo.get("loginName")+"' " +
                        "&& loginPwd='"+userLoginInfo.get("loginPwd")+"'";
                rs=stmt.executeQuery(sql);
                //5.处理查询结果集
                if (rs.next()){
                    //登录成功,将登录结果改为true
                    loginJieGuo=true;
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                //6.关闭资源
                if (rs != null) {
                    try{
                        rs.close();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
                if (stmt != null) {
                    try{
                        stmt.close();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
                if (conn != null) {
                    try{
                        conn.close();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
            //执行完后返回登陆结果
            return loginJieGuo;
        }
    
    //resource.properties配置文件
    driver =com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://127.0.0.1:3306/myjdbc
    user=root
    password=abc1234.
    

    3.主函数调用方法

        public static void main(String[] args) {
            //初始化一个界面
            Map<String,String> userLoginInfo=initUI();
            //验证用户名和密码
            boolean loginSuccess = login(userLoginInfo);
            //输出验证结果
            System.out.println(loginSuccess?"登陆成功!":"登陆失败!");
        }
    

    以上程序存在SQL注入问题

    用户名:fdsa
    密码:fdsa' or '1'='1
    登陆成功
    这种现象被成为SQL注入。(黑客经常使用)
    

    导致sql注入的根本原因是?
    用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程,导致sql语句的愿意被扭曲,进而达到sql注入。

    解决sql注入问题:
    只要用户提供的信息不参与sql语句的编译过程,问题就解决了。即使用户提供的信息中含有sql语句的关键字,但没有参与编译,不起作用。

    要想用户信息不参与sql语句的编译,那么必须使用
    java.sql.PreparedStatement

    PreparedStatement接口继承了java.sql.Statement
    PreparedStatement是属于预编译的数据库操作对象。
    PreparedStatement的原理 是:预先对sql语句的框架进行编译,然后在给sql语句传值。

    • 1.将原第三步:获取数据库操作对象中
      Statement stmt=null;修改为 PreparedStatement stmt=null;


      解决sql注入1
    • 2.修改原sql语句

    String sql="select * from t_user where loginName='"+loginName+"'&& loginPwd='"+loginPwd+"'";
    改为
    String sql="select * from t_user where loginName= ? && loginPwd= ? ";
    

    ?为填充符,一个?将来接受一个“值”。且占位符不能用单引号括起来

    解决sql注入2
    • 3.将原第四步:执行sql语句放在第三步:获取预编译的数据库操作对象中进行


      解决sql注入3
    • 4.在第四步:执行sql前,给占位符传值


      解决sql注入4

    数据是什么类型的,就是set什么类型的方法。如int,setInt()。
    setXxxx(int parameterIndex,Xxxx x )
    parameterIndex:占位符的下标
    x:值
    JDBC中下标从1开始

    解决sql注入5

    Statement与prepareStatement对比:

    1.Statement存在sql注入现象,prepareStatement解决了SQL注入问题
    2.prepareStatement效率较高一些
    MySQL数据库中,第一条指令编、译执行后,如果第二条指令与第一条完全一致,则不会再编译,直接执行。
    因为用户名与密码每次都不一样,故Statement每次都要先编译再执行。


    因为整个sql语句是固定的,故prepareStatement只有第一次需要编译,而后可以直接执行。(?也不会变,因为传值时间在编译之后)

    3.prepareStatement会在编译阶段做类型的安全检查

    什么情况下必须使用Statement呢?

    业务方面要求必须支持SQL注入时(即业务要求是需要进行SQL语句拼接的)
    如:select * from t_user order by xxx desc;降序排列,此语句为select * from t_user order by xxx 拼接desc。
    若:select * from t_user order by xxx ?再调用setString(1,desc)进行赋值。
    结果为:select * from t_user order by xxx ‘desc’;这不符合sql语句的规范。
    故当业务必须支持sql注入(sql拼接)时,必须使用Statement。如果只是给sql语句传值,使用prepareStatement。


    JDBC的事务自动提交机制

    事务自动提交
    1.将自动提交机制改为手动提交
    conn.setAutoCommit(false);
    应在预编译前开启事务
    2.测试是否手动提交事务
    String s=null;
    s.toString();
    

    3.事务结束,手动提交数据
    conn.conmmit();
    4.手动设置回滚

    //如果有异常,则在catch里设置回归
    try{
    ......
    }catch(Exception e){
      if(conn!=null){
        try{
        conn.rollback();//设置回滚
    }catch(SQLException e1){
        e1.printStackTrace();
    }
    }
    }
    

    JDBC工具类的封装及使用

        /*
        *   工具类中的构造方法都是私有的,
        *   因为工具类当中的方法都是静态的,
        *   不需要new对象,直接采用类名调用。
        * */
        private DBuntil(){}
    
        /*
        *   注册驱动只需要执行一次
        *   静态代码块在类加载时执行,且只执行一次
        * */
        static {
            try {
                Class.forName("com.mysql.cj.jdbc.Driver");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    
        /*
        *   获取数据库连接对象
        *   返回连接对象
        * */
        public  static Connection getConnection() throws SQLException{
            return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/myjdbc","root","abc1234.");
        }
    
        /*
        *    执行DQL语句
        *    conn连接对象
        *   ps数据库操作对象
        *   rs结果集
        * */
        public static void close(Connection conn, Statement ps, ResultSet rs){
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /*
         *    执行DML语句
         *    conn连接对象
         *   ps数据库操作对象
         * */
        public static void close(Connection conn, Statement ps){
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    
    JDBC工具类的使用
    使用JDBC工具类进行模糊查询

    锁的概念

    • 行级锁(for update)又被称为悲观锁
      select * from school where age>20 for update;
      在select语句后+for update表示,当前事务没有结束前,对其他事务来说,对满足条件“age>20”的数据,所在行的字段都无法修改。

      行级锁
    • 乐观锁
      多线程并发都可以对数据进行修改,不需要盘对,但需要一个版本号。


      乐观锁示例

    笔记来源:B站动力节点 jdbc学习视频

    视频链接:https://www.bilibili.com/video/BV1Bt41137iB?spm_id_from=333.788.b_636f6d6d656e74.11

    相关文章

      网友评论

          本文标题:JDBC-数据库

          本文链接:https://www.haomeiwen.com/subject/gwsiultx.html