JDBC讲解

一、概述

1.1 简介

JDBC全称java database connectivity,Java数据库连接技术,就是使用java语言连接数据库

1.2本质

JDBC是一种规范,其本质是sun公司制定的一套接口。
那么sun公司为什么要制定这么一套接口呢?

因为数据库有很多种,如mysql,Oracle,SQLserver等,而每一种数据库的底层实现原理都不同,
如果不存在jdbc这套接口,那么作为java程序员要去连接数据库就要根据不同的数据库写不同的代码,
连接mysql要写一套,连接oracle又要重新写一套。。。。。。制定了一套接口后,各大数据库厂家都去实现这个接口,
之后我们要连接数据库就可以面向这套接口编程,不用关心不同数据库底层的实现原理,需要连接不同数据库,直接更换对应的驱动即可。

请添加图片描述

1.3 编码模拟本质

编码模拟sun公司制定接口,数据库厂家实现接口,然后我们面向接口调用

模拟sun公司

public interface JDBC {

    /**
     * 连接数据库的方法
     */
    void getConnection();
}

模拟数据库厂家

/**
 *实现类被称为驱动(mysql驱动)
 *
 * mysql数据库厂家的程序员负责实现jdbc接口
 */

public class Mysql implements JDBC {
    @Override
    public void getConnection() {
        System.out.println("连接到mysql数据库");
    }
}

/**
 * oracle驱动
 *
 * oracle数据库厂家的程序员负责实现接口
 */

public class Oracle implements JDBC {
    @Override
    public void getConnection() {
        // 具体如何连接,不需要我们关心,这是数据厂家的程序员的事儿
        System.out.println("连接到Oracle数据库");
    }
}

Java程序员

public class JavaProgram {
    public static void main(String[] args) {
        // 多态,父类型引用指向子类型对象,直接调用接口中的方法,就可以连接到你想要连接的数据库
        JDBC jdbc = new Mysql();//new Oracle();   // 这个就相当于注册驱动,注册哪个数据库的驱动,就连接哪个数据库
        jdbc.getConnection();   // 面向接口调用方法,不需要做修改
}

(注:代码发给大家,可以去看看然后自己运行一下)

二、编码

2.1 编程六步

jdbc编程六步:
	1. 注册驱动(告诉java程序,即将要连接的是哪个品牌的数据库)
	2. 获取连接(表示jvm的进程和数据库进程之间的通道打开了)
	3. 获取数据库操作对象(专门执行sql语句的对象)
	4. 执行sql语句
	5. 处理查询结果集
	6. 释放资源

2.2 上机实操

2.2.1 插入数据(删除,修改类似)

public class InsertDemo {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        try {
            // 1.注册驱动
            DriverManager.registerDriver(new com.mysql.jdbc.Driver());
            // 2.获取连接
            String url = "jdbc:mysql://localhost:3306/jdbc_train";  //数据库的url
            String user = "root";   // 用户
            String password = "root";   // 密码
            conn = DriverManager.getConnection(url, user, password);
            // 3.获取数据库操作对象
            stmt = conn.createStatement();
            // 4.执行sql
            String sql = "insert into t_user(uname, upwd) values('zhaoliu', '123456')";
            int result = stmt.executeUpdate(sql);   // 执行sql,返回数据库中改变的记录条数
            if(result == 1){
                System.out.println("插入成功");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if(stmt != null){
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2.2.2 查询

public class SelectDemo {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            //1. 注册驱动
            DriverManager.registerDriver(new com.mysql.jdbc.Driver());
            //2. 获取连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_train", "root", "root");
            //3. 获取数据库操作对象
            stmt = conn.createStatement();
            //4. 执行sql
            String sql = "select id,uname,upwd from t_user";
            rs = stmt.executeQuery(sql);    // 执行sql语句,返回一个查询结果集ResultSet对象

            //处理查询结果集
            while(rs.next()){
                // 取数据的第一种方法,使用位置下标,jdbc中所有位置下标都是从1开始的
                /*
                int id = rs.getInt(1);
                String uname = rs.getString(2);
                String password = rs.getString(3);
                */

                // 取数据的第二种方法,
                /*
                * 注:括号里写的是查询结果的列名,而不是表的字段名,如果在查询时起了别名,那么括号里应该写别名
                * */
                int id = rs.getInt("id");
                String uname = rs.getString("uname");
                String password = rs.getString("upwd");

                System.out.println(id + " " + uname + " " + password);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //6. 释放资源
            if(rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(stmt != null){
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


2.2.3 优化

2.2.3.1 注册驱动优化

看mysql驱动Driver类的源码
在这里插入图片描述

源码中,在静态代码块里面有一行代码

DriverManager.registerDriver(new Driver());

这行代码就是注册了mysql的驱动,所以我们注册驱动时,不用自己写这一行代码,只需要让对应的驱动类的这个静态代码块执行就可以。

那么,静态代码块是什么呢?怎么让他执行呢?
静态代码块就是一个初始化块,它会在类加载的时候执行一次。(关于静态代码块的详细内容,大家可以自行查阅相关博客,这里不做多的赘述)

那怎么让类加载呢?
链接: 大家可以看看这篇文章
在反射机制中,使用Class.forName(“完整类名”)获取某个类的字节码文件时,会进行该类的类加载,所以注册驱动的代码可以如下编写

Class.forName("com.mysql.jdbc.Driver");	// 类加载,静态代码块执行,注册驱动

注:大家可能还不了解反射机制,这是javase后面的内容,可以先去了解,当下大家记住这行代码就行。还有大家可能还不明白为什么要用这种方式来注册驱动,下面会解答这个疑惑。

2.2.3.2 属性配置文件优化

在上面代码中,我们把驱动的注册,获取连接中的url,用户名,密码都写死在了代码里面,如果后期要换一个数据库,是不是还得去改代码,改完代码就要重新进行编译,对于我们写的这种小demo没啥影响,那如果是一个已经上线的项目,那就很麻烦,不会允许轻易改源代码。所以,我们把这些需要经常更改的值,写入属性配置文件中,有更改需求时,在配置文件中更改就可以

优化后代码:

public class PropertiesSelectDemo {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        try {
            //1. 注册驱动
            Class.forName(bundle.getString("driver"));
            //2. 获取连接
            String url = bundle.getString("url");
            String user = bundle.getString("user");
            String password = bundle.getString("password");
            conn = DriverManager.getConnection(url, user, password);
            //获取数据库连接对象
            stmt = conn.createStatement();
            //4. 执行sql
            String sql = "select id,uname,upwd from t_user";
            rs = stmt.executeQuery(sql);    // 执行sql语句,返回一个查询结果集ResultSet对象

            //5. 处理查询结果集
            while(rs.next()){
                int id = rs.getInt("id");
                String uname = rs.getString("uname");
                String pwd = rs.getString("upwd");

                System.out.println(id + " " + uname + " " + pwd);
            }

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            //6. 释放资源
            if(rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }if(stmt != null){
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

jdbc.properties文件

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_train
user=root
password=root

2.2.4 sql注入攻击

2.2.4.1 演示

编写了一个用户登录demo,演示sql攻击

t_user表
在这里插入图片描述

正确的输入用户名和密码:
在这里插入图片描述
错误的输入用户名或密码:
在这里插入图片描述
sql注入攻击:
在这里插入图片描述

2.2.4.2 分析

原因:
使用Statement执行sql语句时,使用字符串拼接的方式读取用户输入并 生成对应的sql语句,然后对sql语句进行编译。如果用户输入的信息中含有 sql语句的关键字,那么就会导致sql语句原意被扭曲,进而达到sql注入。

解决:
只要用户的输入信息不参与sql语句的编译过程,就不会造成sql注入了。而要想用户输入信息不参与sql语句的编译就必须使用java.sql.PreparedStatement
PreparedStatement接口继承了java.sql.Statement
PreparedStatement是属于预编译的数据库操作对象
PreparedStatement的原理是:预先对sql语句的框架进行编译,然后给sql语句传值

更改后(部分代码,完整代码大家看我发的源码):

private static boolean login(Map<String, String> userLoginInfo) {
        boolean loginSuccess = false;
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        //用户名
        String userName = userLoginInfo.get("userName");
        //密码
        String userPwd = userLoginInfo.get("userPwd");
        //读取属性配置文件
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        try {
            //注册驱动
            Class.forName(bundle.getString("driver"));
            //获取连接
            String url = bundle.getString("url");
            String user = bundle.getString("user");
            String password = bundle.getString("password");
            conn = DriverManager.getConnection(url, user, password);
            //获取预编译的数据库操作对象
            //sql语句的框子,其中一个?,表示一个占位符,一个?将来接收一个值,注意:占位符不能用单引号括起来
            String sql = "select id, uname, upwd from t_user where uname = ? and upwd = ?";
            ps = conn.prepareStatement(sql);
            //执行sql
            // 给sql占位符传值
            ps.setString(1, userName);   // 1表示第一个占位符
            ps.setString(2, userPwd);
            rs = ps.executeQuery();
            //处理查询结果
            if(rs.next()){
                loginSuccess = true;
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            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();
                }
            }
        }
        return loginSuccess;
    }

2.2.5 事务

jdbc事务默认自动提交,执行一条DML语句就提交一次,但是在实际应用场景中,一个业务常常需要很多条DML语联合完成,必须保证这些 DML语句同时成功或者同时失败。所以需要我们手动提交事务。
如何设置手动提交呢?
在java.sql.Connection接口中有一个setAutoCommit()方法
在这里插入图片描述
调用这个方法,传值false即可将自动提交改为手动提交。
需要提交和回滚事务时,也是调用这个接口中的对应方法。
提交事务:conn.commit();
回滚事务:conn.rollback();
例子:

public class ShiWuDemo {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps1 = null;
        PreparedStatement ps2 = null;
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        try {
            //1.注册驱动
            Class.forName(bundle.getString("driver"));
            //2.获取连接
            conn = DriverManager.getConnection(
                    bundle.getString("url"),
                    bundle.getString("user"),
                    bundle.getString("password"));
            // 改为手动提交
            conn.setAutoCommit(false);
            String sql1 = "insert into t_user(uname, upwd) values(?, ?)";
            String sql2 = "insert into t_user(uname, upwd) values(?, ?)";
            //3.获取预编译的操作对象
            ps1 = conn.prepareStatement(sql1);
            ps2 = conn.prepareStatement(sql2);

            ps1.setString(1,"zhangsan11");
            ps1.setString(2,"888");

            ps2.setString(1,"zhangsan22");
            ps2.setString(2,"888666");

            //4. 执行sql
            int result1 = ps1.executeUpdate();
            // 这一行代码会抛出ArithmeticException异常,导致第二条insert语句无法执行
            int tmp = 1/0;
            int result2 = ps2.executeUpdate();

            if(result1==1 && result2==1){
                //代码能执行到这,并且sql执行结果正确,说明业务完成,可以提交事务
                conn.commit();
            } else {
                // sql执行结果不正确,回滚
                conn.rollback();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (ArithmeticException e) {
            e.printStackTrace();
            // 出现异常回滚
            if(conn != null){
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
        } finally {
            if(ps1 != null ){
                try {
                    ps1.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(ps2 != null ){
                try {
                    ps2.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn != null ){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2.2.6 封装工具类

jdbc技术就是上面所讲到的六步,但是,如果每次访问数据库都编写完整的六步代码,重复代码多,代码冗余,作了很多无用功,所以可以自己写一个工具类,对jdbc六步编程进行封装。代码如下:

public class DBUtil {
    private static ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
    private static String driver = bundle.getString("driver");
    private static String url = bundle.getString("url");
    private static String user = bundle.getString("user");
    private static String password = bundle.getString("password");
    static {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    // 工具类的构造方法一般都是私有
    private DBUtil(){}

    /**
     * 获取数据库连接对象
     * @return 连接对象
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, user, password);
    }

    /**
     * 关闭资源
     * @param conn 数据库连接对象
     * @param ps    数据库操作对象
     * @param 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();
            }
        }
    }
}

三、总结

jdbc是用来连接数据库,做增删改查操作的一门技术,它本质上是一套接口,我们操作数据库时就是面向这套接口进行编程。

jdbc编程分6步:

  1. 注册驱动
  2. 获取连接
  3. 获取数据库操作对象
  4. 执行sql
  5. 处理结果集
  6. 关闭资源

其中

  1. 注册驱动可以利用反射机制,使对应的驱动类类加载执行其中的静态代码块,从而注册驱动,这样做的目的是,可以将需要的驱动写在配置文件中,方便后续的对驱动的切换。
  2. 获取数据库操作对象时,我们通常都是获取预编译的数据库操作对象PreparedStatement,使用它可以使用户输入的信息不参与sql的编译过程,从而防止sql注入攻击

四、补充

idea连接数据库

  1. 点击界面右上角的Database
    在这里插入图片描述

  2. 点击+号,并选择要连接的数据库,这里选择mysql
    在这里插入图片描述

  3. 填写连接相关信息
    在这里插入图片描述

  4. 测试连接,成功后,点击应用
    在这里插入图片描述

  5. 右上角出现如下界面,连接成功
    在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值