JDBC工具类的三个版本

一、JDBC连接数据库的7个步骤

1、加载驱动

2、获取连接

3、编写sql

4、获取执行sql的stmt对象

有两种 stmt(存在sql注入问题 字符串拼接) pstmt(预编译可以防止sql注入)

5、执行sql 拿到结果集

6、遍历结果集

7、关闭资源(按倒序关闭,资源先开的后关,后开的先关)

主方法程序实例

java 复制代码
package com.qcby.dao;

import com.qcby.model.Account;
import com.qcby.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcSelect {
    public static void main(String[] args) {
        Connection connection =null;
        Statement statement=null;
        ResultSet resultSet=null;
        try {
            //建立连接
            connection= JdbcUtils.getConnection();
            //编写sql
            String sql="select * from account";
            //获取执行sql的statement对象
            statement=connection.createStatement();
            //执行sql拿到结果集
            resultSet=statement.executeQuery(sql);
            //遍历结果集
            while (resultSet.next()){
                Account account=new Account();
                account.setId(resultSet.getInt("id"));
                account.setName(resultSet.getString("name"));
                account.setMoney(resultSet.getDouble("money"));
                System.out.println(account);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //关闭资源
            JdbcUtils.close(connection,resultSet,statement);
        }
    }

    
}

二、JDBC工具类的三个版本

上述的7个步骤中可以将加载驱动、获取连接和关闭资源抽取出来从而简化代码。

1.0版本

  • 所有数据库连接信息直接写在代码里(地址、账号密码都固定)
  • 每次要连接数据库时都新建一个连接
  • 用完就直接扔掉,下次再用再新建
java 复制代码
package com.qcby.utils;

import org.gjt.mm.mysql.Driver;

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

//JDBC工具类,减少代码
public class JdbcUtils {

    //1、加载驱动
    public static void  createSDriver(){
        try {
            //2种 直接调用方法
          //  DriverManager.registerDriver(new Driver());
            DriverManager.registerDriver(new Driver());
            //反射加载
            //Class.forName(driverclass); // MySQL 8.0+驱动
        }catch (SQLException e){
            e.printStackTrace();
        }
    }
    //2、获取连接
    public  static Connection getConnection(){
        Connection connection=null;
        try {
            //1、加载驱动
            createSDriver();
            //2、获取连接
          connection= DriverManager.getConnection("jdbc:mysql:///spring_db","root","12345");
        }catch (SQLException e){
            e.printStackTrace();
        }
        return connection;
    }
    //3、关闭连接,方法重载
    public  static  void close(Connection connection, ResultSet resultSet,Statement statement){
        try {
            resultSet.close();
            statement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //3、关闭连接
    public  static  void close(Connection connection,Statement statement){
        try {
            statement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

2.0版本

  • 把数据库信息移到单独的配置文件里(db.properties)
  • 想改数据库地址或密码时,不用改代码,改配置文件就行
  • 但还是每次新建连接,用完就扔

编写properties属性文件,程序就可以读取属性文件

java 复制代码
driverclass=com.mysql.jdbc.Driver
url=jdbc:mysql:///spring_db
username=root
password=12345
java 复制代码
package com.qcby.utils;

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

public class JdbcUtil3 {

    private static final String driverclass;
    private static final String url;
    private static final String username;
    private static final String password;

    static{
        // 加载属性文件
        Properties pro = new Properties();
        InputStream inputStream = JdbcUtil3.class.getResourceAsStream("/db.properties");
        try {
            // 加载属性文件
            pro.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 给常量赋值
        driverclass = pro.getProperty("driverclass");
        url = pro.getProperty("url");
        username = pro.getProperty("username");
        password = pro.getProperty("password");
    }
    /**
     * 加载驱动
     */
    public static void loadDriver(){
        try {
            // 加载驱动类
            Class.forName(driverclass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    /**
     * 加载完驱动,获取到连接,返回连接对象
     * @return
     */
    public static Connection getConnection(){
        // 加载驱动
        loadDriver();
        // 获取到连接对象,返回
        Connection conn = null;
        try {
            // 获取到连接
            conn = DriverManager.getConnection(url,username,password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
    /**
     * 关闭资源
     * @param conn
     * @param stmt
     * @param rs
     */
    public static void close(Connection conn, Statement stmt, ResultSet rs){
        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();
            }
        }
    }
    /**
     * 关闭资源
     * @param conn
     * @param stmt
     */
    public static void close(Connection conn, Statement stmt){
        if(stmt != null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

3.0版本

什么是连接池

连接池技术通过复用数据库连接,显著提升了系统性能。以电商库存系统为例,当100个用户同时购买商品时:

传统方式需要100次连接创建和销毁,消耗大量资源。而使用连接池(如配置10个连接)后:

  1. 系统启动时预先建立10个连接

  2. 前10个请求直接获取现有连接

  3. 使用后连接归还而非销毁

  4. 后续请求复用这些连接

这就像物业管理:

  • 传统方式:每次有访客都现场配钥匙,离开就销毁

  • 连接池方式:预先准备10把钥匙,访客使用后归还

代码

使用druid.properties配置文件管理连接池参数

连接从连接池获取而非每次新建

  • 使用Druid连接池,预先创建一批连接放着
  • 需要用时直接从池子里拿,用完放回去
  • 可以控制最多准备多少连接、最少留多少
  • 优势:速度快(不用每次都新建)、省资源(连接可以重复用)、还能防漏水(连接泄漏检测)
java 复制代码
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///spring_db
username=root
password=12345
initialSize=5
maxActive=10
maxWait=3000
maxIdle=6
minIdle=3
java 复制代码
package com.qcby.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JdbcUtils2 {
    // 连接池对象
    private static DataSource DATA_SOURCE;

    static{
        // 加载属性文件
        Properties pro = new Properties();
        InputStream inputStream = JdbcUtils2.class.getResourceAsStream("/druid.properties");
        try {
            // 加载属性文件
            pro.load(inputStream);
            // 创建连接池对象
            DATA_SOURCE = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 从连接池中获取连接,返回。
     * @return
     */
    public static Connection getConnection(){
        Connection conn = null;
        try {
            conn = DATA_SOURCE.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

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

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

测试

java 复制代码
package com.qcby.dao;

import com.qcby.model.Account;
import com.qcby.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcSelect {
    public static void main(String[] args) {
        Connection connection =null;
        Statement statement=null;
        ResultSet resultSet=null;
        try {
            //建立连接
            connection= JdbcUtils.getConnection();
            //编写sql
            String sql="select * from account";
            //获取执行sql的statement对象
            statement=connection.createStatement();
            //执行sql拿到结果集
            resultSet=statement.executeQuery(sql);
            //遍历结果集
            while (resultSet.next()){
                Account account=new Account();
                account.setId(resultSet.getInt("id"));
                account.setName(resultSet.getString("name"));
                account.setMoney(resultSet.getDouble("money"));
                System.out.println(account);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.close(connection,resultSet,statement);
        }
    }


}

补充:Sql注入问题

想象你开了一家会员制餐厅:

  • 正常流程:客人报会员名"张三",你查名单确认后放行
  • 注入攻击:客人报会员名"张三' OR '1'='1",你的名单系统会把所有人都放进来!

漏洞是如何产生的 (字符串拼接问题)

java 复制代码
String username = "aaa'or'1=1"; // 用户输入
String password = "随便输";
String sql = "SELECT * FROM users WHERE username='"+username+"' AND password='"+password+"'";

实际执行的SQL变成:

SELECT * FROM users WHERE username='aaa'or'1=1' AND password='随便输'

'1'='1'永远成立,相当于不需要密码

攻击者常用的花招

1、万能密码

用户名:admin' --

密码:随便

相当于SQL:SELECT * FROM users WHERE username='admin' --' AND password='随便'

--是SQL注释符,后面条件被忽略

2、永远为真:

用户名:aaa'or'1=1

密码:随便

相当于:SELECT * FROM users WHERE username='aaa'or'1=1' AND password='随便'

如何解决sql注入问题

使用PreparedStatement

就像改进后的餐厅检查流程:

  1. 提前预定格式:会员名必须是一个完整字符串(预编译)
  2. 严格检查输入:即使用户输入特殊符号,也只当作普通字符

基本格式:

java 复制代码
            // 获取到连接对象
            conn = dataSource.getConnection();
            // 编写SQL语句
            String sql = "insert into account values (null,?,?)";
            // 预编译SQL语句
            stmt = conn.prepareStatement(sql);
            // 设置值
            stmt.setString(1,"eee");
            stmt.setString(2,"1233");
            // 执行sql
            stmt.executeUpdate();

为什么使用PreparedStatement安全?

注意:

  1. 永远不要拼接SQL:就像不要直接吃陌生人给的糖果
  2. 始终使用PreparedStatement:这是防止SQL注入的最简单有效方法
  3. 即使参数固定也要用:养成良好的编码习惯
相关推荐
敲上瘾39 分钟前
MySQL数据类型
数据库·c++·mysql·数据库开发·数据库架构
想躺平的咸鱼干40 分钟前
SQL语句的优化
数据库·sql
小陶来咯2 小时前
【高级IO】多路转接之单线程Reactor
服务器·网络·数据库·c++
wei_shuo2 小时前
OB Cloud 云数据库V4.3:SQL +AI全新体验
数据库·人工智能·sql
潇湘秦3 小时前
Oracle非归档模式遇到文件损坏怎么办?
数据库·oracle
极小狐4 小时前
如何使用极狐GitLab 软件包仓库功能托管 maven?
java·运维·数据库·安全·c#·gitlab·maven
野犬寒鸦5 小时前
MySQL索引使用规则详解:从设计到优化的完整指南
java·数据库·后端·sql·mysql
时序数据说6 小时前
IoTDB磁盘I/O性能监控与优化指南
大数据·网络·数据库·时序数据库·iotdb
火云牌神6 小时前
在windows系统中安装图数据库NEO4J
数据库·windows·neo4j
吻等离子8 小时前
解决 MySQL 数据库无法远程连接的问题
数据库·mysql·adb