JDBC的详细使用

news2025/4/3 0:59:55

1. JDBC概述

JDBC[Java Database Connectivity]是 Java 语言中用于连接和操作数据库的一套标准 API。它允许 Java 程序通过统一的方式与各种关系型数据库,如 MySQL、Oracle、SQL Server 等交互,执行 SQL 语句并处理结果。

1.1 JDBC原理

JDBC的核心原理是【通过统一的接口层屏蔽不同数据库的差异】,让Java程序能以标准化的方式与各种数据库交互。其核心设计基于分层架构实现,如下:

  • 接口层【JDBC API】:Java定义了一套标准接口如 ConnectionStatementResultSet,开发者只需调用这些接口的方法。这一套统一规范由SUN公司提供,命名为JDBC
  • 实现层【JDBC Driver】:JDBC只是一堆接口,而JDBC才是接口的实现,数据库驱动才能让应用程序连接和操作特定数据库,其原理就是将JDBC API的调用转换为数据库能理解的指令。数据库驱动就是各个数据库厂商提供的JDBC实现类而已

1.2 JDBC的作用

JDBC接口是 Java 语言中用于连接和操作数据库的核心 API,它通过标准化接口与实现分离的设计,在开发中起到【统一数据库操作、屏蔽底层差异】的关键作用。

  • 提供统一的数据库操作APIJDBC 定义了一套标准接口,开发者只需学习这一套 API,即可操作任意支持JDBC 的数据库。
  • 解耦业务逻辑与数据库实现
    • 开发者面向接口编程:业务代码依赖 JDBC 接口,而非具体数据库的实现。
    • 数据库切换透明化:更换数据库时,只需修改驱动和连接 URL,无需重写业务逻辑。
  • 管理数据库连接与资源:JDBC 接口规范了资源的生命周期管理如连接的建立、关闭,并通过 AutoCloseable 支持自动释放资源,避免内存泄漏。
  • 支持安全、高效的数据库操作
    • SQL注入:通过 PreparedStatement 接口实现参数化查询。
    • 批量处理优化:通过 addBatch()executeBatch() 提升批量操作性能。
  • 事务管理:通过 Connection 接口管理事务,确保数据一致性。

1.3 模拟JDBC接口

首先需要制定接口,模拟SUN公司,如下:

package com.person;

interface JDBC {
    // 负责连接数据库的方法
    void getConnection();
}

其次就是接口的实现,即各个驱动,如下:

// Mysql驱动
package com.person;

public class MySQLDriver implements JDBC{
    @Override
    public void getConnection() {
        System.out.println("你已连接MySQL数据库");
    }
}
// Oracle驱动
package com.person;

public class OracleDriver implements JDBC{
    @Override
    public void getConnection() {
        System.out.println("你已连接MySQL数据库");
    }
}

在完成上述操作后就可以操作数据库了,如下:

package com.person;

public class Client {
    public static void main(String[] args) {
        // 操作MySQL数据库
        JDBC jdbc = new MySQLDriver();
        // 操作Oracle数据库
        // JDBC jdbc = new OracleDriver();
        jdbc.getConnection();
    }
}

很明显上述代码违背开发原则中的OCP原则,如果想要实现OCP原则,可以将创建对象的任务交给反射机制,将类名配置到配置文件中即可,如下:

// jdbc.properties

jdbc.driver=MySQLDriver
import java.util.ResourceBundle;

public class Client{
    public static void main(String[] args) throws Exception{
        
        String driverClassName = ResourceBundle.getBundle("jdbc").getString("jdbc.driver");
        Class c = Class.forName(driverClassName);
        JDBC jdbc = (JDBC)c.newInstance();
        
        jdbc.getConnection();
    }
}

最终通过修改jdbc.properties文件即可做到数据库的切换,这样就做到了驱动和实现的耦合。

1.4 MySQL驱动的下载

驱动是各个厂商自己编写的JDBC实现类,因此需要前往官网下载,以MySQL为例,如下:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

2. JDBC编程的基本实现

2.1 实现步骤

JDBC编程的步骤是很固定的,通常包含以下六步:

  • 注册驱动

    • JDBC 驱动程序从硬盘上的文件系统中加载到内存中。
    • 使得 DriverManager 可以通过一个统一的接口来管理该驱动程序的所有连接操作。
  • 获取数据库连接:获取java.sql.Connection对象,该对象的创建标志着mysql进程和jvm进程之间的通道打开。

  • 获取数据库操作对象:获取java.sql.Statement对象,该对象负责将SQL语句发送给数据库,数据库负责执行该SQL语句。

  • 执行SQL语句:执行具体的SQL语句,例如insert delete update select等。

  • 处理查询结果集

    • 如果之前的操作是SQL查询语句,才会有处理查询结果集这一步。
    • 执行SQL语句通常会返回查询结果集对象java.sql.ResultSet
    • 对于ResultSet查询结果集来说,通常的操作是针对查询结果集进行结果集的遍历。
  • 释放资源

    • 释放资源可以避免资源的浪费。在 JDBC 编程中,每次使用完 ConnectionStatementResultSet 等资源后,都需要显式地调用对应的 close() 方法来释放资源,避免资源的浪费。
    • 释放资源可以避免出现内存泄露问题。在Java中,当一个对象不再被引用时,会被 JVM 的垃圾回收机制进行回收。但是在 JDBC 编程中,如果不显式地释放资源,那么这些资源就不会被 JVM 的垃圾回收机制自动回收,从而导致内存泄露问题。

2.2 代码实现

package com.person;
import java.sql.*;

public class Client {
    public static void main(String[] args){
        Statement stmt = null;
        Connection conn = null;

        try {
            // 注册驱动
            Driver driver = new com.mysql.cj.jdbc.Driver();
            DriverManager.registerDriver(driver);

            // 获取连接
            String url = "jdbc:mysql://localhost:3306/jdbc";
            String user = "root";
            String password = "20041104HT";
            conn = DriverManager.getConnection(url, user, password); // com.mysql.cj.jdbc.ConnectionImpl@f79e

            // 获取数据库操作对象 -- 可以创建多个
            stmt = conn.createStatement(); // com.mysql.cj.jdbc.StatementImpl@4c309d4d

            // 执行SQL语句
            String sql = "INSERT INTO user(name, password, relname, gender, tel) VALUES('王五', '123654', '龙或', '男', '130')";
//        boolean isSuccess = stmt.execute(sql); // 很少用
//        System.out.println(isSuccess ? "插入数据成功" : "插入数据失败");
            int count = stmt.executeUpdate(sql);    // 凡是涉及 增、删、改的操作使用该方法,返回值为数据发生变化的条目
            System.out.println(count == 1 ? "插入数据成功" : "插入数据失败");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 释放资源 从小到大进行释放
            // 每一个都应该try-catch, 否则会导致后面的代码不执行
            if (stmt != null) {
                try{
                    stmt.close();
                }catch (SQLException e){
                    e.printStackTrace();
                }

            }

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

    }
}

2.3 URL格式

JDBC URL 是在使用 JDBC 连接数据库时的一个 URL 字符串,它用来标识要连接的数据库的位置、认证信息和其他配置参数等。JDBC URL 的格式因数据库类型而异,完整格式如jdbc:mysql://<host>:<port>/<database_name>?<connection_parameters>

  • 协议:表示要使用的数据库管理系统DBMS的类型,如 jdbc:mysql 表示要使用 MySQL 数据库,jdbc:postgresql 表示要使用 PostgreSQL 数据库。

  • 主机地址和端口号:表示要连接的数据库所在的服务器的 IP 地址或域名,以及数据库所在服务器监听的端口号。

  • 数据库名称:表示要连接的数据库的名称。

  • 其他可选参数:这些参数包括连接的超时时间、使用的字符集、连接池相关配置等。

    • serverTimezone:设置服务器的时区,默认为 UTC,可以通过该参数来指定客户端和服务器的时区。

      jdbc:mysql://localhost:3306/jdbc?user=root&password=666&serverTimezone=America/Los_Angeles
      
    • useSSL:是否使用 SSL 进行连接,默认为 true

    • useUnicode:是否使用Unicode编码进行数据传输,默认是trueuseUnicode设置的是数据在传输过程中是否使用Unicode编码方式。

    • characterEncoding:连接使用的字符编码,默认为 UTF-8characterEncoding设置的是数据被传输到服务器之后,服务器采用哪一种字符集进行编码。

      jdbc:mysql://localhost:3306/jdbc?useUnicode=true&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=utf-8
      

JDBC URL 是连接数据库的关键,通过 JDBC URL,应用程序可以通过特定的JDBC驱动程序与数据库服务器进行通信,从而实现与数据库的交互。在开发 Web 应用和桌面应用时,使用 JDBC URL 可以轻松地连接和操作各种类型的数据库,例如 MySQL、PostgreSQL、Oracle 等。

2.4 注册驱动的两种方式

在上述代码中,注册驱动的方式如下:

Driver driver = new com.mysql.cj.jdbc.Driver();
DriverManager.registerDriver(driver);

可以看一下Driver类的源码,如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

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!");
        }
    }
}

从源码中不难发现,Driver类的静态代码块已经实现了注册驱动,也就是说,我们只需要让该代码块执行即可实现注册驱动,可以使用forName实现,如下:

Class.forName("com.mysql.cj.jdbc.Driver");

【注意】从JDBC 4.0java 6开始,驱动注册就不再需要手动完成了,由系统自动完成。但是推荐手动注册,因为有一些数据库驱动程序不支持自动注册。

2.5 动态配置连接数据库的信息

为了避免在程序中出现硬编码,符合OCP原则,建议将数据库的配置信息配置到属性文件中,如下:

// jdbc.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc?
useUnicode=true&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=utf-8
user=root
password=666
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;
import java.util.ResourceBundle;

public class JDBCTest04 {
    public static void main(String[] args){
        
    	// 通过以下代码获取属性文件中的配置信息
		ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
		String driver = bundle.getString("driver");
		String url = bundle.getString("url");
		String user = bundle.getString("user");
		String password = bundle.getString("password");

        Connection conn = null;
        Statement stmt = null;
        try {
            // 1. 注册驱动
            Class.forName(driver);

            // 2. 获取连接
            conn = DriverManager.getConnection(url, user, password);

            // 3. 获取数据库操作对象
            stmt = conn.createStatement();

            // 4. 执行SQL语句
            String sql = "insert into user(name,password,relname,gender,tel) values('bruce','123','布鲁斯','男','12566568956')"; 
            int count = stmt.executeUpdate(sql);
            System.out.println("添加数据成功");
            
        } catch(SQLException | ClassNotFoundException e){
            e.printStackTrace();
        } finally {
            // 6. 释放资源
            if(stmt != null){
                try{
                    stmt.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
            if(conn != null){
                try{
                    conn.close();
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

2.6 获取连接的其它方式

在前面的案例中,是使用的如下方法连接的数据库:

Connection conn = DriverManager.getConnection(url, user, password);

当然还有其它方式获取数据库连接,如下:

  • getConnection(String url):参数仅需传递一个url,用户名、密码等配置参数都需要配置在url中。

    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Connection;
    
    public class JDBCTest05 {
        public static void main(String[] args){
            try {
                // 1. 注册驱动
                Class.forName("com.mysql.cj.jdbc.Driver");
    
                // 2. 获取连接
                String url = "jdbc:mysql://localhost:3306/jdbc?user=root&password=666";
                Connection conn = DriverManager.getConnection(url);
    
                System.out.println(conn);
            } catch(SQLException|ClassNotFoundException e){
                e.printStackTrace();
            }
        }
    }
    
  • getConnetion(String url, Properties info):需传递两个参数,一个是url,一个是Properties对象。

    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Connection;
    import java.util.Properties;
    
    public class JDBCTest06 {
        public static void main(String[] args){
            try {
                // 1. 注册驱动
                Class.forName("com.mysql.cj.jdbc.Driver");
    
                // 2. 获取连接
                String url = "jdbc:mysql://localhost:3306/jdbc";
                
                Properties info = new Properties();
                info.setProperty("user", "root");
                info.setProperty("password", "666");
                info.setProperty("useUnicode", "true");
                info.setProperty("serverTimezone", "Asia/Shanghai");
                info.setProperty("useSSL", "true");
                info.setProperty("characterEncoding", "utf-8");
                
                Connection conn = DriverManager.getConnection(url, info);
    
                System.out.println(conn);
            } catch(SQLException|ClassNotFoundException e){
                e.printStackTrace();
            }
        }
    }
    

3. JDBC常用操作

3.1 查询操作

查询数据信息可以通过列索引和列名两种方式,一般使用后者,通过列索引的方式代码可读性太差。在JDBC中,索引都是从1开始的。

package com.person;
import java.sql.*;

public class Client {
    public static void main(String[] args){
        Statement stmt = null;
        Connection conn = null;
        // 查询结果集
        ResultSet result = null;

        try {
            // 注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 获取连接
            String url = "jdbc:mysql://localhost:3306/jdbc";
            String user = "root";
            String password = "20041104HT";
            conn = DriverManager.getConnection(url, user, password);

            stmt = conn.createStatement();

            // 查询操作
            String sql = "SELECT id,name,password FROM user";
            result = stmt.executeQuery(sql); // com.mysql.cj.jdbc.result.ResultSetImpl@3cc1435c
            while(result.next()) {
                // 以指定类型获取数据
                // 如果以Date类型获取数据,返回的是java.sql.Date
                int id = result.getInt("id");
                String name = result.getString("name");
                String pwd = result.getString("password");

                System.out.println(id + name + pwd);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            if (result != null) {
                try{
                    result.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();
                }
            }
        }

    }
}

3.2 查询元数据信息

ResultSetMetaData 是一个接口,用于描述 ResultSet 中的元数据信息,即查询结果集的结构信息,例如查询结果集中包含了哪些列,每个列的数据类型、长度、标识符等。ResultSetMetaData 可以通过 ResultSet 接口的 getMetaData() 方法获取,一般在对 ResultSet 进行元数据信息处理时使用。

import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;
import java.util.ResourceBundle;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

public class JDBCTest12 {
    public static void main(String[] args){
        
    	// 通过以下代码获取属性文件中的配置信息
		ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
		String driver = bundle.getString("driver");
		String url = bundle.getString("url");
		String user = bundle.getString("user");
		String password = bundle.getString("password");

        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            Class.forName(driver);
            conn = DriverManager.getConnection(url, user, password);
            stmt = conn.createStatement();
            String sql = "select id,name,price,create_time as createTime from t_product";
            rs = stmt.executeQuery(sql);
            // 获取元数据信息
            ResultSetMetaData rsmd = rs.getMetaData();
            // 获取元数据的个数
            int columnCount = rsmd.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                System.out.println(rsmd.getColumnName(i)); // 列名
                System.out.println(rsmd.getColumnTypeName(i)); // 列的数据类型
                System.out.println(rsmd.getColumnDisplaySize(i)); // 列的显示长度
            }
            
        } catch(SQLException | ClassNotFoundException e){
            e.printStackTrace();
        } finally {
            // 6. 释放资源
            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();
                }
            }
        }
    }
}

3.3 获取新增行的主键值

大多数表的主键字段值都是自增的,在某些特殊的业务环境下,当我们插入了新数据后,希望能够获取到这条新数据的主键值,应该如何获取呢?在 JDBC 中,如果要获取插入数据后的主键值,可以使用 Statement 接口的 executeUpdate() 方法的重载版本,该方法接受一个额外的参数,用于指定是否需要获取自动生成的主键值。然后,通过以下两个步骤获取插入数据后的主键值:

  • 在执行 executeUpdate() 方法时指定一个标志位,表示需要返回插入的主键值。

  • 调用 Statement 对象的 getGeneratedKeys() 方法,返回一个包含插入的主键值的 ResultSet 对象。

    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Connection;
    import java.sql.Statement;
    import java.util.ResourceBundle;
    import java.sql.ResultSet;
    
    public class JDBCTest13 {
        public static void main(String[] args){
            
        	// 通过以下代码获取属性文件中的配置信息
    		ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
    		String driver = bundle.getString("driver");
    		String url = bundle.getString("url");
    		String user = bundle.getString("user");
    		String password = bundle.getString("password");
    
            Connection conn = null;
            Statement stmt = null;
            ResultSet rs = null;
            try {
                // 1. 注册驱动
                Class.forName(driver);
    
                // 2. 获取连接
                conn = DriverManager.getConnection(url, user, password);
    
                // 3. 获取数据库操作对象
                stmt = conn.createStatement();
    
                // 4. 执行SQL语句
                String sql = "insert into user(name,password,realname,gender,tel) values('zhangsan','111','张三','男','19856525352')";
                // 第一步
                int count = stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
                // 第二步
                rs = stmt.getGeneratedKeys();
                if(rs.next()){
                    int id = rs.getInt(1);
                    System.out.println("新增数据行的主键值: " + id);
                }
                
            } catch(SQLException | ClassNotFoundException e){
                e.printStackTrace();
            } finally {
                // 6. 释放资源
                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();
                    }
                }
            }
        }
    }
    

3.4 封装DbUtils

JDBC 封装为类似 DbUtils 的工具类可以大幅简化数据库操作代码,减少重复性工作,如资源释放、异常处理等。

package com.powernode.jdbc;

import java.sql.*;
import java.util.ResourceBundle;


public class DbUtils {
    private static String url;
    private static String user;
    private static String password;

    static {
        // 读取属性资源文件
        ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
        String driver = bundle.getString("driver");
        url = bundle.getString("url");
        user = bundle.getString("user");
        password = bundle.getString("password");
        // 注册驱动
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取数据库连接
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        Connection conn = DriverManager.getConnection(url, user, password);
        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) {
                throw new RuntimeException(e);
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

4. SQL注入

4.1 原理

SQL 注入【SQL Injection】 是一种针对数据库的安全漏洞攻击技术。攻击者通过向应用程序的输入字段如表单、URL参数等注入恶意构造的 SQL 代码,从而操纵后端数据库执行非预期的 SQL 命令。这种攻击可能导致数据泄露、数据篡改、账户越权,甚至数据库完全被控制。其原理如下:

  • 输入拼接漏洞:如果应用程序直接将用户输入拼接到 SQL 语句中,未做任何过滤或转义,攻击者可通过输入特殊字符如 '";--改变 SQL 语句的逻辑。
  • 恶意代码执行:攻击者注入的 SQL 代码会被数据库解析执行,例如绕过登录验证、删除或修改数据库表、窃听敏感数据。

如下代码用来模拟SQL注入:

package com.person;

import java.sql.*;
import java.util.ResourceBundle;
import java.util.Scanner;

/**
 * 用户登录案例演示SQL注入问题
 */
public class Client {
    public static void main(String[] args) {
        // 输出欢迎页面
        System.out.println("欢迎使用用户管理系统, 请登录!");
        // 接收用户名和密码
        Scanner scanner = new Scanner(System.in);
        System.out.print("用户名:");
        String loginName = scanner.nextLine();
        System.out.print("密码:");
        String loginPwd = scanner.nextLine();
        // 读取属性配置文件,获取连接数据库的信息。
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        String driver = bundle.getString("jdbc.driver");
        String url = bundle.getString("url");
        String user = bundle.getString("user");
        String password = bundle.getString("password");
        // JDBC程序验证用户名和密码是否正确
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            // 1.注册驱动
            Class.forName(driver);
            // 2.获取连接
            conn = DriverManager.getConnection(url, user, password);
            // 3.获取数据库操作对象
            stmt = conn.createStatement();
            // 4.执行SQL语句
            String sql = "select relname from user where name = '"+loginName+"' and password = '"+loginPwd+"'";
            rs = stmt.executeQuery(sql);
            // 5.处理查询结果集
            if (rs.next()) {
                String relname = rs.getString("relname");
                System.out.println("登录成功, 欢迎您, " + relname);
            } else {
                System.out.println("登录失败, 用户名不存在或者密码错误");
            }
        } catch (ClassNotFoundException | SQLException e) {
            throw new RuntimeException(e);
        } finally {
            // 6.释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

当我输入如下指令时,发现也能成功登录,即使数据库中并不存在该用户信息。

欢迎使用用户管理系统, 请登录!
用户名:王五
密码:aaa' or '1==1 --
登录成功, 欢迎您, 吉吉

4.2 解决SQL注入

从上述代码中不难发现,导致SQL注入的根本原因就是Statement造成的,Statement先进行字符串的拼接,然后将拼接好的字符串发送给SQL服务器编译执行。

因此JDBC为了解决这个问题,引入了PreparedStatement接口,即预编译的数据库操作对象。PreparedStatement先对SQL语句进行预编译,确定其语法结构,然后再向SQL语句的指定位置传值,这样就算用户传递的信息中包括SQL关键字,也只会被当作一个值传递给SQL语句,而不会再参与SQL语句的编译。

package com.person;

import java.sql.*;
import java.util.ResourceBundle;
import java.util.Scanner;

/**
 * 用户登录案例演示SQL注入问题
 */
public class Client {
    public static void main(String[] args) {
        System.out.println("欢迎使用用户管理系统, 请登录!");
        Scanner scanner = new Scanner(System.in);
        System.out.print("用户名:");
        String loginName = scanner.nextLine();
        System.out.print("密码:");
        String loginPwd = scanner.nextLine();


        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        String driver = bundle.getString("jdbc.driver");
        String url = bundle.getString("url");
        String user = bundle.getString("user");
        String password = bundle.getString("password");
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            Class.forName(driver);
            conn = DriverManager.getConnection(url, user, password);
            String sql = "SELECT * FROM user WHERE name=? AND password=?";
            pstmt = conn.prepareStatement(sql);
            // setXXX限制传值的类型
            pstmt.setString(1, loginName);
            pstmt.setString(2, loginPwd);

            rs = pstmt.executeQuery();
            if (rs.next()) {
                String relname = rs.getString("relname");
                System.out.println("登录成功, 欢迎您, " + relname);
            } else {
                System.out.println("登录失败, 用户名不存在或者密码错误");
            }
        } catch (ClassNotFoundException | SQLException e) {
            throw new RuntimeException(e);
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (pstmt != null) {
                try {
                    pstmt.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

PreparedStatementStatement都是用来执行SQL语句的接口,但是PreparedStatement预编译SQL语句,执行速度也更快,可以避免SQL注入。

4.3 PreparedStatement的使用

PreparedStatementStatement的子类,自然也可以实现增删改查的操作,如下:

  • 新增操作

    Class.forName(driver);
    conn = DriverManager.getConnection(url, user, password);
    String sql = "INSERT INTO user(name, password, relname, gender, tel) VALUES(?, ?, ?, ?, ?)";
    pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, "迪迦");
    pstmt.setString(2, "111");
    pstmt.setString(3, "李天");
    pstmt.setString(4, "女");
    pstmt.setString(5, "666");
    int count = pstmt.executeUpdate();
    if (count == 1) {
        System.out.println("插入数据成功");
    } else {
        System.out.println("插入数据失败");
    }
    
  • 修改操作

    Class.forName(driver);
    conn = DriverManager.getConnection(url, user, password);
    String sql = "UPDATE user SET name=?, tel=? WHERE name=?";
    pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, "哈哈");
    pstmt.setString(2, "111sssssss");
    pstmt.setString(3, "迪迦");
    int count = pstmt.executeUpdate();
    if (count == 1) {
        System.out.println("修改数据成功");
    } else {
        System.out.println("修改数据失败");
    }
    
  • 删除操作

    Class.forName(driver);
    conn = DriverManager.getConnection(url, user, password);
    String sql = "DELETE FROM user WHERE name=?";
    pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, "王五");
    int count = pstmt.executeUpdate();
    if (count != 0) {
        System.out.println("删除数据成功");
    } else {
        System.out.println("删除数据失败");
    }
    
  • 查询操作

    Class.forName(driver);
    conn = DriverManager.getConnection(url, user, password);
    String sql = "SELECT * FROM user WHERE name like ?";
    pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, "_四");
    rs = pstmt.executeQuery();
    while (rs.next()) {
        String name = rs.getString("name");
        System.out.println(name);
    }
    

4.4 JDBC批处理

JDBC 中,批处理是一种用于高效执行多个 SQL 操作的机制,特别适合需要批量插入、更新或删除数据的场景。以下是批处理的完整使用方法和注意事项:首先是不使用批处理,所用时间为36931ms,如下:

// 记录时间
long begin = System.currentTimeMillis();
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
String sql = "INSERT INTO user(name, password, relname, gender, tel) VALUES(?, ?, ?, ?, ?)";
for (int i = 0; i <= 10000; i++) {
    pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, "迪迦");
    pstmt.setString(2, "111");
    pstmt.setString(3, "李天");
    pstmt.setString(4, "女");
    pstmt.setString(5, "666");
    // 逐条提交
    pstmt.executeUpdate();
}
System.out.println("不使用批处理插入10000条记录成功");
long end = System.currentTimeMillis();
System.out.println("使用时间" + (end - begin) + "ms");  // 使用时间36931ms

再使用批处理,使用时间为2235ms如下:

// 记录时间
long begin = System.currentTimeMillis();
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
// 关闭自动提交,开启事务
conn.setAutoCommit(false);
String sql = "INSERT INTO user(name, password, relname, gender, tel) VALUES(?, ?, ?, ?, ?)";
for (int i = 0; i <= 10000; i++) {
    pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, "迪迦");
    pstmt.setString(2, "111");
    pstmt.setString(3, "李天");
    pstmt.setString(4, "女");
    pstmt.setString(5, "666");
    // 将参数添加到批量处理的队列
    pstmt.addBatch();
    if (i % 1000 == 0) {
        // 执行批量处理队列,返回值为int[],表示每个SQL操作影响的行数
        pstmt.executeBatch();
        // 清空批处理队列,防止内存溢出
        pstmt.clearBatch();
    }
}
// 执行剩余批处理
pstmt.executeBatch();
// 手动提交事务
conn.commit();
System.out.println("使用批处理插入10000条记录成功");
long end = System.currentTimeMillis();
System.out.println("使用时间" + (end - begin) + "ms");  // 使用时间717ms

5. 事务 & 存储过程

事务简单来说就是一个完整的业务,在这个业务中需要多条DML语句共同联合才能完成。事务可以保证多条DML语句同时成功和同时失败,从而保证数据的安全。DML语句即修改数据的语句,如INSERTDELETEUPDATE。下面模拟一个简单的业务场景:

package com.person;

import java.sql.*;
import java.util.ResourceBundle;
import java.util.Scanner;

/**
 * 用户登录案例演示SQL注入问题
 */
public class Client {
    public static void main(String[] args) {
        // 转账金额
        double money = 10000.0;

        Connection conn = null;
        PreparedStatement ps1 = null;
        PreparedStatement ps2 = null;
        try {
            conn = DbUtils.getConnection();

            // 更新 001 账户
            String sql1 = "update act set balance = balance - ? where actno = ?";
            ps1 = conn.prepareStatement(sql1);
            ps1.setDouble(1, money);
            ps1.setString(2, "001");
            int count1 = ps1.executeUpdate();
            
            // 模拟异常
            String str = null;
            str.toString();

            // 更新 002账户
            String sql2 = "update act set balance = balance + ? where actno = ?";
            ps2 = conn.prepareStatement(sql2);
            ps2.setDouble(1, money);
            ps2.setString(2, "002");
            int count2 = ps2.executeUpdate();

            System.out.println("转账成功");

        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.close(null, ps1, null);
            DbUtils.close(conn, ps1, null);
        }

    }
}

5.1 事务

上述代码发生异常时,001账户的钱会减少,但是002账户并不会增加,这在实际开发中是很危险的。我们想要的情况时发生异常时能够回滚到发生异常前的状态,这时就需要用到回滚,步骤如下:

  • 关闭自动提交,即开启事务。

  • 当整个业务结束后,手动提交业务。

  • 如果处理业务过程中发生异常,手动回滚事务。

    package com.person;
    
    import java.sql.*;
    import java.util.ResourceBundle;
    import java.util.Scanner;
    
    /**
     * 用户登录案例演示SQL注入问题
     */
    public class Client {
        public static void main(String[] args) {
            // 转账金额
            double money = 10000.0;
    
            Connection conn = null;
            PreparedStatement ps1 = null;
            PreparedStatement ps2 = null;
            try {
                conn = DbUtils.getConnection();
                // 开启事务
                conn.setAutoCommit(false);
    
                // 更新 001 账户
                String sql1 = "update act set balance = balance - ? where actno = ?";
                ps1 = conn.prepareStatement(sql1);
                ps1.setDouble(1, money);
                ps1.setString(2, "001");
                int count1 = ps1.executeUpdate();
    
                // 模拟异常
                String str = null;
                str.toString();
    
                // 更新 002账户
                String sql2 = "update act set balance = balance + ? where actno = ?";
                ps2 = conn.prepareStatement(sql2);
                ps2.setDouble(1, money);
                ps2.setString(2, "002");
                int count2 = ps2.executeUpdate();
    
                System.out.println("转账成功");
                // 手动提交
                conn.commit();
    
            } catch (SQLException e) {
                // 回滚事务
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    throw new RuntimeException(ex);
                }
            } finally {
                DbUtils.close(null, ps1, null);
                DbUtils.close(conn, ps1, null);
            }
    
        }
    }
    
    // 设置事务的隔离级别
    conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
    

5.2 存储过程

首先在MySQL中创建存储过程,如下:

create procedure mypro(in n int, out sum int)
begin 
	set sum := 0;
	repeat 
		if n % 2 = 0 then 
		  set sum := sum + n;
		end if;
		set n := n - 1;
		until n <= 0
	end repeat;
end;

然后就可以使用JDBC去调用存储过程,如下:

package com.powernode.jdbc;

import com.powernode.jdbc.utils.DbUtils;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Types;


public class Client {
    public static void main(String[] args) {
        Connection conn = null;
        CallableStatement cs = null;
        try {
            conn = DbUtils.getConnection();
            String sql = "{call mypro(?, ?)}";
            // 预编译SQL语句
            cs = conn.prepareCall(sql);
			
            // 传递参数
            cs.setInt(1, 100);
            // 注册出参
            cs.registerOutParameter(2, Types.INTEGER);
            // 执行存储过程
            cs.execute();
            // 通过出参获取结果
            int result = cs.getInt(2);
            System.out.println("计算结果:" + result);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.close(conn, cs, null);
        }
    }
}

系统的大致功能如下:

public class Client {
    static Connection conn = null;
    static PreparedStatement pstm = null;
    static ResultSet res = null;
    static Scanner sc = new Scanner(System.in);

    static {
        // 数据库连接
        try {
            ResourceBundle resourceBundle = ResourceBundle.getBundle("jdbc");
            String driver = resourceBundle.getString("jdbc.driver");
            String user = resourceBundle.getString("user");
            String pwd = resourceBundle.getString("password");
            String url = resourceBundle.getString("url");
            // 注册驱动
            Class.forName(driver);
            // 获取连接
            conn = DriverManager.getConnection(url, user, pwd);
            System.out.println(conn);

        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws SQLException {
        System.out.println("欢迎来到员工信息管理系统, 该系统功能如下: ");
        System.out.println("[1]查看员工列表");
        System.out.println("[2]查看某员工详细信息");
        System.out.println("[3]新增员工");
        System.out.println("[4]修改员工信息");
        System.out.println("[5]删除员工");
        System.out.println("[0]退出系统");

        while (true) {
            System.out.println("请输入您的选择: ");
            int select = sc.nextInt();

            switch (select) {
                case 1:
                    // 查看员工列表
                    doList();
                    break;
                case 2:
                    // 查看某员工详细信息
                    System.out.println("请输入员工姓名: ");
                    String name = sc.next();
                    doDetail(name);
                    break;
                case 3:
                    // 新增员工
                    doSave();
                    break;
                case 4:
                    // 修改员工信息
                    doModify();
                    break;
                case 5:
                    // 删除员工
                    doDelete();
                    break;
                case 0:
                    // 退出系统
                    System.out.println("感谢您的使用!!!");
                    return;
                default:
                    System.out.println("您的输入有误,请重新输入!!!");
            }
        }
    }

6. 员工信息管理

首先导入如下员工信息:

drop table if exists t_employee;

create table t_employee(
  id bigint primary key auto_increment,
  name varchar(255),
  job varchar(255),
  hiredate char(10),
  salary decimal(10,2),
  address varchar(255)
);

insert into t_employee(name,job,hiredate,salary,address) values('张三','销售员','1999-10-11',5000.0,'北京朝阳');
insert into t_employee(name,job,hiredate,salary,address) values('李四','编码人员','1998-02-12',5000.0,'北京海淀');
insert into t_employee(name,job,hiredate,salary,address) values('王五','项目经理','2000-08-11',5000.0,'北京大兴');
insert into t_employee(name,job,hiredate,salary,address) values('赵六','产品经理','2022-09-11',5000.0,'北京东城');
insert into t_employee(name,job,hiredate,salary,address) values('钱七','测试员','2024-12-11',5000.0,'北京西城');

commit;

select * from t_employee;

6.1 查看员工信息

static void doList() throws SQLException {
    // 查看所有员工信息
    String sql = "SELECT name, job FROM t_employee";
    pstm = conn.prepareStatement(sql);
    res = pstm.executeQuery();
    while (res.next()) {
        String name = res.getString("name");
        String job = res.getString("job");
        System.out.println(name + "--" + job);
    }
}

6.2 查看员工详情

static void doDetail(String name) throws SQLException {
    String sql = "SELECT * from t_employee WHERE name=?";
    pstm = conn.prepareStatement(sql);
    pstm.setString(1, name);
    res = pstm.executeQuery();
    if (res.next()) {
        String id  = res.getString("id");
        String tname = res.getString("name");
        String job = res.getString("job");
        String hireDate = res.getString("hireDate");
        double salary = res.getDouble("salary");
        String address = res.getString("address");
        System.out.println(String.format("%s %s %s %s %f %s", id, tname, job, hireDate, salary, address));
    } else {
        System.out.println("很抱歉, 该员工信息不存在!!!");
    }
}

6.3 新增员工

static void doSave() throws SQLException {
    String sql = "INSERT INTO t_employee(name, salary, job, hireDate, address) VALUES(?, ?, ?, ?, ?)";
    pstm = conn.prepareStatement(sql);
    System.out.println("请输入员工姓名: ");
    String name = sc.next();
    System.out.println("请输入员工职位 ");
    String job = sc.next();
    System.out.println("请输入员工薪资: ");
    double salary = sc.nextDouble();
    System.out.println("请输入员工入职时间: ");
    String hireDate = sc.next();
    System.out.println("请输入员工家庭地址: ");
    String address = sc.next();
    pstm.setString(1, name);
    pstm.setDouble(2, salary);
    pstm.setString(3, job);
    pstm.setString(4, hireDate);
    pstm.setString(5, address);
    pstm.executeUpdate();
    System.out.println("员工信息录入成功");
}

6.4 修改员工信息

static void doModify() throws SQLException {
    // 修改员工信息
    String sql = "UPDATE t_employee set salary=?, job=? WHERE name=?";
    pstm =conn.prepareStatement(sql);
    System.out.println("请输入需要修改的员工姓名: ");
    String tname = sc.next();
    System.out.println("请输入修改后的薪资: ");
    double salary = sc.nextDouble();
    System.out.println("请输入修改后的职位: ");
    String job = sc.next();
    pstm.setDouble(1, salary);
    pstm.setString(2, job);
    pstm.setString(3, tname);
    pstm.executeUpdate();
    System.out.println("员工信息修改成功!!!");
}

6.5 删除员工信息

static void doDelete() throws SQLException {
    // 删除员工
    String sql = "DELETE FROM t_employee WHERE name=?";
    System.out.println("请输入要删除的员工姓名: ");
    String t_name = sc.next();
    pstm= conn.prepareStatement(sql);
    pstm.setString(1, t_name);
    pstm.executeUpdate();
    System.out.println(t_name + "的员工信息已成功删除");
}

7. DAO

DAOData Access Object,翻译为数据访问对象,是一种用于将业务逻辑与数据访问逻辑分离的设计模式,核心目的是通过抽象接口对数据库的操作,提高代码的可维护性、可扩展性以及可测试性。DAO的核心作用如下:

  • 解耦业务与数据访问:业务层Service通过调用 DAO 接口操作数据,无需关心底层是 JDBCHibernate 还是其他技术实现。
  • 统一数据操作规范:定义标准 CRUD接口,确保不同数据源如 MySQLMongoDB的操作方式一致。
  • 提高代码复用性:同一 DAO 可被多个业务模块复用,减少重复代码。
  • 简化单元测试:通过 Mock DAO 实现,无需真实数据库即可测试业务逻辑。

DAO的典型分层结构为:表现层调用业务逻辑层,业务逻辑层调用DAO接口层,DAO接口层实现DAO实现层。DAO实现层操作数据源。

  • 表现层:负责用户交互,如网页、APP界面,展示数据并收集用户输入。
  • 业务逻辑层:处理业务规则、流程控制、调用DAO接口操作数据。
  • DAO接口层:定义数据操作的抽象接口,屏蔽底层实现细节。
  • DAO实现层:实现接口,具体操作数据库或其它数据源。
  • 数据源:实际存储或提供数据的位置。

7.1 javaBeanpojo

JavaBeanPOJOJava 中用于描述简单数据对象的术语,它们都用于封装数据,但在设计规范和用途上有所不同。

  • pojo:指普通的java对象,不依赖于任何框架或接口,仅通过简单的字段和方法实现数据封装。其特点就是无强制规范、无侵入式。

    public class User {
        private String name;
        private int age;
    
        public User() {}
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    
        public int getAge() { return age; }
        public void setAge(int age) { this.age = age; }
    
        public boolean isAdult() {
            return age >= 18;
        }
    }
    
  • javaBeanJavaBean是一种符合特定规范的 POJO,主要用于 GUI 开发、工具类库或需要反射操作的场景。无参构造器必须存在,字段私有化,通过属性访问器访问字段。

    import java.io.Serializable;
    
    public class UserBean implements Serializable { // 实现 Serializable
        // 字段私有
        private String name;
        private int age;
        private boolean active;
    
        // 无参构造器
        public UserBean() {}
    
        // Getter 和 Setter
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    
        public int getAge() { return age; }
        public void setAge(int age) { this.age = age; }
    
        // 布尔属性可用 isXxx()
        public boolean isActive() { return active; }
        public void setActive(boolean active) { this.active = active; }
    }
    
    特性POJOJavaBean
    规范要求必须遵守无参构造器、Getter/Setter
    框架依赖无,但常用于需要内省的工具场景
    序列化可选通常实现 Serializable
    方法逻辑可包含自定义业务逻辑通常仅限 Getter/Setter
    典型用途领域模型、Spring Bean网路传输、缓存存储

7.2 实现步骤

首先封装Employee类,一般将普通类称为javaBeanpojo如下:

package com.person;

public class Employee {
    private Long id;
    private String name;
    private String job;
    private Double salary;
    private String hireDate;
    private String address;

    public Employee(){

    }

    public Employee(Long id, String name, String job, Double salary, String hireDate, String address) {
        this.id = id;
        this.name = name;
        this.job = job;
        this.salary = salary;
        this.hireDate = hireDate;
        this.address = address;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    public String getHireDate() {
        return hireDate;
    }

    public void setHireDate(String hireDate) {
        this.hireDate = hireDate;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", job='" + job + '\'' +
                ", salary=" + salary +
                ", hireDate='" + hireDate + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

然后就是定义EmployeeDaoDAO不负责任何业务逻辑的处理,只负责CRUD操作。

package com.person.jdbc.dao;

import com.powernode.jdbc.beans.Employee;
import com.powernode.jdbc.utils.DbUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class EmployeeDao {
    /**
     * 新增员工
     */
    public int insert(Employee employee) {
        Connection conn = null;
        PreparedStatement ps = null;
        int count = 0;
        try {
            conn = DbUtils.getConnection();
            String sql = "insert into t_employee(name,job,salary,hiredate,address) values(?,?,?,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1, employee.getName());
            ps.setString(2, employee.getJob());
            ps.setDouble(3, employee.getSalary());
            ps.setString(4, employee.getHiredate());
            ps.setString(5, employee.getAddress());
            count = ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.close(conn, ps, null);
        }
        return count;
    }

    /**
     * 修改员工
     */
    public int update(Employee employee){
        Connection conn = null;
        PreparedStatement ps = null;
        int count = 0;
        try {
            conn = DbUtils.getConnection();
            String sql = "update t_employee set name=?, job=?, salary=?, hiredate=?, address=? where id=?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, employee.getName());
            ps.setString(2, employee.getJob());
            ps.setDouble(3, employee.getSalary());
            ps.setString(4, employee.getHiredate());
            ps.setString(5, employee.getAddress());
            ps.setLong(6, employee.getId());
            count = ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.close(conn, ps, null);
        }
        return count;
    }

    /**
     * 根据id删除员工信息
     */
    public int deleteById(Long id){
        Connection conn = null;
        PreparedStatement ps = null;
        int count = 0;
        try {
            conn = DbUtils.getConnection();
            String sql = "delete from t_employee where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setLong(1, id);
            count = ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.close(conn, ps, null);
        }
        return count;
    }

    /**
     * 根据id查询所有员工
     */
    public Employee selectById(Long id){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        Employee employee = null;
        try {
            conn = DbUtils.getConnection();
            String sql = "select * from t_employee where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setLong(1, id);
            rs = ps.executeQuery();
            if(rs.next()){
                employee = new Employee();
                employee.setId(id);
                employee.setName(rs.getString("name"));
                employee.setJob(rs.getString("job"));
                employee.setSalary(rs.getDouble("salary"));
                employee.setHiredate(rs.getString("hiredate"));
                employee.setAddress(rs.getString("address"));
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.close(conn, ps, rs);
        }
        return employee;
    }

    /**
     * 查询所有员工信息
     */
    public List<Employee> selectAll(){
        List<Employee> employees = new ArrayList<>();
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = DbUtils.getConnection();
            String sql = "select * from t_employee";
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();
            while(rs.next()){
                Employee employee = new Employee();
                employee.setId(rs.getLong("id"));
                employee.setName(rs.getString("name"));
                employee.setJob(rs.getString("job"));
                employee.setSalary(rs.getDouble("salary"));
                employee.setHiredate(rs.getString("hiredate"));
                employee.setAddress(rs.getString("address"));
                employees.add(employee);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.close(conn, ps, rs);
        }
        return employees;
    }
}

8. 连接池

Connetion对象是重量级对象,创建一个Connection对象就是建立两个进程之间的通信,非常消耗资源,一次完整的数据库操作,90%的时间都花在了连接对象的创建。除了效率低下,还有一个问题就是连接对象的数量无法限制,如果连接对象的数量过高,可能会导致Mysql数据库服务器崩溃。

如果使用数据库连接池,就会提前创建好N个连接对象,将其存放到缓存中。用户请求时,连接对象就可以直接从连接池中获取,而不需要再创建新的连接。并且,当连接池中没有空闲的连接对象时,用户请求就只能等待,就可以避免无法控制连接对象的数量。

连接池有很多,不过所有的连接池都实现了javax.sql.DataSource接口。因此,不管使用哪家的连接池产品,都直接调用javax.sql.DataSource接口的方法即可。当然,我们也可以通过实现该接口编写自己的连接池。

对于一个基本的连接池,一般都包含以下属性:

  • initialSize:初始化连接数,连接池初始化时创建的连接数。
  • maxActive:连接池中的最大连接数。当连接池中的连接数量达到此值时,后续请求被阻塞并等待连接池中有连接被释放后再处理。
  • minidle: 指连接池中最小的空闲连接数,也就是即使当前没有请求,连接池中至少也要保持一定数量的空闲连接,以便应对高并发请求或突发连接请求的情况。
  • maxidle: 指连接池中最大的空闲连接数,也就是连接池中最多允许保持的空闲连接数量。当连接池中的空闲连接数量达到了maxIdle设定的值后,多余的空闲连接将会被连接池释放掉。
  • maxWait:最大等待时间,当连接池中的连接数量达到最大值时,后续请求需要等待的最大时间,如果超过这个时间,则会抛出异常。
  • testOnBorrow、testOnReturn:为了确保连接池中只有可用的连接,一些连接池会定期对连接进行有效性检查,这里的属性就是配置这些检查的选项。
  • driverurlpassworduser等。

常用的连接池有DruidHikariCP等连接池,如下:

  • Druid

    jdbc.driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/jdbc
    user=root
    password=asdf
    initialSize=5
    minIdle=10
    maxActive=20
    
    // 读取属性配置文件
    InputStream in = DruidConfig.class.getClassLoader().getResourceAsStream("jdbc.properties");
    Properties props = new Properties();
    props.load(in);
    // 创建连接池
    DataSource dataSource = DruidDataSourceFactory.createDataSource(props);
    Connection conn = dataSource.getConnection();
    
    
    // 关闭连接 并不是真正的关闭连接, 只是将连接归还连接池
    conn.close()
    
  • HikariCP

    jdbcUrl=jdbc:mysql://localhost:3306/jdbc
    username=root
    password=asdf
    driverClassName=com.mysql.cj.jdbc.Driver
    minimumIdle=5
    maximumPoolSize=20
    
    InputStream in = HikariConfig.class.getClassLoader().getResourceAsStream("config.properties");
    props.load(in);
    HikariConfig config = new HikariConfig(props);
    DataSource dataSource = new HikariDataSource(config);
    Connection conn = dataSource.getConnection();
    
    conn
    

量过高,可能会导致Mysql数据库服务器崩溃。

如果使用数据库连接池,就会提前创建好N个连接对象,将其存放到缓存中。用户请求时,连接对象就可以直接从连接池中获取,而不需要再创建新的连接。并且,当连接池中没有空闲的连接对象时,用户请求就只能等待,就可以避免无法控制连接对象的数量。

连接池有很多,不过所有的连接池都实现了javax.sql.DataSource接口。因此,不管使用哪家的连接池产品,都直接调用javax.sql.DataSource接口的方法即可。当然,我们也可以通过实现该接口编写自己的连接池。

对于一个基本的连接池,一般都包含以下属性:

  • initialSize:初始化连接数,连接池初始化时创建的连接数。
  • maxActive:连接池中的最大连接数。当连接池中的连接数量达到此值时,后续请求被阻塞并等待连接池中有连接被释放后再处理。
  • minidle: 指连接池中最小的空闲连接数,也就是即使当前没有请求,连接池中至少也要保持一定数量的空闲连接,以便应对高并发请求或突发连接请求的情况。
  • maxidle: 指连接池中最大的空闲连接数,也就是连接池中最多允许保持的空闲连接数量。当连接池中的空闲连接数量达到了maxIdle设定的值后,多余的空闲连接将会被连接池释放掉。
  • maxWait:最大等待时间,当连接池中的连接数量达到最大值时,后续请求需要等待的最大时间,如果超过这个时间,则会抛出异常。
  • testOnBorrow、testOnReturn:为了确保连接池中只有可用的连接,一些连接池会定期对连接进行有效性检查,这里的属性就是配置这些检查的选项。
  • driverurlpassworduser等。

常用的连接池有DruidHikariCP等连接池,如下:

  • Druid

    jdbc.driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/jdbc
    user=root
    password=asdf
    initialSize=5
    minIdle=10
    maxActive=20
    
    // 读取属性配置文件
    InputStream in = DruidConfig.class.getClassLoader().getResourceAsStream("jdbc.properties");
    Properties props = new Properties();
    props.load(in);
    // 创建连接池
    DataSource dataSource = DruidDataSourceFactory.createDataSource(props);
    Connection conn = dataSource.getConnection();
    
    
    // 关闭连接 并不是真正的关闭连接, 只是将连接归还连接池
    conn.close()
    
  • HikariCP

    jdbcUrl=jdbc:mysql://localhost:3306/jdbc
    username=root
    password=asdf
    driverClassName=com.mysql.cj.jdbc.Driver
    minimumIdle=5
    maximumPoolSize=20
    
    InputStream in = HikariConfig.class.getClassLoader().getResourceAsStream("config.properties");
    props.load(in);
    HikariConfig config = new HikariConfig(props);
    DataSource dataSource = new HikariDataSource(config);
    Connection conn = dataSource.getConnection();
    
    conn
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2324716.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

瑞芯微 RKrga接口 wrapbuffer_virtualaddr 使用笔记

一、源码 官方在librga中给了很多 demo 以供参考&#xff0c;例如 imresize 操作&#xff1a; /** Copyright (C) 2022 Rockchip Electronics Co., Ltd.* Authors:* YuQiaowei <cerf.yurock-chips.com>** Licensed under the Apache License, Version 2.0 (the &qu…

【数据结构】[特殊字符] 并查集优化全解:从链式退化到近O(1)的性能飞跃 | 路径压缩与合并策略深度实战

并查集的优化 导读一、合并优化1.1 基本原理1.2 按大小合并1.3 按秩合并1.4 两种合并的区别**1.4.1 核心目标****1.4.2 数据存储****1.4.3 合并逻辑****1.4.4 树高控制****1.4.5 适用场景****1.4.6 路径压缩兼容性****1.4.7 极端案例对比****1.4.8 小结**二、查找优化2.1 路径压…

如何在 AI 搜索引擎(GEO)霸屏曝光,快速提升知名度?

虽然大多数人仍然使用 Google 来寻找答案&#xff0c;但正在发生快速转变。ChatGPT、Copilot、Perplexity 和 DeepSeek 等 LLM 已成为主流。这主要是因为每个都有自己的免费和公共版本&#xff0c;并且总是有重大的质量改进。 许多人每天都使用这些工具来提问和搜索互联网&…

VLAN综合实验二

一.实验拓扑&#xff1a; 二.实验需求&#xff1a; 1.内网Ip地址使用172.16.0.0/分配 2.sw1和SW2之间互为备份 3.VRRP/STP/VLAN/Eth-trunk均使用 4.所有Pc均通过DHCP获取IP地址 5.ISP只能配置IP地址 6.所有…

Kubernetes》k8s》Containerd 、ctr 、cri、crictl

containerd ctr crictl ctr 是 containerd 的一个客户端工具。 crictl 是 CRI 兼容的容器运行时命令行接口&#xff0c;可以使用它来检查和调试 k8s 节点上的容器运行时和应用程序。 ctr -v 输出的是 containerd 的版本&#xff0c; crictl -v 输出的是当前 k8s 的版本&#x…

SQL语句及其应用(中)(DQL语句之单表查询)

SQL语句的定义: 概述: 全称叫 Structured Query Language, 结构化查询语言, 主要是实现 用户(程序员) 和 数据库软件(例如: MySQL, Oracle)之间交互用的. 分类: DDL: 数据定义语言, 主要是操作 数据库, 数据表, 字段, 进行: 增删改查(CURD) 涉及到的关键字: create, drop, …

算法题(111):k与迷宫

审题&#xff1a; 本题需要我们寻找迷宫中的所有出口&#xff0c;若有出口需要输出距离最近的出口的距离&#xff0c;若没有就输出-1 时间复杂度&#xff1a;由于边距为1&#xff0c;我们本题采用bfs算法&#xff0c;在最坏的情况下我们需要遍历所有位置&#xff0c;时间复杂度…

Redis-常用命令

目录 1、Redis数据结构 2、命令简介 2.1、通用命令 DEL EXISTS EXPIRE 2.2、String命令 SET和GET MSET和MGET INCR和INCRBY和DECY SETNX SETEX 2.3、Key的层级结构 2.4、Hash命令 HSET和HGET HMSET和HMGET HGETALL HKEYS和HVALS HINCRBY HSETNX 2.5、List命…

从虚拟现实到可持续设计:唐婉歆的多维创新之旅

随着线上线下融合逐渐成为全球家居与建材行业的发展趋势,全球市场对高品质、个性化家居和建材产品的需求稳步攀升,也对设计师提出更高的要求。在这一背景下,设计师唐婉歆将以产品设计师的身份,正式加入跨国企业AmCan 美加集团,投身于备受行业瞩目的系列设计项目。她将负责Showr…

音视频 四 看书的笔记 MediaPlayerService

Binder机制看这里 Binde机智 这是一个分割符 Binder机智 分割(goutou) Binder机制 MediaPlayerService多媒体框架中一个非常重要的服务。MediaPlayerService 我原称之为链接之王 图片来源 MediaPlayer 是客户端 C/S 中的CMediaPlayerService MediaPlayerService::Client 是服…

vmware 创建win10 系统,虚拟机NAT网络设置

虚拟机设置&#xff1a; 物理机本机创建桥接&#xff1a; 如何创建桥接&#xff0c;请自行脑补~

【初阶数据结构】线性表之双链表

文章目录 目录 一、双链表的概念 二、双链表的实现 1.初始化 2.尾插 3.头插 4.打印 5.判断双链表是否为空 6.尾删 7.头删 8.查找 9.在指定的位置之后插入数据 10.删除指定位置的数据 11.销毁 三、完整源码 总结 一、双链表的概念 链表的结构非常多样&#xff0…

智能路由系统-信息泄露漏洞挖掘

1.漏洞描述&#xff1a; Secnet-智能路由系统 actpt_5g.data 信息泄露&#xff0c;攻击者可利用此漏洞收集敏感信息&#xff0c;从而为下一步攻击做准备。 2.fofa搜索语句 title"安网-智能路由系统" || title"智能路由系统" || title"安网科技-智能…

表格图表切换,图表无法展示问题复盘

项目背景 103项目CPC卡使用模块在原有的表格展示数据的基础之上&#xff0c;增加环状饼图图表展示&#xff0c;采用tab切换的方式实现 问题描述 图表无法设置宽高&#xff0c;导致饼图无法渲染 具体代码 // 入口页<el-tabs type"card" class"cts_flex_t…

AI赋能python数据处理、分析与预测操作流程

以数据集预测鱼类种类(Species)开展以下研究。数据格式如下: 以下是一个系统的分析思路及推荐的机器学习算法: 1. 数据预处理与探索性分析 缺失值与异常值处理: 检查数据完整性(如Roach类中Weight=0的记录需修正或删除)。 通过箱线图或Z-Score检测异常值,判断是否需…

基于74LS192的十进制两位数正向计时器(proteus仿真)

在数字电路设计中&#xff0c;计时器是一个非常常见的应用。今天&#xff0c;我将分享一个基于 74LS192 双向计数器 的十进制两位数正向计时器电路设计。这个电路可以实现从 00 到 99 的十进制正向计数&#xff0c;并通过两个七段数码管显示结果。 最终效果如图&#xff1a; 各…

算法-前缀和与差分

一、前缀和&#xff08;Prefix Sum&#xff09; 1. 核心思想 前缀和是一种预处理数组的方法&#xff0c;通过预先计算并存储数组的前缀和&#xff0c;使得后续的区间和查询可以在**O(1)**时间内完成。 2. 定义 给定数组 nums&#xff0c;前缀和数组 prefixSum 的每个元素 p…

React(六)React过渡动画-CSS编写方式

React过渡动画 react-transition-group介绍 在开发中&#xff0c;我们想要给一个组件的显示和消失添加某种过渡动画&#xff0c;提高用户体验→可通过react-transition-group实现。React曾为开发者提供过动画插件 react-addons-css-transition-group&#xff0c;后由社区维护…

第十五章:Python的Pandas库详解及常见用法

在数据分析领域&#xff0c;Python的Pandas库是一个不可或缺的工具。它提供了高效的数据结构和数据分析工具&#xff0c;使得数据处理变得简单而直观。本文将详细介绍Pandas库的基本功能、常见用法&#xff0c;并通过示例代码演示如何使用Pandas进行数据处理。最后&#xff0c;…

libva基础

Libva&#xff08;Lib Video Acceleration&#xff09;是一个开源的库&#xff0c;实现了 **VA-API**&#xff08;Video Acceleration API&#xff09;&#xff0c;旨在为视频处理提供跨平台的硬件加速支持。 1、核心功能与作用 硬件加速抽象层&#xff1a;Libva 作为中间层&…