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. 即使参数固定也要用:养成良好的编码习惯
相关推荐
科技小花3 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸3 小时前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain3 小时前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希3 小时前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神3 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员3 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java4 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿4 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴4 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存
YOU OU4 小时前
三大范式和E-R图
数据库