JDBC-基础

JDBC-基础

一、JDBC简介

讲解

1. 什么是JDBC
  • JDBC(Java DataBase Connectivity,java数据库连接):Sun公司提供的一套规范(接口), 是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。
2. JDBC的作用
  • 实现了Java程序对不同数据库统一操作

  • 没有JDBC时,Java操作数据库的模式:

    • 不同数据库的API,由数据库厂商自己提供,没有统一标准
    • 需要编写不同的Java程序,来操作不同的数据库
  • 有JDBC后,Java操作数据库的模式

    • Sun公司规定了操作数据库的统一规范:JDBC
    • 各数据库厂商在提供操作数据库的API时,都要实现JDBC规范
    • 只需要编写一套程序,就可以操作不同的数据库
3. 数据库驱动:
  • 由数据库厂商提供的,JDBC规范的实现类,打包形成的jar包,叫数据库驱动包

    • 要操作什么数据库,就必须有什么数据库的驱动

小结

  • JDBC:Java操作数据库。由一些接口和工具类组成
  • 好处:实现了对不同数据库的统一访问
  • 数据库驱动:由数据库厂商提供的、Jdbc规范的实现类,打包形成的jar包,叫数据库驱动包
    • 要操作什么数据库,就必须导入什么数据库的驱动包

二、JDBC快速入门

1. 建表语句
drop database if exists heima;
create database heima character set utf8;
use heima;

DROP TABLE IF EXISTS USER;
CREATE TABLE USER(
	id INT PRIMARY KEY AUTO_INCREMENT,
	username VARCHAR(20),
	PASSWORD VARCHAR(50),
	NAME VARCHAR(10),
	birthday DATE,
	age INT,
	sex CHAR(1)
) CHARACTER SET = utf8 COLLATE = utf8_general_ci;
INSERT INTO USER(id,username,PASSWORD,NAME,birthday,age,sex) VALUES (NULL, 'xiaohong', 'xiaohong', '小红', '1999-09-09', 20, '男');
INSERT INTO USER(id,username,PASSWORD,NAME,birthday,age,sex) VALUES (NULL, 'xiaolan', 'xiaolan', '小兰', '1997-09-09', 22, '女');
INSERT INTO USER(id,username,PASSWORD,NAME,birthday,age,sex) VALUES (NULL, 'xiaolv', 'xiaolv', '小吕', '1999-09-09', 20, '女');
INSERT INTO USER(id,username,PASSWORD,NAME,birthday,age,sex) VALUES (NULL, 'xiaozi', 'xiaozi', '小紫', '1999-09-09', 20, '男');
INSERT INTO USER(id,username,PASSWORD,NAME,birthday,age,sex) VALUES (NULL, 'xiaoming', 'xiaoming', '小明', '1999-09-09', 20, '男');
2. JDBC操作的步骤
  1. 导入数据库驱动jar包
  2. 编写程序
    1. 注册驱动:要操作哪种数据库,就要注册哪种数据库的驱动类
    2. 获取连接:连接上数据库
    3. 创建SQL执行平台:SQL执行平台提供了执行SQL语句的方法
    4. 执行SQL语句
    5. 处理结果
    6. 释放资源

实现

1. 导入jar包
2. 编写代码
public class JdbcQuickStart {
    public static void main(String[] args) throws Exception {
        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
        //3.创建SQL执行平台
        Statement statement = connection.createStatement();
        //4.执行SQL语句
        ResultSet resultSet = statement.executeQuery("select * from user");
        //5.处理结果
        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            String name = resultSet.getString("name");
            String birthday = resultSet.getString("birthday");
            int age = resultSet.getInt("age");
            String sex = resultSet.getString("sex");

            System.out.println("id: " + id + ", username: " + username +", password: "+ password + ", name: " + name + ", birthday: " + birthday + ", age: " + age + ", sex: " + sex);
        }
        //6.释放资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}

小结

  • JDBC操作的步骤
    1. 注册驱动
    2. 获取连接
    3. 创建SQL执行平台
    4. 执行SQL语句
    5. 处理结果
    6. 释放资源

三、JDBC的API详解

3.1 注册驱动

讲解
1. API介绍
  • 相关类介绍
类名介绍
java.sql.DriverJDBC规定的驱动类接口
com.mysql.jdbc.DriverMySql驱动包里提供的驱动类,实现了Driver接口
java.sql.DriverManagerJDBC提供的工具类,用于注册驱动
  • DriverManager提供了静态方法,用于注册驱动
方法名返回值
registerDriver(Driver driver)void
2. 注册驱动的方式
  • 方式一(不推荐):使用DriverManager提供的方法注册

    DriverManager.registerDriver(new com.mysql.jdbc.Driver());
    • 注册了2次:

      • com.mysql.jdbc.Driver中有静态代码块,注册了一次

      • 我们的DriverManager.register()方法又注册了一次

    • 存在硬编码问题:代码中写死了,注册的是MySql的驱动,只能操作MySql数据库

  • 方式二:使用反射技术注册

    Class.forName("com.mysql.jdbc.Driver");
    • 只注册了一次
    • 可以把字符串提取到配置文件中,需要修改时只要修改配置文件即可,不需要修改程序源码,解决硬编码问题
小结
  • 相关的API
    • Jdbc提供的驱动类接口:java.sql.Driver
    • MySql驱动包里的驱动类:com.mysql.jdbc.Driver
  • 如何注册驱动:
    • 不建议:DriverManager.registerDriver(new com.mysql.jdbc.Driver())
      • 注册了2次
      • 有硬编码问题
    • 使用这种:Class.forName("com.mysql.jdbc.Driver")
      • 注册了1次
      • 硬编码问题可以解决:提取到配置文件

3.2 获取连接

目标
  • 了解JDBC的Connection接口
  • 掌握获取连接的方法
讲解
1. API介绍
  • 相关类介绍
类名介绍
java.sql.DriverManagerJDBC的工具类,提供了获取连接的方法
java.sql.ConnectionJDBC提供的,数据库连接对象的接口(无需关心具体的实现类,多态)
  • DriverManager提供了静态方法
方法名返回值
getConnection(url, username, password)java.sql.Connection
  • 方法的参数介绍:
    • url:数据库的连接地址
      • 语法:JDBC规定了 由三部分组成,三部分使用:连接
        • 第一部分:协议名,固定写法jdbc
        • 第二部分:子协议名,通常是数据库名,连接MySql写成:mysql
        • 第三部分:数据库地址,具体写法由数据库厂商自己决定://ip:port/database
      • 示例:
        • 完整写法:jdbc:mysql://localhost:3306/heima
        • 简写形式:jdbc:mysql:///heima,表示连接本机、默认端口数据库的heima库
    • username:数据库的用户名
    • password:数据库的密码
2. 获取连接的方式
Class.forName("com.mysql.jdbc.Driver");
//获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
小结
  • 相关的API
    • java.sql.DriverManager:jdbc提供的工具类,提供了获取连接的方法
    • java.sql.Connection:jdbc提供的,连接类的接口,我们得到的是这个接口的实现类对象
  • 如何获取连接:
Connection conn = DriverManager.getConnection("url地址","数据库用户名","数据库密码");
  • url地址:三部分组成,使用:连接
    • 协议名:固定值jdbc
    • 子协议:mysql
    • 具体地址:由数据库厂商规定的,MySql的写法是:
      • 完整写法://数据库ip:端口/database
      • 简写形式:///database, 只有连接本机、3306端口的MySql时,才可以使用简写形式
  • MySql的连接地址示例:jdbc:mysql://localhost:3306/heima

3.3 创建SQL执行平台

讲解
API介绍
  • 相关类
类名介绍
java.sql.StatementJDBC提供的,用于执行SQL语句的对象接口(无需关注具体的实现类)
  • java.sql.Connection对象提供了方法,可以创建SQL执行平台
方法名返回值
createStatement()java.sql.Statement
创建SQL执行平台
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
//创建SQL执行平台
Statement stmt = conn.createStatement();
小结
  • 相关的API:
    • java.sql.Statement:jdbc规定的执行平台的接口
  • 如何获取SQL执行平台:
    • Statement stmt = connection.createStatement()

3.4 执行SQL语句【重点】

讲解
1. API介绍
  • 相关类
类名介绍
java.sql.ResultSetJDBC提供的,用于封装查询结果集的接口
  • java.sql.Statement对象提供了执行不同SQL语句的方法
方法名参数返回值
executeQuery(String sql)DQL语句java.sql.ResultSet,查询结果集对象
executeUpdate(String sql)DML语句int,表示影响的行数
exeucte(String sql)任意语句boolean,表示是否执行了查询
2. 执行SQL语句
  • 执行DQL语句
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
Statement stmt = conn.createStatement();
//执行DQL语句
ResultSet rs = stmt.executeQuery("select * from user");
  • 执行DML语句
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
Statement stmt = conn.createStatement();
//执行DML语句
int count = stmt.executeUpdate("delete from user where id = 1");
  • 执行任意语句
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
Statement stmt = conn.createStatement();
//执行任意语句
boolean isQuery = stmt.execute("create database itcast");
小结
  • 执行DQL:statement.executeQuery(String sql),得到ResultSet结果集
  • 执行DML:statement.executeUpdate(String sql),得到int,表示影响的行数
  • 执行任意语句:statement.execute(String sql),得到boolean,表示 执行的是否是查询
    • 如果true:执行的是查询语句,有结果集ResultSet
    • 如果false:执行的不是查询语句,没有结果集ResultSet

3.5 处理结果集

讲解
1. API介绍
  • 相关类
类名介绍
java.sql.ResultSetJDBC提供的,用于封装查询结果集的对象接口(无需关注具体的实现类)
  • ResultSet对象的结构

    • ResultSet是类似二维表的结构,存储了查询的多行多列的结果
    • ResultSet自带行指针
      • 行指针在哪一行,就能获取哪一行的数据
      • 初始状态的行指针,默认指向第0行之前,必须向下移动一次,才可以获取数据
  • ResultSet对象提供了方法

方法名参数返回值
next()boolean,向下移动行指针,是否移动成功
previous()boolean,向上移动行指针,是否移动成功
getXXX(int columnCount)列序号,从1开始columnCount列的值,XXX指数据类型
getXXX(String columnName)列名称columnName列的值,XXX指数据类型
2. 循环遍历ResultSet
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from user");
//处理结果,循环遍历ResultSet
while(rs.next()){
    int id = rs.getInt("id");
    String username = rs.getString("username");
    System.out.println("id: " + id + ",    username: " + username);
}
小结
  • java.sql.ResultSet:是查询结果集,是n行n列的结构,通过移动行指针,可以获取指针所在行的数据
  • 向下移动行指针:resultSet.next()
    • 如果返回true:向下移动成功,下一行存在
    • 如果返回false:向下移动失败,下一行不存在
  • 获取指针所在行里,某一列的数据:
    • resultSet.getXXX(int 列序号),从1开始
    • resultSet.getXXX(String 列名称)
    • 其中:xxx指的是 想要获取的数据类型,通常和字段类型是对应的

3.6 释放资源

讲解
1. 需要释放的资源
  • 需要释放的资源有:java.sql.ResultSet, java.sql.Statement, java.sql.Connection
    • 以上三个资源都提供了close()方法,用于释放资源
  • 释放的顺序:ResultSet --> Statement --> Connection
    • 和获取的顺序相反
2. 释放资源的方法
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from user");
while(rs.next()){
    int id = rs.getInt("id");
    String username = rs.getString("username");
    System.out.println("id: " + id + ",    username: " + username);
}

//释放资源
rs.close();
stmt.close();
conn.close();
小结

四、JDBC工具类封装

分析

JDBC存在的问题
  • 重复代码问题,每次操作数据库都需要:
    • 注册驱动
    • 获取连接
    • 释放资源
  • 注册驱动的代码,每次操作数据库都要执行一次,而实际只需要执行一次
  • 硬编码问题,代码中写死了,如果连接的数据库有变化,还需要修改源码:
    • 驱动类名
    • 数据库地址
    • 数据库用户名
    • 数据库密码
问题解决的思路
  • 重复代码问题:提取到一个类,设置成为静态的公用方法,随用随调
  • 注册驱动的代码,只需要执行一次:提取到一个类,放到静态代码块中
  • 硬编码问题:提取到配置文件中,如果数据库有变化,只需要修改配置文件,不需要修改源码

实现

  • 创建配置文件jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///heima
username=root
password=root
  • 创建工具类JdbcUtils
public class JdbcUtils {
    private static String url;
    private static String username;
    private static String password;

    static {
        InputStream is = null;
        try {
            //读取properties文件
            is = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
            //加载到properties对象中
            Properties properties = new Properties();
            properties.load(is);
            //获取配置文件中的信息
            String driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
            //注册驱动
            Class.forName(driver);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭流
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 获取连接
     */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, username, password);
    }

    /**
     * 释放资源
     */
    public static void close(ResultSet rs, Statement stmt, Connection conn){
        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();
            }
        }
    }
}

小结

五、预编译对象

1. 登录功能练习

分析
  1. 获取用户在控制台输入的用户名和密码:Scanner
  2. 执行SQL语句,判断用户名和密码是否正确:
  3. 如果正确:输出“登录成功”;否则输出“登录失败”
实现
/**
 * 登录功能
 * @author liuyp
 * @date 2020/01/07
 */
public class DemoLogin {

    public static void main(String[] args) {
        //读取用户在控制台输入的用户名和密码
        Scanner scanner = new Scanner(System.in);

        System.out.println("请输入用户名:");
        String username = scanner.nextLine();
        System.out.println("请输入密码:");
        String password = scanner.nextLine();

        //登录校验,得到结果
        boolean success = login(username, password);

        //显示结果
        if (success) {
            System.out.println("登录成功");
        }else{
            System.out.println("登录失败");
        }
    }

    /**
     * 登录校验
     * @param username 用户名
     * @param password 密码
     * @return 登录校验结果。true表示登录成功, false表示登录失败
     */
    private static boolean login(String username, String password) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            connection = JdbcUtils.getConnection();
            statement = connection.createStatement();
            String sql = "select * from user where username = '"+ username + "' and password = '" + password + "'";
            resultSet = statement.executeQuery(sql);
            if (resultSet.next()) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.close(connection, statement, resultSet);
        }

        return false;
    }
}
小结

2. SQL注入漏洞

分析
  • 在刚刚的登录功能中,正常情况下:
    • 必须输入正确的用户名和密码,才会登录成功;否则会登录失败
  • 但是实际情况是:
    • 请输入用户名:xiaohong, 密码:' or '1' = '1
    • 输入的密码是不正确的,但是结果显示的却是: 登录成功
  • 说明刚刚编写的登录功能代码,存在安全漏洞
讲解
  • 什么是SQL注入漏洞?

    • 通过输入一些特殊的参数,导致最终拼接的SQL语句结构出现了问题,形成的安全漏洞,叫SQL注入
    • 理想的登录SQL语句:
    select * from user where username = 'xiaohong' and password = 'xiaohong'
    
    • 注入后的SQL语句:
    select * from user where username = 'xiaohong' and password = '' or '1' = '1'
    
  • 如何解决SQL注入漏洞?

    • 出现SQL注入漏洞的原因,是拼接SQL语句字符串,导致SQL语句结构改变了
    • 解决的方案是:使用JDBC提供的预编译对象PreparedStatement执行SQL语句,而不是Statement
小结
  • SQL注入漏洞:通过输入一些特殊参数,导致拼接的SQL语句结构发生变化,从而绕过SQL的一些条件
  • 如何解决SQL注入:使用预编译对象PreparedStatement代替掉Statement

3. 预编译详解

分析
  • 使用步骤

    1. 注册驱动

    2. 获取连接

    3. 创建预编译对象,预编译SQL–和之前不同

    4. 执行SQL语句–和之前不同

    5. 处理结果

    6. 释放资源

  • API介绍

讲解
API介绍
什么是预编译对象
  • 预编译对象:是指java.sql.PreparedStatement的对象,它是java.sql.Statement的子接口。
  • 好处:
    • 更安全:可以解决SQL注入漏洞,通过预先编译SQL,SQL语句结构就不会再改变了
    • 效率高:同一语句,多次执行时效率比Statement的效率更高
    • 易于阅读
如何获取预编译对象
  • Connection对象提供了方法:
方法返回值说明
prepareStatement(String sql)PreparedStatement预编译SQL,得到预编译对象
  • 其中,SQL语句需要使用占位符?代替掉参数值,例如:
//原本SQL
String sql = "select * from user where username = '"+username+"' and password = '"+password+"'";

//预编译对象需要的SQL:把参数值替换成占位符 ?
String sql = "select * from user where username = ? and password = ?";
预编译对象的常用API
  • PreparedStatement对象提供了常用方法:
方法返回值说明
setXXX(int 序号, XXX value)给预编译SQL中第个?,设置参数值value
executeQuery()ResultSet执行DQL语句,得到结果集对象
executeUpdate()int执行DML语句,得到影响的行数
execute()boolean执行任意语句,得到boolean:执行的是查询,结果为true;否则false
使用示例
/**
 * 预编译对象 快速入门
 * @author liuyp
 * @date 2020/01/07
 */
public class Demo01PreparedStatementQuickStart {

    public static void main(String[] args) throws Exception {
        //1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
        //3. 创建预编译对象
        String sql = "select * from user where sex = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1, "男");
        //4. 执行SQL语句
        ResultSet resultSet = preparedStatement.executeQuery();
        //5. 处理结果
        while (resultSet.next()) {
            String username = resultSet.getString("username");
            String sex = resultSet.getString("sex");
            System.out.println("username: " + username + ",  sex: " + sex);
        }
        //6. 释放资源
        JdbcUtils.close(connection, preparedStatement, resultSet);
    }
}
小结
  • 使用预编译对象,执行SQL语句:
    1. 注册驱动
    2. 获取连接
    3. 创建预编译对象
    4. 执行SQL语句
    5. 处理结果
    6. 释放资源
  • 如何创建预编译对象?
    • connection.prepareStatement(String sql),返回值:PreparedStatement
      • SQL语句:需要使用占位符?,代替掉参数值
    • 设置SQL语句的参数值:preparedStatement.setXXX(序号, 值)
  • 如何执行SQL语句?
    • executeQuery():执行查询,得到resultSet
    • executeUpdate():执行DML,得到int
    • execute():执行任意语句,得到boolean。true:表示执行的是查询;false:表示执行的不是查询

4. 预编译完成CURD

六、JDBC的事务管理

分析

建表语句
DROP TABLE IF EXISTS account;
CREATE TABLE account(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(20),
	money DOUBLE
);
INSERT INTO account(id,NAME,money) VALUES(NULL, 'tom', 10000);
INSERT INTO account(id,NAME,money) VALUES(NULL, 'jerry', 10000);
事务管理的API
  • java.sql.Connection对象提供了事务管理的方法
方法返回值说明
setAutoCommit(false) (相当于SQL语句:set autocommit=0void开启事务
commit()void提交事务
rollback()void回滚事务
事务管理的步骤
try{
    //1.注册驱动
    //2.获取连接
    //===开启事务===
    //3.创建预编译对象
    //4.执行SQL语句:多次
    //5.处理结果
    //===提交事务===
}catch(Exception e){
    //===回滚事务===
}finally{
    //6.释放资源
}

实现

public class DemoTransfer {
    public static void main(String[] args) throws Exception {
        String from = "tom";
        String to = "jerry";
        Double money = 1000d;

        transfer(from, to, money);
    }

    /**
     * 转账功能
     * @param from 转账人
     * @param to 收款人
     * @param money 转账金额
     */
    private static void transfer(String from, String to, Double money) throws Exception {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            //1. 注册驱动
            //2. 获取连接
            connection = JdbcUtils.getConnection();

            //开启事务
            connection.setAutoCommit(false);

            //3. 创建预编译对象
            String sql = "update account set money = money + ? where name = ?";
            preparedStatement = connection.prepareStatement(sql);

            //4. 执行SQL语句
            //4.1 扣钱
            preparedStatement.setDouble(1, -money);
            preparedStatement.setString(2, from);
            preparedStatement.executeUpdate();

            int i = 1/0;

            //4.2 加钱
            preparedStatement.setDouble(1, money);
            preparedStatement.setString(2, to);
            preparedStatement.executeUpdate();

            //5. 处理结果,提交事务
            connection.commit();
            System.out.println("转账成功,事务提交");
        } catch (Exception e) {
            //5. 处理结果,回滚事务
            if (connection != null) {
                connection.rollback();
            }
            System.out.println("转账失败,事务回滚");
            e.printStackTrace();
        } finally {
            //6. 释放资源
            JdbcUtils.close(connection, preparedStatement, null);
        }
    }
}

小结

  • 事务管理的步骤
try{
    //1.注册驱动
    //2.获取连接
    //===开启事务===
    //3.创建预编译对象
    //4.执行SQL语句
    //5.处理结果
    //===提交事务===
}catch(Exception e){
    //===回滚事务===
}finally{
    //6.释放资源
}
  • 开启事务:connection.setAutoCommit(false)
  • 提交事务:connection.commit()
  • 回滚事务:connection.rollback()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值