#JDBC应用开发最佳实践

# 适用范围

本文适用于Java应用通过YashanDB JDBC驱动执行常规业务操作的场景,集中说明连接检查、参数绑定、业务校验、批量操作、事务控制、资源释放和异常记录。

示例验证环境如下:

组件 版本
JDK 17.0.19
YashanDB JDBC驱动 1.10.11
YashanDB Server 23.4.10.100

示例需要建表、插入、更新、查询和删除表权限。生产应用账号应只保留业务表DML权限,表对象由部署流程创建。

# 预期效果

完成本实践后,应用应具备以下能力:

  1. 连接前先检查连接是否可用。
  2. 使用参数绑定完成库存调整。
  3. 在同一事务内更新库存并写入审计。
  4. 出错时能回滚并释放资源。
  5. 日志保留错误信息,不记录敏感数据。

# 实施步骤

# 1. 准备连接和数据库对象

通过环境变量提供连接信息:

环境变量 说明 示例值
YASDB_URL JDBC连接URL jdbc:yasdb://192.168.1.2:1688/yasdb
YASDB_USER 业务账号 app_user
YASDB_PASSWORD 业务账号密码 通过Secret注入,不设置默认值

生产环境由部署账号执行以下建表语句:

CREATE TABLE inventory_stock (
    item_id INTEGER PRIMARY KEY,
    quantity INTEGER
);

CREATE TABLE inventory_stock_audit (
    audit_id INTEGER PRIMARY KEY,
    item_id INTEGER,
    change_amount INTEGER
);

运行账号只需目标表的SELECT、INSERT和UPDATE权限。示例程序为便于独立验证,使用jdbc_bp_stock和jdbc_bp_stock_audit临时对象代替生产表。

# 2. 组织业务事务

库存调整和审计记录必须使用同一个Connection。程序先用带条件的UPDATE完成库存非负校验,再用PreparedStatement.addBatch()批量写入审计记录。任一步骤失败时回滚整个事务。

连接归还前恢复原有自动提交状态。连接池还应负责重置只读属性、隔离级别和会话级配置。不要在多个线程之间共享同一个连接。

# 3. 执行完整业务示例

以下示例创建临时对象、初始化库存、执行两项库存调整、批量写入审计记录并查询结果。程序只删除本次成功创建的对象;如果同名对象已经存在,建表失败且不会删除已有对象。

import com.yashandb.jdbc.YasDataSource;

import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;

public class JdbcApplicationBestPracticeExample {
    private static final String STOCK_TABLE = "jdbc_bp_stock";
    private static final String AUDIT_TABLE = "jdbc_bp_stock_audit";

    public static void main(String[] args) throws Exception {
        YasDataSource dataSource = dataSource();
        boolean stockCreated = false;
        boolean auditCreated = false;
        try (Connection connection = dataSource.getConnection()) {
            if (!connection.isValid(3)) {
                throw new SQLException("The JDBC connection is not valid");
            }
            try (Statement statement = connection.createStatement()) {
                statement.execute("CREATE TABLE " + STOCK_TABLE
                        + " (item_id INTEGER PRIMARY KEY, quantity INTEGER)");
                stockCreated = true;
                statement.execute("CREATE TABLE " + AUDIT_TABLE
                        + " (audit_id INTEGER PRIMARY KEY, item_id INTEGER, "
                        + "change_amount INTEGER)");
                auditCreated = true;
                statement.executeUpdate("INSERT INTO " + STOCK_TABLE
                        + " VALUES (1001, 20)");
                statement.executeUpdate("INSERT INTO " + STOCK_TABLE
                        + " VALUES (1002, 30)");
            }

            adjustStock(connection,
                    new int[] {1001, 1002}, new int[] {-3, 5});
            printResult(connection);
        } finally {
            if (auditCreated) {
                drop(dataSource, AUDIT_TABLE);
            }
            if (stockCreated) {
                drop(dataSource, STOCK_TABLE);
            }
        }
    }

    private static void adjustStock(
            Connection connection, int[] itemIds, int[] changes)
            throws SQLException {
        boolean initialAutoCommit = connection.getAutoCommit();
        connection.setAutoCommit(false);
        try (PreparedStatement update = connection.prepareStatement(
                     "UPDATE " + STOCK_TABLE
                             + " SET quantity = quantity + ? "
                             + "WHERE item_id = ? AND quantity + ? >= 0");
             PreparedStatement audit = connection.prepareStatement(
                     "INSERT INTO " + AUDIT_TABLE
                             + " (audit_id, item_id, change_amount) "
                             + "VALUES (?, ?, ?)")) {
            for (int index = 0; index < itemIds.length; index++) {
                update.setInt(1, changes[index]);
                update.setInt(2, itemIds[index]);
                update.setInt(3, changes[index]);
                if (update.executeUpdate() != 1) {
                    throw new SQLException(
                            "Stock adjustment rejected for item "
                                    + itemIds[index]);
                }

                audit.setInt(1, 9001 + index);
                audit.setInt(2, itemIds[index]);
                audit.setInt(3, changes[index]);
                audit.addBatch();
            }

            int[] counts = audit.executeBatch();
            for (int count : counts) {
                if (count == Statement.EXECUTE_FAILED) {
                    throw new BatchUpdateException("Audit batch failed", counts);
                }
            }
            connection.commit();
            System.out.println("Audit batch counts: "
                    + Arrays.toString(counts));
            System.out.println("Transaction committed");
        } catch (SQLException exception) {
            try {
                connection.rollback();
            } catch (SQLException rollbackException) {
                exception.addSuppressed(rollbackException);
            }
            log(exception);
            throw exception;
        } finally {
            connection.setAutoCommit(initialAutoCommit);
        }
    }

    private static void printResult(Connection connection)
            throws SQLException {
        try (Statement statement = connection.createStatement();
             ResultSet resultSet = statement.executeQuery(
                     "SELECT item_id, quantity FROM " + STOCK_TABLE
                             + " ORDER BY item_id")) {
            while (resultSet.next()) {
                System.out.println("Stock " + resultSet.getInt(1)
                        + " = " + resultSet.getInt(2));
            }
        }
    }

    private static void log(SQLException exception) {
        for (SQLException current = exception;
             current != null; current = current.getNextException()) {
            System.err.println("SQLSTATE=" + current.getSQLState()
                    + ", errorCode=" + current.getErrorCode()
                    + ", message=" + current.getMessage());
        }
    }

    private static YasDataSource dataSource() throws SQLException {
        YasDataSource dataSource = new YasDataSource();
        dataSource.setURL(required("YASDB_URL"));
        dataSource.setUser(required("YASDB_USER"));
        dataSource.setPassword(required("YASDB_PASSWORD"));
        return dataSource;
    }

    private static void drop(YasDataSource dataSource, String table)
            throws SQLException {
        try (Connection connection = dataSource.getConnection();
             Statement statement = connection.createStatement()) {
            statement.execute("DROP TABLE " + table);
        }
    }

    private static String required(String name) {
        String value = System.getenv(name);
        if (value == null || value.isEmpty()) {
            throw new IllegalStateException(
                    "Missing environment variable: " + name);
        }
        return value;
    }
}

# 4. 验证结果

示例运行成功时,输出如下:

Audit batch counts: [1, 1]
Transaction committed
Stock 1001 = 17
Stock 1002 = 35
edit-icon
反馈
coperate-icon
coperate
合作
communication-icon
communicate
交流