# JDBC

# JDBC 概述

Java DataBase Connectivity(java 数据库连接) JDBC 规范,MySQL 等驱动实现了 JDBC 规范

# MySQL 驱动

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.48</version>
</dependency>

# JDBC 入门案例

package com.imooc.jdbc.demo1;
import org.junit.Test;
import java.sql.*;
import com.mysql.jdbc.Driver;


public class JDBCDemo1 {

  @Test
  public void demo1() {
    Connection conn = null;
    Statement stmt = null;
    ResultSet resultSet = null;
    try {
      // 1. 加载驱动
      DriverManager.registerDriver(new Driver());
      // 2. 获得连接
      conn = DriverManager.getConnection("jdbc:mysql://192.168.2.130:3306/jdbctest", "root", "password");
      // 3. 创建执行SQL语句的对象并且执行SQL
      String sql = "SELECT * FROM user";
      stmt = conn.createStatement();
      resultSet = stmt.executeQuery(sql);
      while(resultSet.next()) {
        int uid = resultSet.getInt("uid");
        String username = resultSet.getString("username");
        String password = resultSet.getString("password");
        String name = resultSet.getString("name");

        System.out.println(uid + "  " + username + "  " + password + "  " + name);
      }

    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      // 4. 释放资源
      if (resultSet != null) {
        try {
          resultSet.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
        resultSet = null;
      }

      if (stmt != null) {
        try {
          stmt.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
        stmt = null;
      }

      if (conn != null) {
        try {
          conn.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
        conn = null; // 垃圾回收机制更早回收对象
      }
    }
  }

}

# JDBC 常用 API

# DriverManager()

驱动管理类

主要作用:

  • 注册驱动
    // DriverManager.registerDriver(new Driver()); // 会导致驱动加载两次
    // 实际开发中注册驱动会使用如下方式
    // 加载类的同时类内部的静态代码块会自动执行注册
    Class.forName("com.mysql.jdbc.Driver");
    
  • 获得连接
    Connection getConnection(String url, String username, String password);
    // url写法: jdbc:mysql://localhost:3306/db_name
    // jdbc: 协议  mysql: 子协议
    // url简写: jdbc:mysql:///jdbc  连接本地 localhost:3306 简写
    

# Connection

连接对象

主要作用:

  • 创建执行 SQL 语句的对象
    Statement craeteStatement() //执行SQL语句,有SQL注入的漏洞存在
    PreparedStatement prepareStatement(String sql) //预编译SQL语句,解决了SQL注入漏洞
    CallableStatement prepareCall(String sql) //执行SQL中存储过程
    
  • 进行事务的管理
    setAutoCommit(boolean autoCommit) //设置事务是否自动提交
    commit() //事务提交
    rollback() //事务回滚
    

# Statement

  • 执行 SQL 语句
    boolean execute(String sql) // 执行SQL语句,执行select语句返回true,否则返回false
    ResultSet executeQuery(String sql) // 执行SQL中select语句
    int executeUpdate(String sql) // 执行SQL中insert/update/delete 语句
    
  • 执行批处理操作
    addBatch(String sql) // 添加到批处理
    executeBatch() // 执行批处理
    clearBatch() // 情况批处理
    

# ResultSet

结果集:查询语句(select)查询到的结果

  • 获取查询到的结果
    next() //判断是否有下一条记录并将光标向后移动
    getXXX() // 获取字段的值 getInt getString getObject ...
    

# 资源释放

JDBC 程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是 ResultSet,Statement 和 Connection 对象。

特别是 Connection 对象,它是非常稀有的资源,用完后必须马上释放,Connection 的使用原则是尽量晚创建,尽量早释放。

# CRUD 操作

# 插入操作

stmt = conn.createStatement();
String sql = "INSERT INTO user VALUES(null, 'eee', '123', '张三')";
int i = stmt.executeUpdate(sql);
if (i > 0) {
    System.out.println("插入成功");
}

# 更新操作

stmt = conn.createStatement();
String sql = "UPDATE user SET username='qqq',password='456',name='赵六' WHERE id=4";
int i = stmt.executeUpdate(sql);
if (i > 0) {
    System.out.println("修改成功");
}

# 删除操作

stmt = conn.createStatement();
String sql = "DELETE FROM user WHERE uid=4";
int i = stmt.executeUpdate(sql);
if (i > 0) {
    System.out.println("删除成功");
}

# 查询操作

# 查询所有记录

stmt = conn.createStatement();
String sql = "SELECT * FROM user";
resultSet = stmt.executeQuery(sql);
while (resultSet.next()) {
    System.out.println(resultSet.getInt("uid") + "  " + resultSet.getString("username") + "  " + resultSet.getString("password") + "  " + resultSet.getString("name"));
}

# 查询一条记录

stmt = conn.createStatement();
String sql = "SELECT * FROM user WHERE uid=2";
resultSet = stmt.executeQuery(sql);
if (resultSet.next()) {
    System.out.println(resultSet.getInt("uid") + "  " + resultSet.getString("username") + "  " + resultSet.getString("password") + "  " + resultSet.getString("name"));
}

# 抽取工具类

package com.imooc.jdbc.utils;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JDBCUtils {
  private static final String driverClass;
  private static final String url;
  private static final String username;
  private static final String password;

  static {
    // 加载属性文件并解析
    Properties props = new Properties();
    // 使用类的加载器的方式获取文件的输入流
    InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
    try {
      props.load(is);
    } catch (IOException e) {
      e.printStackTrace();
    }
    driverClass = props.getProperty("driverClass");
    url = props.getProperty("url");
    username = props.getProperty("username");
    password = props.getProperty("password");
  }

  /*
  注册驱动
   */
  public static void loadDriver() throws ClassNotFoundException {
    Class.forName(driverClass);
  }

  /*
   获得连接
   */
  public static Connection getConnection() throws Exception {
    loadDriver();
    Connection conn = DriverManager.getConnection(url, username, password);
    return conn;
  }

  /*
  资源释放
   */
  public static void release(Statement stmt, Connection conn) {
    if (stmt != null) {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      stmt = null;
    }

    if (conn != null) {
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      conn = null;
    }
  }

  public static void release(ResultSet rs, Statement stmt, Connection conn) {
    if (rs != null) {
      try {
        rs.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      rs = null;
    }

    if (stmt != null) {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      stmt = null;
    }

    if (conn != null) {
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      conn = null;
    }
  }
}

# SQL 注入漏洞

# 演示

package com.imooc.jdbc.demo1;

import com.imooc.jdbc.utils.JDBCUtils;
import org.junit.Test;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

public class JDBCDemo4 {
  /**
   * 测试SQL注入漏洞
   * 原因在于用户可以输入SQL关键字同时系统内采用了字符串拼接的方式
   */
  @Test
  public void demo1(){
    // SELECT * FROM user WHERE username='aaa' OR '1=1' AND password='adc'
    // '1=1' AND password='adc' False
    // username='aaa' True
    // True OR False  为  True
    // 只要知道用户名就可以不用密码访问
    boolean flag = JDBCDemo4.login("aaa' OR '1=1", "adc");
    // SELECT * FROM user WHERE username='aaa' -- ' AND password='adc'
    // -- ' AND password='adc' 被注释了
//    boolean flag = JDBCDemo4.login("aaa' -- ", "adc");
    if(flag == true){
      System.out.println("登录成功!");
    } else {
      System.out.println("登录失败!");
    }
  }

  public static boolean login(String username, String password) {
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    boolean flag = false;

    try {
      conn = JDBCUtils.getConnection();
      stmt = conn.createStatement();
      String sql = "SELECT * FROM user WHERE username='" + username +"' AND password='"+ password +"'";
      rs = stmt.executeQuery(sql);
      // 判断结果集是否有数据
      if (rs.next()) {
        flag = true;
      }

    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      JDBCUtils.release(rs, stmt, conn);
    }
    return flag;


  }
}

# 解决方案

使用 PreparedStatement 解决 SQL 注入漏洞

PreparedStatement 是 Statement 的子接口,它的实例对象可以通过调用 Connection.preparedStatement(sql)方法获得,相对于 Statement 而言有以下优势:

  • PreparedStatement 可以避免 SQL 注入的问题
  • PreparedStatement 可对 SQL 进行预编译(编译一次),提高数据库的执行效率,而 Statement 每次都会编译 SQL
  • PreparedStatement 对于 SQL 中的参数,允许使用占位符的形式进行替换,简化 sql 语句的编写
try {
    conn = JDBCUtils.getConnection();
    String sql = "SELECT * FROM user WHERE username=? AND password=?";
    // 预处理SQL
    pstmt = conn.prepareStatement(sql);
    // 设置参数
    pstmt.setString(1, username);
    pstmt.setString(2, password);
    // 执行SQL
    rs = pstmt.executeQuery();
    // 判断结果集是否有数据
    if (rs.next()) {
        flag = true;
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    JDBCUtils.release(rs, pstmt, conn);
}
return flag;

# PreparedStatement

# 插入数据

package com.imooc.jdbc.demo1;

import com.imooc.jdbc.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;

public class JDBCDemo5 {

  public void demo1() {
    Connection conn = null;
    PreparedStatement pstmt = null;

    try {
      // 获得连接
      conn = JDBCUtils.getConnection();
      // 编写SQL
      String sql = "INSERT user VALUES(null, ?, ?, ?)";
      // 预处理SQL
      pstmt = conn.prepareStatement(sql);
      // 设置参数的值
      pstmt.setString(1, "qqq");
      pstmt.setString(2, "123");
      pstmt.setString(3, "张武");
      // 执行SQL
      int num = pstmt.executeUpdate();
      if (num > 0) {
        System.out.println("插入成功");
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      JDBCUtils.release(pstmt, conn);
    }
  }

}

# 更新数据

try {
    // 获得连接
    conn = JDBCUtils.getConnection();
    // 编写SQL
    String sql = "UPDATE user SET username=?,password=?,name=? WHERE uid=?";
    // 预处理SQL
    pstmt = conn.prepareStatement(sql);
    // 设置参数的值
    pstmt.setString(1, "www");
    pstmt.setString(2, "123456");
    pstmt.setString(3, "张六");
    pstmt.setInt(4, 5);
    // 执行SQL
    int num = pstmt.executeUpdate();
    if (num > 0) {
        System.out.println("更新成功");
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    JDBCUtils.release(pstmt, conn);
}

# 删除数据

try {
    // 获得连接
    conn = JDBCUtils.getConnection();
    // 编写SQL
    String sql = "DELETE FROM user WHERE uid=?";
    // 预处理SQL
    pstmt = conn.prepareStatement(sql);
    // 设置参数的值
    pstmt.setInt(1, 5);
    // 执行SQL
    int num = pstmt.executeUpdate();
    if (num > 0) {
        System.out.println("删除成功");
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    JDBCUtils.release(pstmt, conn);
}

# 查询数据

# 查询多条记录

try {
    // 获得连接
    conn = JDBCUtils.getConnection();
    // 编写SQL
    String sql = "SELECT * FROM user";
    // 预处理SQL
    pstmt = conn.prepareStatement(sql);
    // 设置参数的值
    // 执行SQL
    rs = pstmt.executeQuery();
    while (rs.next()) {
        System.out.println(rs.getInt("uid") + "  " + rs.getString("username") + "  " + rs.getString("password") + "  " + rs.getString("name"));
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    JDBCUtils.release(rs, pstmt, conn);
}

# 查询单条记录

try {
    // 获得连接
    conn = JDBCUtils.getConnection();
    // 编写SQL
    String sql = "SELECT * FROM user";
    // 预处理SQL
    pstmt = conn.prepareStatement(sql);
    // 设置参数的值
    // 执行SQL
    rs = pstmt.executeQuery();
    while (rs.next()) {
        System.out.println(rs.getInt("uid") + "  " + rs.getString("username") + "  " + rs.getString("password") + "  " + rs.getString("name"));
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    JDBCUtils.release(rs, pstmt, conn);
}

# 数据库连接池

连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。

应用程序直接获取连接的缺点:

  • 用户每次请求都需要向数据库获得连接,而数据库创建连接需要消耗较大的资源且创建时间较长
  • 如果创建连接数量过大极易造成数据库服务器内存溢出

常用连接池

  • DBCP
  • C3P0

# 连接池 C3P0

# 依赖包

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.2</version>
</dependency>

# 手动配置

package com.imooc.jdbc.demo1;

import com.imooc.jdbc.utils.JDBCUtils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class DataSourceDemo1 {
  /**
   * 手动设置了连接池
   */
  @Test
  public void demo1() {

    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      // 创建连接池
      ComboPooledDataSource dataSource = new ComboPooledDataSource();
      // 设置连接池参数
      dataSource.setDriverClass("com.mysql.jdbc.Driver");
      dataSource.setJdbcUrl("jdbc:mysql://192.168.2.130:3306/jdbctest");
      dataSource.setUser("root");
      dataSource.setPassword("password");
      dataSource.setMaxPoolSize(20);
      dataSource.setInitialPoolSize(3);
      // 获得连接
      conn = dataSource.getConnection();
      // 编写SQL
      String sql = "SELECT * FROM user";
      // 预编译SQL
      pstmt = conn.prepareStatement(sql);
      // 设置参数
      // 执行SQL
      rs = pstmt.executeQuery();
      while (rs.next()) {
        System.out.println(rs.getInt("uid") + "  " + rs.getString("username") + "  " + rs.getString("password") + "  " + rs.getString("name"));
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      JDBCUtils.release(rs, pstmt, conn);
    }
  }
}


# 使用配置文件



  public void demo2() {

    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      // 重复创建连接池
      // ComboPooledDataSource dataSource = new ComboPooledDataSource();
      // conn = dataSource.getConnection();
      // 抽取工具类保证连接池只初始化一次
      conn = JDBCUtils2.getConnection();
      // 编写SQL
      String sql = "SELECT * FROM user";
      // 预编译SQL
      pstmt = conn.prepareStatement(sql);
      // 设置参数
      // 执行SQL
      rs = pstmt.executeQuery();
      while (rs.next()) {
        System.out.println(rs.getInt("uid") + "  " + rs.getString("username") + "  " + rs.getString("password") + "  " + rs.getString("name"));
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      JDBCUtils2.release(rs, pstmt, conn);
    }
  }
// 工具类
public class JDBCUtils2 {
  // 自动去classPath中读取c3p0-config.xml
  private static final ComboPooledDataSource dataSource = new ComboPooledDataSource();

  /*
   获得连接
   */
  public static Connection getConnection() throws Exception {
    Connection conn = dataSource.getConnection();
    return conn;
  }

  ...
}

上次更新: 2/13/2025, 3:29:47 AM