JDBC特点的总结


JDBC概述

  • JDBC的全称为:Java DataBase Connectivity(Java数据库连接);
  • 早期没有JDBC的时候与各种数据库进行交互的模式;
  • 现在有了JDBC之后与各种数据库进行交互的模式,Java程序员就不需要了解每一个数据库的工作模式,只需要了解JDBC接口的使用方法就可以了。

早期没有JDBC的时候与各种数据库进行交互的模式
现在有了JDBC之后与各种数据库进行交互的新模式

JDBC入门

  • 搭建开发环境;
  • 编写程序,在程序中加载数据库驱动;
  • 建立连接;
  • 创建用于向数据库发送SQL的Statement对象;
  • 从代表结果集的ResultSet中取出数据;
  • 断开与数据库的连接,并释放相关资源。

JDBC的API详解

DriverManager

  • 驱动管理类;

  • 主要作用:

    1. 注册驱动

      DriverManager.registerDriver(new Driver()); //会导致驱动注册两次
      //在源代码中已经有静态代码块注册一次驱动了
      
      //实际注册驱动方式,这是实际开发使用的代码,
      //这个代码的意思是加载一个叫com.mysql.jdbc.Driver的类,
      //在加载类之后,静态代码块就被执行一次了
      Class.forName("com.mysql.jdbc.Driver");
      
    2. 获得连接

      Connection conn = DriverManager.getConnection(String url,String username,String password);
      //url写法: jdbc:mysql://localhost:3306/jdbc?serverTimezone=GMT
          //jdbc: 协议
          //mysql: jdbc的子协议
          //localhost: 主机名
          //3306: 端口号
          //jdbc: 数据库名
          //serverTimezone=GMT: 时区设置
      //如果连接的是本机的数据库,url简写: jdbc:mysql:///jdbc?serverTimezone=GMT
      

Connection

  • 连接对象;

  • 主要作用:

    1. 创建执行SQL语句的对象

      //下面三个方法产生的对象都可以用来执行SQL语句
      Statement createStatement()     //用来执行SQL语句,会有SQL注入的漏洞存在
      PreparedStatment prepareStatement(String sql)   //预编译SQL语句,解决了SQL注入的漏洞
      CallableStatement prepareCall(String sql)   //执行SQL中的存储过程
      
    2. 进行事物的管理

      setAutoCommit(boolean autoCommit)   //设置事务是否自动提交
      commit()                            //事务提交
      rollback()                          //事务回滚
      

Statement

  • 执行SQL;

  • 主要作用:

    1. 执行SQL语句

      boolean execute(String sql)         //执行SQL
      //执行SELECT语句返回true,否则返回false
      ResultSet executeQuery(String sql)  //只能执行SQL中的SELECT语句
      //返回查询的结果
      int executeUpate(String sql)        //执行SQL中的insert/update/delete语句
      //返回一个影响的行数
      
    2. 执行批处理操作

      addBatch(String sql)    //将操作添加到批处理中
      executeBatch()          //执行批处理
      clearBatch()            //清空批处理
      

ResultSet

  • 结果集:查询(SELECT)语句所查询到的结果的封装;

  • 主要作用:

    1. 结果集获取到查询的结果

      next()  //用来判断是否有下一行记录,如果有指向下一行
      //针对不同类型的数据可以使用getXXX()的方法来获取数据
      //通用获取数据的方法
      getObject(String columnLabel)   //参数是传入字符串类型值,表示字段名称
      

JDBC的资源释放

  • JDBC程序运行完之后,一定要记得释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, StatementConnection对象
  • Connection往往是有限的,如果没有释放掉Connection,那么被人有可能无法连接到数据库。Connection对象是非常稀有的资源,用完之后必须马上释放,如果Connection不能及时、正确地关闭,极易导致系统宕机。Connection使用的原则是尽量晚创建,尽量早释放。
//一种比较好的释放资源的办法,需要将这一段放在finally代码块中
if(stmt != null){
    try {
        stmt.close();
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }
    stmt = null;    //这么写可以让垃圾回收机制更早地回收con对象
}

JDBC的CRUD操作

向数据库中保存记录

Class.forName("com.mysql.cj.jdbc.Driver");  //注册驱动
conn = DriverManager.getConnection("jdbc:mysql:///jdbctest?serverTimezone=GMT","root","vbe200");    //创建连接
stmt = conn.createStatement();  //创建Statement
String sql = "insert into user values (null,'zhbw','123','张三')";  //保存记录
int i = stmt.executeUpdate(sql);    //执行保存操作
if(i>0){
    System.out.println("保存成功!");   //获取结果
}

修改数据库中的记录

Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql:///jdbctest?serverTimezone=GMT", "root", "vbe200");
stmt = conn.createStatement();
String sql = "update user set username = 'qqq',password='456',name='赵六' where uid=4";
int i = stmt.executeUpdate(sql);
if (i > 0) {
    System.out.println("修改成功!");
}

删除数据库中的记录

Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql:///jdbctest?serverTimezone=GMT", "root", "vbe200");
stmt = conn.createStatement();
String sql = "delete from user where uid=4";
int i = stmt.executeUpdate(sql);
if (i > 0) {
    System.out.println("删除成功!");
}

查询数据库中的记录

  1. 查询所有记录

    Class.forName("com.mysql.cj.jdbc.Driver");
    conn = DriverManager.getConnection("jdbc:mysql:///jdbctest?serverTimezone=GMT","root","vbe200");
    stmt = conn.createStatement();
    String sql = "select * from user";
    rs = stmt.executeQuery(sql);
    while(rs.next()){
        int uid = rs.getInt("uid");
        String username = rs.getString("username");
        String password = rs.getString("password");
        String name= rs.getString("name");
        System.out.println(uid+"  "+username+"  "+password+"  "+name);
    }
    
  2. 查询某一条特定的记录

    Class.forName("com.mysql.cj.jdbc.Driver");
    conn = DriverManager.getConnection("jdbc:mysql:///jdbctest?serverTimezone=GMT", "root", "vbe200");
    stmt = conn.createStatement();
    String sql = "select * from user where uid=2";
    rs = stmt.executeQuery(sql);
    if (rs.next()) {
        int uid = rs.getInt("uid");
        String username = rs.getString("username");
        String password = rs.getString("password");
        String name = rs.getString("name");
        System.out.println(uid + "  " + username + "  " + password + "  " + name);
    }
    

JDBC工具类的抽取

  • 由于很多代码都是重复的,所以为了简化JDBC开发,将一些重复的代码进行提取,创建一个工具类。
package io.github.zbw0520.jdbc.utils;

import java.sql.*;

/**
 * JDBC的工具类
 *
 * @author zhbw
 */
public class JDBCUtils {
    //静态常量
    private static final String driverClass;
    private static final String url;
    private static final String username;
    private static final String password;
    //静态代码块
    static {
        driverClass = "com.mysql.cj.jdbc.Driver";
        url = "jdbc:mysql:///jdbctest?serverTimezone=GMT";
        username = "root";
        password = "vbe200";
    }
    /**
     * 注册驱动的方法
     */
    public static void loadDriver() throws ClassNotFoundException {
        Class.forName(driverClass);
    }

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

    /**
     * 资源的释放
     * 做增删改的方法
     */
    public static void release(Statement stmt,Connection conn){
        if(stmt !=null){
            try {
                stmt.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            stmt = null;
        }
        if(conn !=null){
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            conn = null;
        }
    }

    /**
     * 资源的释放
     * 做查询操作的方法
     */
    public static void release(Statement stmt, Connection conn, ResultSet rs){
        if(stmt !=null){
            try {
                stmt.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            stmt = null;
        }
        if(conn !=null){
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            conn = null;
        }
        if(rs !=null){
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            rs = null;
        }
    }
}
  • 使用工具类的保存记录的实例代码
@Test
public void demo(){
    Statement stmt = null;
    Connection connection = null;
    try {
        connection = JDBCUtils.getConnection();
        stmt = connection.createStatement();
        String sql = "insert into user value(null,'qwq','123456','老王')";
        int i = stmt.executeUpdate(sql);
        if(i > 0){
            System.out.println("保存成功");
        }
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        JDBCUtils.release(stmt,connection);
    }
}
  • 注意,在之前的工具类文件中,我们将参数保存在源代码中,每一次需要修改参数的时候都需要修改源代码,这是不合理的,我们应该将参数放在源代码之外的一个文件中。我们选择将参数放在jdbc.properties这样一个属性文件中,文件中的键值对语法如下。
driverClass = com.mysql.cj.jdbc.Driver
url = jdbc:mysql:///jdbctest?serverTimezone=GMT
username = root
password = vbe200
  • 这时候需要修改工具类源代码来实现读取属性文件中的参数值,我们可以使用如下代码实现这一操作。
static {
    //加载属性文件并解析
    Properties prop = new Properties();
    //如何获得属性文件的输入流?
    //通常情况下采用类的加载器的方式进行获取:
    //通常不采用FileInputStream("src/jbdc.properties"),因为web应用没法适配
    InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
    try {
        prop.load(in);
    } catch (IOException e) {
        e.printStackTrace();
    }
    driverClass = prop.getProperty("driverClass");
    url = prop.getProperty("url");
    username = prop.getProperty("username");
    password = prop.getProperty("password");
}

JDBC的SQL注入漏洞

  • 只需要知道用户名,不需要知道密码就可以对数据库进行修改,这成为SQL注入漏洞。

  • 产生原因:在输入用户名的时候输入了SQL的关键字,使其不能正常解析用户名和密码。

  • 解决方案:不能使用Statement对象,必须要使用PreparedStatement类的对象,这种对象会把可以通过调用Connection.PreparedStatment(sql)的方法获得,相对于Statement对象而言:

    1. PreparedStatment可以避免SQL注入问题;

    2. Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。PreparedStatement可以对SQL进行预编译,从而提高数据库的执行效率;

    3. 并且PreparedStatement对于SQL中的参数,允许使用占位符(?)的形式进行替换,简化SQL语句的编写。如下代码是一个使用PreparedStatement类对象的示例代码,下面这个代码块就可以解决漏洞问题。

      String username = "zhbw";
      String password = "vbe200";
      //编写SQL:
      String sql = "select * from user where username = ? and password = ?";
      //预处理SQL:
      pstmt = conn.prepareStatement(sql);
      //设置参数:
      pstmt.setString(1,username);
      pstmt.setString(2.password);
      //执行SQL语句,因为已经进行预处理了,所以在这里就不需要传入语句参数了
      pstmt.executeQuery();
      

JDBC中的PreparedStatement

数据库连接池C3P0

  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库。C3P0是常用的开源连接池

手动设置连接池

//连接池需要引入两个jar包:
//c3p0-0.9.5.5.jar、mchange-commons-java-0.2.12.jar
//创建连接池:
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//设置连接池相关参数:
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql:///jdbctest?serverTimezone=GMT");
dataSource.setUser("root");
dataSource.setPassword("vbe200");
dataSource.setMaxPoolSize(20);
dataSource.setInitialPoolSize(3);
//获得连接:
conn = dataSource.getConnection();
//编写SQL:
String sql = "select * from user";
//预编译SQL
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();

使用文件设置连接池

  • 可以使用XML文件来设置连接池,进一步简化代码的复杂程度。

文章作者: 南航古惑仔
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 南航古惑仔 !
  目录