JDBC

Published 2026-07-29 10:54 Updated 2026-07-29 10:54 2739 words 14 min read ... Page views

This article introduces the basic concepts, usage process and best practices of JDBC. JDBC is a standard interface for Java to connect to databases. Access to different databases is achieved through drivers, and support connection management, SQL operations, transaction control and metadata acquisition. The article details how to connect to MySQL, use PreparedStatement to prevent SQL injection, process query results, manage transactions, and resource shutdown, and emphasizes code security and maintainability, such as avoiding hard-coded passwords, using DAO mode and connection pooling, and correctly handling primary keys and result sets.

JDBC

JDBC Overview

JDBC is a set of database access specifications provided by Java and is mainly located in the java.sql and javax.sql packages. Database manufacturers implement the JDBC interface through drivers, and Java programs write code for the unified interface to connect different types of relational databases.

JDBC is commonly used to perform the following operations:

  • Establish and close database connections.
  • Executes add, modify, delete, and query statements.
  • Process query result sets.
  • Control transactions.
  • Gets metadata for the database and result set.

Prepare MySQL drivers

Before using JDBC to connect to MySQL, you need to add the MySQL Connector/J driver to the project dependency.

In traditional Java projects, you can put the driver JAR file into the project’s lib directory and add it to the class library. In the Maven project, versions should be driven through project dependency management.

Common driver class names for MySQL 8 are as follows:

com.mysql.cj.jdbc.Driver

Modern JDBC drivers usually support automatic registration, but explicit loading of drivers still helps understand the basic JDBC process.

try {
    Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
    throw new RuntimeException("未找到 MySQL JDBC 驱动", e);
}

establish a database connection

Use DriverManager.getConnection() to create a Connection object.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionTest {
    public static void main(String[] args) {
        String url =
                "jdbc:mysql://localhost:3306/java2601" +
                "?serverTimezone=UTC&characterEncoding=utf8";
        String username = "root";
        String password = "root";

        try (Connection connection = DriverManager.getConnection(
                url,
                username,
                password
        )) {
            System.out.println("数据库连接成功:" + !connection.isClosed());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Common configurations in JDBC URLs include user names, passwords, time zones, character encoding, and SSL settings. The username and password are usually passed in as independent parameters of getConnection(), and it is not recommended to write directly to the URL.

Database accounts and passwords should not be hard-coded in public repositories. Real projects typically use configuration files, environment variables, or key management services.

JDBC Basic Programming Steps

Take inserting a record into employee table as an example. The basic steps are as follows:

loaded for

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

acquire a connection

Connection connection = DriverManager.getConnection(
        "jdbc:mysql://localhost:3306/java2601?serverTimezone=UTC&characterEncoding=utf8",
        "root",
        "root"
);

Create SQL execution objects

PreparedStatement is recommended.

String sql = "insert into employee(first_name, salary, department_id) " +
        "values(?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(sql);

Set parameters and execute

statement.setString(1, "Tom");
statement.setDouble(2, 8000);
statement.setInt(3, 5001);

int affectedRows = statement.executeUpdate();
System.out.println("受影响行数:" + affectedRows);

close the asset

Connection, Statement and ResultSet are all resources that need to be closed. It is recommended to use try-with-resources to automatically shut down.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class InsertEmployee {
    public static void main(String[] args) {
        String url =
                "jdbc:mysql://localhost:3306/java2601" +
                "?serverTimezone=UTC&characterEncoding=utf8";
        String sql = "insert into employee(" +
                "first_name, salary, department_id" +
                ") values(?, ?, ?)";

        try (Connection connection = DriverManager.getConnection(
                url,
                "root",
                "root"
        ); PreparedStatement statement = connection.prepareStatement(sql)) {

            statement.setString(1, "张三");
            statement.setDouble(2, 8000);
            statement.setInt(3, 5001);

            int affectedRows = statement.executeUpdate();
            System.out.println("受影响行数:" + affectedRows);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Execute add, delete, and change statements

executeUpdate() is used to execute INSERT, UPDATE, and DELETE to return the affected rows.

new data

String sql = "insert into employee(" +
        "first_name, job_id, salary, department_id" +
        ") values(?, ?, ?, ?)";

try (PreparedStatement statement = connection.prepareStatement(sql)) {
    statement.setString(1, "Rose");
    statement.setString(2, "程序员");
    statement.setDouble(3, 9000);
    statement.setInt(4, 5001);
    statement.executeUpdate();
}

modify data

String sql = "update employee " +
        "set first_name = ?, job_id = ?, salary = ?, department_id = ? " +
        "where employee_id = ?";

try (PreparedStatement statement = connection.prepareStatement(sql)) {
    statement.setString(1, "Jack");
    statement.setString(2, "项目经理");
    statement.setDouble(3, 12000);
    statement.setInt(4, 5002);
    statement.setInt(5, 101);
    statement.executeUpdate();
}

delete data

String sql = "delete from employee where employee_id = ?";

try (PreparedStatement statement = connection.prepareStatement(sql)) {
    statement.setInt(1, 101);
    statement.executeUpdate();
}

Use ResultSet to process query results

Calling executeQuery() to execute the query statement will return the ResultSet object.

ResultSet can be understood as a two-dimensional result set with a cursor. The initial cursor is located before the first line and moves down one line after each call to next().

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

public class QueryEmployee {
    public static void main(String[] args) {
        String url =
                "jdbc:mysql://localhost:3306/java2601" +
                "?serverTimezone=UTC&characterEncoding=utf8";
        String sql = "select " +
                "employee_id as eid, first_name, job_id, salary " +
                "from employee";

        try (Connection connection = DriverManager.getConnection(
                url,
                "root",
                "root"
        ); PreparedStatement statement = connection.prepareStatement(sql);
             ResultSet resultSet = statement.executeQuery()) {

            while (resultSet.next()) {
                int employeeId = resultSet.getInt("eid");
                String firstName = resultSet.getString("first_name");
                String jobId = resultSet.getString("job_id");
                double salary = resultSet.getDouble("salary");

                System.out.println(
                        employeeId + "\t" +
                        firstName + "\t" +
                        jobId + "\t" +
                        salary
                );
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

getXxx() can obtain data by column sequence number or column name. Column numbers start from 1. Using column names or aliases is usually clearer.

For numeric columns that may be NULL, methods such as getInt() and getDouble() will return default values for basic types. When you need to distinguish NULL in the database, you can call wasNull() or use getObject() to obtain the packaging type.

SQL injection issues

The following string splicing method carries SQL injection risks:

String sql = "select * from db_users " +
        "where username = '" + username + "' " +
        "and password = '" + password + "'";

User input becomes directly part of the SQL syntax. An attacker may construct special input to change the semantics of the original query.

Using PreparedStatement

PreparedStatement uses placeholders to represent parameter values and sets parameters through the type-safe setXxx() method. Parameter values are not parsed as SQL structures, so most SQL injection problems can be effectively avoided.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;

public class LoginTest {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.println("请输入用户名:");
        String username = scanner.nextLine();

        System.out.println("请输入密码:");
        String password = scanner.nextLine();

        String sql = "select id from db_users " +
                "where username = ? and password = ?";

        try (Connection connection = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/java2601?serverTimezone=UTC&characterEncoding=utf8",
                "root",
                "root"
        ); PreparedStatement statement = connection.prepareStatement(sql)) {

            statement.setString(1, username);
            statement.setString(2, password);

            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    System.out.println("登录成功");
                } else {
                    System.out.println("登录失败");
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

The example is only used to illustrate JDBC queries. Real systems should not store clear passwords in the database, but should store the results processed by a reliable password hashing algorithm and use random salts.

Differences between Statement and PreparedStatement

Comparison ItemsStatementPreparedStatement
SQL parameteris usually spliced by stringusing ? placeholder
SQL injection riskHigherParameter value separated from SQL structure
ReadabilityDynamic splicing is more complexParameter position is clear
Repeat executionprocessing complete SQLdriver or database may reuse execution plan
type settingsrelies on string formatUse setString(), setInt() and other methods

PreparedStatement should be used first in business codes. SQL structures such as table names, column names, and sort directions cannot use placeholders and must be selected through a trusted whitelist.

Common JDBC APIs

Connection

Connection represents a database connection. Common methods include:

MethodAction
prepareStatement()Creating a precompiled SQL object
setAutoCommit(false)Turn off automatic submission and start manual control of transactions
commit()Submission
rollback()Rollback Transactions
setSavepoint()Create save point
rollback(Savepoint)Rollback to the specified save point
getMetaData()Getting database metadata
close()Close connection

To form the same local transaction, multiple SQL must be executed in the same Connection.

PreparedStatement

Common methods include setString(), setInt(), setObject(), executeUpdate() and executeQuery().

Parameter sequence numbers start from 1.

ResultSet

Common methods include next(), getString(), getInt(), getObject() and wasNull().

JDBC transaction control

The following example deletes two employee records in the same transaction. When any operation fails, all operations are rolled back.

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class TransactionTest {
    public static void deleteEmployees(
            Connection connection,
            int firstId,
            int secondId
    ) throws SQLException {
        String sql = "delete from employee where employee_id = ?";
        boolean originalAutoCommit = connection.getAutoCommit();

        try {
            connection.setAutoCommit(false);

            try (PreparedStatement statement = connection.prepareStatement(sql)) {
                statement.setInt(1, firstId);
                statement.executeUpdate();

                statement.setInt(1, secondId);
                statement.executeUpdate();
            }

            connection.commit();
        } catch (SQLException e) {
            connection.rollback();
            throw e;
        } finally {
            connection.setAutoCommit(originalAutoCommit);
        }
    }
}

When using connection pools, the connection state should be restored before returning the connection, otherwise subsequent borrowing of code from the same physical connection may be affected.

savepoint

Savepoints allow transactions to be rolled back to intermediate states.

import java.sql.Connection;
import java.sql.Savepoint;
import java.sql.SQLException;

public class SavepointTest {
    public static void execute(Connection connection) throws SQLException {
        connection.setAutoCommit(false);
        Savepoint savepoint = null;

        try {
            executeFirstSql(connection);
            savepoint = connection.setSavepoint("after_first_sql");
            executeSecondSql(connection);
            connection.commit();
        } catch (SQLException e) {
            if (savepoint != null) {
                connection.rollback(savepoint);
                connection.commit();
            } else {
                connection.rollback();
            }
            throw e;
        }
    }

    private static void executeFirstSql(Connection connection) {
    }

    private static void executeSecondSql(Connection connection) {
    }
}

database metadata

Through DatabaseMetaData, you can obtain database product name, version, driver name and table structure and other information.

import java.sql.DatabaseMetaData;

DatabaseMetaData metadata = connection.getMetaData();
System.out.println(metadata.getDatabaseProductName());
System.out.println(metadata.getDatabaseProductVersion());
System.out.println(metadata.getDriverName());

Result set metadata

Through ResultSetMetaData, you can obtain information such as the number of columns, column names, and column types in the result set.

import java.sql.ResultSetMetaData;

ResultSetMetaData metadata = resultSet.getMetaData();
int columnCount = metadata.getColumnCount();

for (int index = 1; index <= columnCount; index++) {
    System.out.println(metadata.getColumnLabel(index));
    System.out.println(metadata.getColumnTypeName(index));
}

JDBC encapsulation

Encapsulation connection creation process

package com.hyxy.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public final class DBUtil {
    private static final String URL =
            "jdbc:mysql://localhost:3306/java2601" +
            "?serverTimezone=UTC&characterEncoding=utf8";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root";

    private DBUtil() {
    }

    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USERNAME, PASSWORD);
    }
}

Real projects usually use database connection pools instead of creating physical connections directly through DriverManager each time.

DAO and VO

DAO is the abbreviation for Data Access Object and is used to encapsulate database access logic. The business layer completes additions, deletions, modifications, and inspections through the DAO method to avoid scattering SQL in the business code.

VO is the abbreviation of Value Object. In the current course example, VO is used to hold database records, and the fields usually correspond to the table structure. Real projects also often use names such as Entity, POJO, or DTO, but the meanings are not exactly the same.

Apache Commons DbUtils

DbUtils is a lightweight encapsulation of JDBC. The core class QueryRunner simplifies parameter binding, result set processing, and resource management.

When QueryRunner is created using an external connection, the caller is still responsible for closing the connection.

new data

import org.apache.commons.dbutils.QueryRunner;

import java.sql.Connection;
import java.sql.SQLException;

public class DbUtilsInsertTest {
    public static void main(String[] args) {
        QueryRunner runner = new QueryRunner();
        String sql = "insert into employee(first_name, job_id) values(?, ?)";

        try (Connection connection = DBUtil.getConnection()) {
            int affectedRows = runner.update(
                    connection,
                    sql,
                    "Smith",
                    "财务总监"
            );
            System.out.println("受影响行数:" + affectedRows);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Get self-added primary key

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import java.sql.Connection;
import java.sql.SQLException;

public class DbUtilsPrimaryKeyTest {
    public static void main(String[] args) {
        QueryRunner runner = new QueryRunner();
        String sql = "insert into employee(first_name, job_id) values(?, ?)";

        try (Connection connection = DBUtil.getConnection()) {
            Number primaryKey = runner.insert(
                    connection,
                    sql,
                    new ScalarHandler<>(),
                    "Smith2",
                    "财务总监2"
            );
            System.out.println(primaryKey);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

The specific type of primary key value returned by the driver may vary, and it is usually safer to use Number than fix it to BigInteger.

Query multiple records

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public class DbUtilsListTest {
    public static void main(String[] args) {
        QueryRunner runner = new QueryRunner();
        String sql = "select * from employee";

        try (Connection connection = DBUtil.getConnection()) {
            List<Employee> employees = runner.query(
                    connection,
                    sql,
                    new BeanListHandler<>(Employee.class)
            );

            for (Employee employee : employees) {
                System.out.println(employee);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

The JavaBean property name needs to match the column label of the query result. When column names are inconsistent, aliases can be used in SQL.

select employee_id as employeeId,
       first_name as firstName,
       department_id as departmentId
from employee;

Query by condition

String sql = "select * from employee " +
        "where department_id = ? and salary >= ?";

try (Connection connection = DBUtil.getConnection()) {
    List<Employee> employees = runner.query(
            connection,
            sql,
            new BeanListHandler<>(Employee.class),
            5001,
            3000
    );
}

Query by primary key

import org.apache.commons.dbutils.handlers.BeanHandler;

String sql = "select * from employee where employee_id = ?";

try (Connection connection = DBUtil.getConnection()) {
    Employee employee = runner.query(
            connection,
            sql,
            new BeanHandler<>(Employee.class),
            101
    );

    System.out.println(employee);
}

Comprehensive example of user management

Create user table

drop table if exists t_user;

create table t_user (
    id int not null auto_increment,
    username varchar(255) null,
    pwd varchar(255) null,
    email varchar(255) null,
    primary key (id)
) engine = InnoDB
  default character set = utf8mb4;

insert into t_user(username, pwd, email)
values('Jerry', '888888', 'jerry@126.com');

Create a user entity class

package com.hyxy.vo;

public class User {
    private int id;
    private String username;
    private String pwd;
    private String email;

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

It is not recommended to output passwords in toString().

Create user DAO

package com.hyxy.dao;

import com.hyxy.vo.User;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public class UserDao {
    private final QueryRunner runner = new QueryRunner();

    public int register(User user) throws SQLException {
        String sql = "insert into t_user(username, pwd, email) values(?, ?, ?)";

        try (Connection connection = DBUtil.getConnection()) {
            return runner.update(
                    connection,
                    sql,
                    user.getUsername(),
                    user.getPwd(),
                    user.getEmail()
            );
        }
    }

    public User login(String username, String password) throws SQLException {
        String sql = "select id, username, email " +
                "from t_user where username = ? and pwd = ?";

        try (Connection connection = DBUtil.getConnection()) {
            return runner.query(
                    connection,
                    sql,
                    new BeanHandler<>(User.class),
                    username,
                    password
            );
        }
    }

    public int update(User user) throws SQLException {
        String sql = "update t_user " +
                "set username = ?, pwd = ?, email = ? where id = ?";

        try (Connection connection = DBUtil.getConnection()) {
            return runner.update(
                    connection,
                    sql,
                    user.getUsername(),
                    user.getPwd(),
                    user.getEmail(),
                    user.getId()
            );
        }
    }

    public int deleteByUsername(String username) throws SQLException {
        String sql = "delete from t_user where username = ?";

        try (Connection connection = DBUtil.getConnection()) {
            return runner.update(connection, sql, username);
        }
    }

    public List<User> findAll() throws SQLException {
        String sql = "select id, username, email from t_user";

        try (Connection connection = DBUtil.getConnection()) {
            return runner.query(
                    connection,
                    sql,
                    new BeanListHandler<>(User.class)
            );
        }
    }
}

Example considerations

  • The interaction layer should not directly splice SQL.
  • Data access logic should be placed in the DAO and business rules should be placed in the Service.
  • Neither database passwords nor user passwords should be hard-coded.
  • The login function should use password hashes rather than plaintext comparisons.
  • Each JDBC resource must be turned off correctly.
  • Multiple related updates require transactions to ensure consistency.

If you enjoyed this, leave a comment~

... Page views
© 2026 跨越星轨的客 @Hoshiumi
Powered by theme astro-koharu · Inspired by Shoka