JDBC

作者: zhl 分类: MySQL 发布时间: 2023-09-02 13:15

一、JDBC概述

JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。
Java 程序员使用JDBC ,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作

Java程序员实际上只要面向接口编程即可。

JDBC API

JDBC API 是一系列的接口,它统一和规范了应用程序与数据库的连接,执行SQL语句,并得到返回结果等各类操作,相关类和接口在java.sql与javax.sql包中

JDBC 程序编写步骤:

  1. 注册驱动 - 加载Diver类
  2. 获取连接 - 得到Connection
  3. 执行增删改查 - 发送SQL 给MySQL执行
  4. 释放资源 - 关闭相关

前置工作:移入驱动 jar包

二、JDBC-连接数据库

获取数据库连接的五种方式

  • 方式一:

    public static void main(String[] args) throws SQLException {
        //前置工作,在项目下创建一个文件夹,如libs
        // 将mysql.jar 拷贝到该目录下,点击add as libraries 加入到项目中
        //1. 注册驱动
        Driver driver = new Driver();
    
        //2. 得到连接
        //(1) jdbc:mysql:// 规定好表示协议,通过jdbc的方式连接mysql
        //(2) localhost 主机,可以是 ip地址
        //(3) :3306 表示mysql监听的端口
        //(4) jdbc 连接到 mysql 的哪个数据库
        //(5) mysql 的连接本质就是前面学过的socket连接
        String url = "jdbc:mysql://localhost:3306/jdbc";
        //将 用户名和密码 放入到Properties 对象
        Properties properties = new Properties();
        properties.setProperty("user","root");      //用户
        properties.setProperty("password","15737979065");   //密码
        Connection connect = driver.connect(url, properties);
    
        //3. 执行SQL
    //        String sql = "insert into actor values(null,'张三','男','2003-10-11','110')";
    //        String sql = "update actor set name = '丁真' where id = 1";
        String sql = "delete from actor where id = 1";
        //statement 用于执行静态SQL语句并返回其生成的结果的对象
        Statement statement = connect.createStatement();
        int rows = statement.executeUpdate(sql);    //如果是 DML 语句,返回的就是影响行数
        System.out.println(rows > 0 ? "成功" : "失败");
    
        //4. 关闭资源
        statement.close();
        connect.close();
    
    }
  • 方式二:
    方式1的不足 会直接使用 com.mysql.jdbc.Driver(), 属于静态加载,灵活性差,依赖强。

    public void connect02() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
    //使用反射 动态 加载Driver 类,更加灵活,减少依赖性
    Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
    Driver driver = (Driver) clazz.newInstance();
    
    String url = "jdbc:mysql://localhost:3306/jdbc";
    Properties info = new Properties();
    info.setProperty("user","root");
    info.setProperty("password","15737979065");
    
    Connection connect = driver.connect(url, info);
    System.out.println("方式二:"+connect);     
    }
  • 方式三:使用 DriverManager 替换 Driver 进行统一管理

    public void connect03() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
        //使用反射 加载Driver
        Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver)clazz.newInstance();
    
        String url = "jdbc:mysql://localhost:3306/jdbc";
        String user = "root";
        String password = "15737979065";
    
        /*Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","15737979065");*/
    
        DriverManager.registerDriver(driver);   //注册Driver 驱动
    
        //getConnection(url,user,password)      对于 给定的数据库url 建立连接
        Connection connection = DriverManager.getConnection(url, user,password);    //可以直接是 String 也可以是 Properties 对象
    //        Connection connect = driver.connect(url, info);
        System.out.println("方式三:"+ connection);
    
    }
  • 方式四:使用 Class.forName 自动完成注册驱动 => 推荐使用

    public void connect04() throws ClassNotFoundException, SQLException {
    //使用反射加载了 Driver 类
    //在加载 Driver 类时,完成了注册
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://localhost:3306/jdbc";
    String user = "root";
    String password = "15737979065";
    Connection connection = DriverManager.getConnection(url, user, password);
    
    System.out.println("方式四:"+connection);
    }

    1.可以无需 Class.forName("com.mysql.cj.jdbc.Driver"); mysql 5.1.6 之后
    2.从 jdk 1.5 以后使用了 jdbc4,不再需要显示调用 Class.forName() 注册驱动而是自动调用驱动 jar 包下 META-INF\services\java.sqlDriver 文本中的类名称去注册
    3.建议还是写上 Class.forName("com.mysql.cj.jdbc.Driver") ,更加明确

      package com.mysql.cj.jdbc;
      import java.sql.DriverManager;
      import java.sql.SQLException;
      public class Driver extends NonRegisteringDriver implements java.sql.Driver {
        public Driver() throws SQLException {
        }
    
        static {
            try {
                DriverManager.registerDriver  (new Driver());
            } catch (SQLException var1) {
                throw new RuntimeException("Can't register driver!");
            }
        }
    }

    在使用DriverManager 替换Driver 统一管理时,可以不注册 Driver 驱动
    通过反射加载 Class.forName("com.mysql.cj.jdbc.Driver") 时 ,在 Driver 类中(上面代码)存在静态代码块,在 加载 Driver 类时,执行静态代码块,创建 Driver 驱动DriverManager.registerDriver(new Driver());

  • 方式五:创建 .properties 文件,以配置的形式,是 mysql 的连接更灵活。避免硬编码 => 推荐使用

    # .properties 文件
    user=root
    password=15737979065
    url=jdbc:mysql://localhost:3306/jdbc
    driver=com.mysql.cj.jdbc.Driver
    public void connect05() throws IOException, ClassNotFoundException, SQLException {
    
    // 通过 properties 对象获取配置文件信息
    Properties properties = new Properties();
    properties.load(new FileInputStream("src\\mysql.properties"));
    
    String user = properties.getProperty("user");
    String password = properties.getProperty("password");
    String url = properties.getProperty("url");
    String driver = properties.getProperty("driver");
    
    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection connection = DriverManager.getConnection(url, user, password);
    
    System.out.println("方式五:"+ connection);
    }

三、JDBC-ResultSet

ResultSet 结果集

  1. 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
  2. ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
  3. next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false, 因此可以在while循环中使用循环来遍历结果集

public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
    // 通过 properties 对象获取配置文件信息
    Properties properties = new Properties();
    properties.load(new FileInputStream("C:\\Users\\zhl36\\JavaSEcode\\chapter20_jdbc\\src\\mysql.properties"));

    String user = properties.getProperty("user");
    String password = properties.getProperty("password");
    String url = properties.getProperty("url");
    String driver = properties.getProperty("driver");

    //1. 注册驱动
    Class.forName(driver);
    //2. 得到连接
    Connection connection = DriverManager.getConnection(url, user, password);
    //3. 得到statement
    Statement statement = connection.createStatement();
    //4. 组织sql 语句
    String sql = "select id,name,sex,borndate from actor";
    //执行给定的 sql 语句,该语句返回一个 ResultSet 对象
    ResultSet resultSet = statement.executeQuery(sql);
    //5. 使用while 取出数据
    while (resultSet.next()){   //让光标向后移动,如果没有更多行,则返回false
        int id = resultSet.getInt(1);           //获取该行的第 1 列
        String name = resultSet.getString(2);   //获取该行的第 2 列
        String sex = resultSet.getString(3);    //获取该行的第 3 列
        Date date = resultSet.getDate(4);       //获取该行的第 2 列
        System.out.println(id +"\t" + name + "\t" + sex + "\t" + date);
    }

    //6. 关闭资源
    resultSet.close();
    statement.close();
    connection.close();

}

四、JDBC-SQL注入问题

Statement对象,用于执行SQL语句并返回其生成的结果的对象

在连接建立后,需要对数据库进行访问,执行命名或是SQL语句,可以通过

  • Statement [存在 SQL注入]
  • PreparedStatement [预处理]
  • CallableStatement [存储过程]

Statement 对象执行SQL 语句,存在SQL 注入风险
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL 语句段或命令,恶意攻击数据库。

要防范SQL 注入,只要用 PreparedStatement (从Statement扩展而来) 取代Statement 就可以了。

例如数据库中一张表 有 name 和 pwd 字段
如果使用 Statement 对象
正常查询 select * from admin where name = 'tom' and pwd = '123'
SQL 注入 用户名为 1 ' or 密码为 or '1' = '1
相当于 select * from admin where name = '1 ' or ' and pwd = 'or '1' = '1'

PreparedStatement

  1. preparedStatement 执行的SQL 语句中的参数用问号 ? 来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数,setXxx()有两个参数,第一个参数是要设置的SQL语句 中的参数的索引(从1 开始),第二个是设置的SQL 语句中的参数值
  2. 调用executeQuery(), 返回ResultSet对象
  3. 调用executeUpdate(), 执行更新,包括增删改

预处理的好处

  1. 不再使用 + 拼接SQL 语句,减少语法错误
  2. 有效的解决了sql 注入问题
  3. 大大减少了编译次数,效率较高
public static void main(String[] args) throws Exception{
    Scanner in = new Scanner(System.in);
    //让用户输入 管理员的名字和密码
    System.out.println("输入管理员的名字:");
    //nextLine() 回车作为结束的标志      next() 空格作为结束的标志
    String adminName = in.nextLine();
    System.out.println("输入管理员的密码:");
    String adminPwd = in.nextLine();       

    // 通过 properties 对象获取配置文件信息
    Properties properties = new Properties();
    properties.load(new FileInputStream("C:\\Users\\zhl36\\JavaSEcode\\chapter20_jdbc\\src\\mysql.properties"));

    String user = properties.getProperty("user");
    String password = properties.getProperty("password");
    String url = properties.getProperty("url");
    String driver = properties.getProperty("driver");

    //1. 注册驱动
    Class.forName(driver);

    //2. 得到连接
    Connection connection = DriverManager.getConnection(url, user, password);

    //3. 得到PreparedStatement
    //3.1 组织sql 语句, SQL 语句中的 ? 相当于 占位符
    String sql = "select name,pwd from admin where name = ? and pwd = ?";
    java.sql.PreparedStatement psmt = connection.prepareStatement(sql);

    //3.2 给 ? 所在的index 赋值
    psmt.setString(1,adminName);
    psmt.setString(2,adminPwd);

    //4. 执行 select 语句
    //   执行 executeQuery 不需要再写 sql
    ResultSet resultSet = psmt.executeQuery();
    if (resultSet.next()){  //查询到一条语句登录成功
        /*String name = resultSet.getString(1);*/
        System.out.println("登录成功");
    }else {
        System.out.println("登录失败");
    }

    resultSet.close();
    psmt.close();
    connection.close();
}

五、JDBC-事务-批量处理

在默认情况下事务自动提交

@Test
public void useTransaction(){
    Connection conn = null;
    String sql = "update account set money = money - 100 where id = 1";
    String sql2 = "update account set money = money + 100 where id = 2";
    PreparedStatement psmt = null;
    try {
        conn = JDBCUtils.getConnection();       //默认自动提交
        //将 Connection 取消自动提交
        conn.setAutoCommit(false);

        //执行第一条sql
        psmt = conn.prepareStatement(sql);
        psmt.executeUpdate();

        int i = 1 / 0;        //模拟异常
        //执行第二条sql
        psmt = conn.prepareStatement(sql2);
        psmt.executeUpdate();

        //如果无异常 提交事务
        conn.commit();
    } catch (SQLException e) {
        //如果发生异常,可以捕获异常,进行回滚,即撤销执行的 SQL
        //默认回滚到事务开始的状态
        System.out.println("执行发生了异常");
        try {
            conn.rollback();
        } catch (SQLException ex) {
            throw new RuntimeException(ex);
        }
        throw new RuntimeException(e);
    }finally {
        JDBCUtils.close(psmt,conn);
    }
}

批处理

当需要成批插入或更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。
JDBC的批量处理语句包括下面方法:

  • addBatch(): 添加需要批量处理的SQL语句或参数
  • executeBatch(): 执行批量处理语句;
  • clearBatch(): 清空批处理包的语句
  • JDBC连接MySQL时,如果要使用批处理功能,请再url中加参数?rewriteBatchedStatements=true

批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高。

addBatch() 本质是初始化了一个 ArrayList集合,用于存放 sql 语句

六、JDBC-数据库连接池

数据库连接池的种类:

  1. JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由第三方提供实现
  2. C3P0 数据库连接池,速度相对较慢,稳定性不错(hibernate,spring) //four years ago 已停止维护
  3. DBCP 数据库连接池,速度相对c3p0较快,但不稳定
  4. Proxool 数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
  5. BoneCP数据库连接池,速度快
  6. Druid 是阿里提供的数据库连接池,集DBCP、C3P0、Proxool 优点于一身的数据库连接池

Druid 实现数据库连接池:

# druid.properties 配置文件
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc?rewriteBatchedStatements=true
username=root
password=15737979065
#initial connection Size
initialSize=10
#min idle connection size
minIdle=5
#max active connection size
maxActive=20
#max wait time
maxWait=5000

JDBCUtilsByDruid的工具类

public class JDBCUtilsByDruid {
    private static DataSource ds;

    static {
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("C:\\Users\\zhl36\\JavaSEcode\\chapter20_jdbc\\src\\druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }

    public static void close(ResultSet set, Statement smt , Connection conn){
        try {
            if(set != null){
                set.close();
            }
            if (smt != null){
                smt.close();
            }
            if(conn != null){
                conn.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

七、JDBC-DBUtils工具类

Apache-DBUtils 的功能介绍

  1. Commons-dbutils 是 Apache 组织的提供的一个开源 JDBC 工具类库,它是对 JDBC的封装,使用dbutils 能极大简化jdbc编码的工作量
    DbUtils 类
  2. QueryRunner 类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理
  3. 使用QueryRunner 类实现查询
  4. ResultSetHandler 接口:该接口用于处理java.sql.ResultSet, 将数据按要求转换为另一种形式

ResultSetHandler 是父接口,在query()方法中可以传入
BeanListHandler<>() =>pojo类的对象 封装到 ArrayList集合中
BeanHandler<>() => pojo 类的对象 数据库表的行数据
ScalarHandler() => 返回Object 单个数据
Object ...Params => 可传入多个Object 参数,给 sql 语句中的 ? 赋值

@Test
//使用apache-DBUtils 工具类 + druid 完成对表的crud 操作
public void testQueryMany() throws SQLException {    //返回结果是多行的情况
    //1. 得到连接(druid)
    Connection conn = JDBCUtilsByDruid.getConnection();
    //2. 使用 DBUtils 类和接口,先引入DBUtils 相关的 jar, 加入到project
    //3. 创建QueryRunner
    QueryRunner qr = new QueryRunner();
    //4. 执行相关的方法,返回 ArrayList
    String sql = "select * from actor where id >= ?";
    /*String sql = "select id,name from actor where id >= ?";*/

    //(1) query 方法就是执行方法就是执行 sql语句,得到resultSet ---封装到 --> ArrayList 集合中
    //(2) 返回集合
    //(3) conn 连接
    //(4) sql:执行的sql 语句
    //(5) new BeanListHandler<>(Actor.class) :将 resultSet -> Actor 对象 -> 封装到 ArrayList
    //    底层使用反射机制去获取 Actor 属性,然后进行封装
    //(6) 1 就是给sql 语句中的 ? 赋值,可以有多个值,因为是可变参数 Object...params
    //(7) 底层得到的 resultSet, 会在 query 关闭,关闭 PreparedStatement
    List<Actor> list = qr.query(conn, sql, new BeanListHandler<>(Actor.class), 1);
    System.out.println("输出 actor ");
    /*System.out.println(list);*/

    for (Actor actor : list){
        System.out.print(actor);
    }
    //5. 释放资源
    JDBCUtilsByDruid.close(null,null ,conn);
}

分析 qr.query(conn, sql, new BeanListHandler<>(Actor.class), 1) 方法

public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
    if (conn == null) {
        throw new SQLException("Null connection");
    } else if (sql == null) {
        throw new SQLException("Null SQL statement");
    } else if (rsh == null) {
        throw new SQLException("Null ResultSetHandler");
    } else {
        Statement stmt = null;
        ResultSet resultSet = null;
        T result = null;
        try {
            if (params != null && params.length > 0) {
                PreparedStatement ps = this.prepareStatement(conn, sql);  //创建PreparedStatement 
                stmt = ps;
                this.fillStatement(ps, params);    //对 SQL ? 进行赋值
                resultSet = this.wrap(ps.executeQuery());   //执行sql,返回resultSet
            } else {
                stmt = conn.createStatement();    
                resultSet = this.wrap(((Statement)stmt).executeQuery(sql));
            }

            result = rsh.handle(resultSet);        //返回的 resultSet --> arrayList[使用反射]
        } catch (SQLException var12) {
            this.rethrow(var12, sql, params);
        } finally {
            this.closeQuietly(resultSet);        //关闭ResultSet 
            this.closeQuietly((Statement)stmt);  //关闭Statement
        }

        return result;
    }
}

八、JDBC-BasicDao

Apachae-dbutils + Druid 简化了JDBC 开发,但还有不足

  1. SQL 语句是固定的,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查
  2. 对于select 操作,如果有返回值,返回类型不能固定,需要使用泛型
  3. 将来的表很多,业务需求复杂,不可能只靠一个Java类完成

DAO :data access object 数据访问对象
这样的通用类,称为 BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作。

public class BasicDAO<T> {  //泛型指定具体类型
    private QueryRunner qr = new QueryRunner();

    //开发通用的 DML 方法,针对任意的表
    public int update(String sql,Object ...params){

        Connection conn = null;
        try {
            conn = JDBCUtilsByDruid.getConnection();
            int update = qr.update(conn, sql, params);
            return update;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsByDruid.close(null,null,conn);
        }

    }

    //返回多个对象(即查询的结果是多行),针对任意表
    public List<T> queryMulti(String sql, Class clazz, Object ...params){
        Connection conn = null;
        try {
            conn = JDBCUtilsByDruid.getConnection();
            return qr.query(conn,sql,new BeanListHandler<T>(clazz),params);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            JDBCUtilsByDruid.close(null,null,conn);
        }
    }

    //返回单行对象(即查询的结果是单行),针对任意表
    public T querySingle(String sql,Class clazz, Object ...params){
        Connection conn = null;
        try {
            conn = JDBCUtilsByDruid.getConnection();
            return qr.query(conn,sql,new BeanHandler<T>(clazz),params);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            JDBCUtilsByDruid.close(null,null,conn);
        }
    }

    //返回单行单列对象(即查询的结果是单值),针对任意表
    public Object queryScalar(String sql, Object ...params){
        Connection conn = null;
        try {
            conn = JDBCUtilsByDruid.getConnection();
            return qr.query(conn,sql,new ScalarHandler<>(),params);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            JDBCUtilsByDruid.close(null,null,conn);
        }
    }

}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注