如何无侵入通过JDBC获取达梦/MySQL/PgSQL数据库响应数据?

需要给现有数据库业务系统做审计(有达梦数据库、MySQL、PgSQL等主流株距看),必须无侵入(不能改业务代码、配置、依赖,业务系统对我来说是黑盒的,无法修改任何东西),通过 JDBC 层捕获所有 SQL 语句和查询响应数据(结果集)
目前是通过java agent实现无侵入增强业务系统的,但是卡在了获取响应数据

环境:

  • JDK:8;
  • 达梦数据库驱动:dm8
  • 其他数据库暂未测试

因为游标默认是无法移动的,如果我在捕获响应数据时,移动了游标,那么业务系统将无法正常获取响应数据,我在尝试复原游标时也是无法正常复原
当前卡点:在达梦数据库下,CachedRowSet.populate () 会把原始 ResultSet 的游标移到末尾,尝试用以下代码恢复游标,但是无法成功:

// 恢复游标代码
int originalRow = rs.getRow();
cachedRowSet.populate(rs);
if (originalRow > 0) {
    rs.absolute(originalRow); // 达梦驱动下偶尔报SQL异常:无效的游标位置
} else {
    rs.beforeFirst();
}

达梦或者其他数据库环境下,有没有稳定恢复 ResultSet 游标的方案?(最好有实测过的代码);
除了 CachedRowSet,有没有更轻量的无依赖方案,既能缓存结果集又不影响游标?

有没有大佬遇到过类似问题?求指点,感激不尽!🙏

阅读 962
2 个回答

项目结构

src/main/java/
  com.example.jdbcproxy/
    ProxyDriver.java
    ProxyConnection.java
    ProxyStatement.java       
    ProxyPreparedStatement.java
    ProxyResultSet.java
    Agent.java

ProxyDriver.java

package com.example.jdbcproxy;

import java.sql.*;
import java.util.Properties;
import java.util.logging.Logger;

public class ProxyDriver implements Driver {
    private final Driver target;

    public ProxyDriver(Driver target) {
        this.target = target;
    }

    @Override
    public Connection connect(String url, Properties info) throws SQLException {
        Connection conn = target.connect(url, info);
        return (conn != null) ? new ProxyConnection(conn) : null;
    }

    @Override
    public boolean acceptsURL(String url) throws SQLException {
        return target.acceptsURL(url);
    }

    @Override
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
        return target.getPropertyInfo(url, info);
    }

    @Override
    public int getMajorVersion() {
        return target.getMajorVersion();
    }

    @Override
    public int getMinorVersion() {
        return target.getMinorVersion();
    }

    @Override
    public boolean jdbcCompliant() {
        return target.jdbcCompliant();
    }

    @Override
    public Logger getParentLogger() {
        try {
            return target.getParentLogger();
        } catch (SQLFeatureNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

ProxyConnection.java

package com.example.jdbcproxy;

import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

public class ProxyConnection implements Connection {
    private final Connection target;

    public ProxyConnection(Connection target) {
        this.target = target;
    }

    @Override
    public Statement createStatement() throws SQLException {
        return new ProxyStatement(target.createStatement());
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return new ProxyPreparedStatement(target.prepareStatement(sql), sql);
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return target.prepareCall(sql);
    }

    @Override
    public String nativeSQL(String sql) throws SQLException {
        return target.nativeSQL(sql);
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        target.setAutoCommit(autoCommit);
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return target.getAutoCommit();
    }

    @Override
    public void commit() throws SQLException {
        target.commit();
    }

    @Override
    public void rollback() throws SQLException {
        target.rollback();
    }

    @Override
    public void close() throws SQLException {
        target.close();
    }

    @Override
    public boolean isClosed() throws SQLException {
        return target.isClosed();
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        return target.getMetaData();
    }

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {
        target.setReadOnly(readOnly);
    }

    @Override
    public boolean isReadOnly() throws SQLException {
        return target.isReadOnly();
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {
        target.setCatalog(catalog);
    }

    @Override
    public String getCatalog() throws SQLException {
        return target.getCatalog();
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {
        target.setTransactionIsolation(level);
    }

    @Override
    public int getTransactionIsolation() throws SQLException {
        return target.getTransactionIsolation();
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return target.getWarnings();
    }

    @Override
    public void clearWarnings() throws SQLException {
        target.clearWarnings();
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        return target.getTypeMap();
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
        target.setTypeMap(map);
    }

    @Override
    public void setHoldability(int holdability) throws SQLException {
        target.setHoldability(holdability);
    }

    @Override
    public int getHoldability() throws SQLException {
        return target.getHoldability();
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
        return target.setSavepoint();
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        return target.setSavepoint(name);
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
        target.rollback(savepoint);
    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
        target.releaseSavepoint(savepoint);
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return new ProxyStatement(target.createStatement(resultSetType, resultSetConcurrency));
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return new ProxyPreparedStatement(target.prepareStatement(sql, resultSetType, resultSetConcurrency), sql);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return target.prepareCall(sql, resultSetType, resultSetConcurrency);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        return new ProxyPreparedStatement(target.prepareStatement(sql, autoGeneratedKeys), sql);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        return new ProxyPreparedStatement(target.prepareStatement(sql, columnIndexes), sql);
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        return new ProxyPreparedStatement(target.prepareStatement(sql, columnNames), sql);
    }

    @Override
    public Clob createClob() throws SQLException {
        return target.createClob();
    }

    @Override
    public Blob createBlob() throws SQLException {
        return target.createBlob();
    }

    @Override
    public NClob createNClob() throws SQLException {
        return target.createNClob();
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        return target.createSQLXML();
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
        return target.isValid(timeout);
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {
        target.setClientInfo(name, value);
    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {
        target.setClientInfo(properties);
    }

    @Override
    public String getClientInfo(String name) throws SQLException {
        return target.getClientInfo(name);
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        return target.getClientInfo();
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        return target.createArrayOf(typeName, elements);
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        return target.createStruct(typeName, attributes);
    }

    @Override
    public void setSchema(String schema) throws SQLException {
        target.setSchema(schema);
    }

    @Override
    public String getSchema() throws SQLException {
        return target.getSchema();
    }

    @Override
    public void abort(Executor executor) throws SQLException {
        target.abort(executor);
    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
        target.setNetworkTimeout(executor, milliseconds);
    }

    @Override
    public int getNetworkTimeout() throws SQLException {
        return target.getNetworkTimeout();
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return target.unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return target.isWrapperFor(iface);
    }
}

ProxyStatement.java

package com.example.jdbcproxy;

import java.sql.*;
import java.util.List;

public class ProxyStatement implements Statement {
    private final Statement target;

    public ProxyStatement(Statement target) {
        this.target = target;
    }

    @Override
    public ResultSet executeQuery(String sql) throws SQLException {
        ResultSet rs = target.executeQuery(sql);
        return new ProxyResultSet(rs, sql);
    }

    @Override
    public int executeUpdate(String sql) throws SQLException {
        return target.executeUpdate(sql);
    }

    @Override
    public void close() throws SQLException {
        target.close();
    }

    @Override
    public int getMaxFieldSize() throws SQLException {
        return target.getMaxFieldSize();
    }

    @Override
    public void setMaxFieldSize(int max) throws SQLException {
        target.setMaxFieldSize(max);
    }

    @Override
    public int getMaxRows() throws SQLException {
        return target.getMaxRows();
    }

    @Override
    public void setMaxRows(int max) throws SQLException {
        target.setMaxRows(max);
    }

    @Override
    public void setEscapeProcessing(boolean enable) throws SQLException {
        target.setEscapeProcessing(enable);
    }

    @Override
    public int getQueryTimeout() throws SQLException {
        return target.getQueryTimeout();
    }

    @Override
    public void setQueryTimeout(int seconds) throws SQLException {
        target.setQueryTimeout(seconds);
    }

    @Override
    public void cancel() throws SQLException {
        target.cancel();
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return target.getWarnings();
    }

    @Override
    public void clearWarnings() throws SQLException {
        target.clearWarnings();
    }

    @Override
    public void setCursorName(String name) throws SQLException {
        target.setCursorName(name);
    }

    @Override
    public boolean execute(String sql) throws SQLException {
        return target.execute(sql);
    }

    @Override
    public ResultSet getResultSet() throws SQLException {
        ResultSet rs = target.getResultSet();
        return (rs != null) ? new ProxyResultSet(rs, null) : null;
    }

    @Override
    public int getUpdateCount() throws SQLException {
        return target.getUpdateCount();
    }

    @Override
    public boolean getMoreResults() throws SQLException {
        return target.getMoreResults();
    }

    @Override
    public void setFetchDirection(int direction) throws SQLException {
        target.setFetchDirection(direction);
    }

    @Override
    public int getFetchDirection() throws SQLException {
        return target.getFetchDirection();
    }

    @Override
    public void setFetchSize(int rows) throws SQLException {
        target.setFetchSize(rows);
    }

    @Override
    public int getFetchSize() throws SQLException {
        return target.getFetchSize();
    }

    @Override
    public int getResultSetConcurrency() throws SQLException {
        return target.getResultSetConcurrency();
    }

    @Override
    public int getResultSetType() throws SQLException {
        return target.getResultSetType();
    }

    @Override
    public void addBatch(String sql) throws SQLException {
        target.addBatch(sql);
    }

    @Override
    public void clearBatch() throws SQLException {
        target.clearBatch();
    }

    @Override
    public int[] executeBatch() throws SQLException {
        return target.executeBatch();
    }

    @Override
    public Connection getConnection() throws SQLException {
        return target.getConnection();
    }

    @Override
    public boolean getMoreResults(int current) throws SQLException {
        return target.getMoreResults(current);
    }

    @Override
    public ResultSet getGeneratedKeys() throws SQLException {
        return target.getGeneratedKeys();
    }

    @Override
    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
        return target.executeUpdate(sql, autoGeneratedKeys);
    }

    @Override
    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
        return target.executeUpdate(sql, columnIndexes);
    }

    @Override
    public int executeUpdate(String sql, String[] columnNames) throws SQLException {
        return target.executeUpdate(sql, columnNames);
    }

    @Override
    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
        return target.execute(sql, autoGeneratedKeys);
    }

    @Override
    public boolean execute(String sql, int[] columnIndexes) throws SQLException {
        return target.execute(sql, columnIndexes);
    }

    @Override
    public boolean execute(String sql, String[] columnNames) throws SQLException {
        return target.execute(sql, columnNames);
    }

    @Override
    public int getResultSetHoldability() throws SQLException {
        return target.getResultSetHoldability();
    }

    @Override
    public boolean isClosed() throws SQLException {
        return target.isClosed();
    }

    @Override
    public void setPoolable(boolean poolable) throws SQLException {
        target.setPoolable(poolable);
    }

    @Override
    public boolean isPoolable() throws SQLException {
        return target.isPoolable();
    }

    @Override
    public void closeOnCompletion() throws SQLException {
        target.closeOnCompletion();
    }

    @Override
    public boolean isCloseOnCompletion() throws SQLException {
        return target.isCloseOnCompletion();
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return target.unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return target.isWrapperFor(iface);
    }
}

ProxyPreparedStatement.java

package com.example.jdbcproxy;

import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.*;
import java.util.Calendar;

public class ProxyPreparedStatement implements PreparedStatement {
    private final PreparedStatement target;
    private final String sql;

    public ProxyPreparedStatement(PreparedStatement target, String sql) {
        this.target = target;
        this.sql = sql;
    }

    @Override
    public ResultSet executeQuery() throws SQLException {
        ResultSet rs = target.executeQuery();
        return new ProxyResultSet(rs, sql);
    }

    @Override
    public int executeUpdate() throws SQLException {
        return target.executeUpdate();
    }

    @Override
    public void setNull(int parameterIndex, int sqlType) throws SQLException {
        target.setNull(parameterIndex, sqlType);
    }

    @Override
    public void setBoolean(int parameterIndex, boolean x) throws SQLException {
        target.setBoolean(parameterIndex, x);
    }

    @Override
    public void setByte(int parameterIndex, byte x) throws SQLException {
        target.setByte(parameterIndex, x);
    }

    @Override
    public void setShort(int parameterIndex, short x) throws SQLException {
        target.setShort(parameterIndex, x);
    }

    @Override
    public void setInt(int parameterIndex, int x) throws SQLException {
        target.setInt(parameterIndex, x);
    }

    @Override
    public void setLong(int parameterIndex, long x) throws SQLException {
        target.setLong(parameterIndex, x);
    }

    @Override
    public void setFloat(int parameterIndex, float x) throws SQLException {
        target.setFloat(parameterIndex, x);
    }

    @Override
    public void setDouble(int parameterIndex, double x) throws SQLException {
        target.setDouble(parameterIndex, x);
    }

    @Override
    public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
        target.setBigDecimal(parameterIndex, x);
    }

    @Override
    public void setString(int parameterIndex, String x) throws SQLException {
        target.setString(parameterIndex, x);
    }

    @Override
    public void setBytes(int parameterIndex, byte[] x) throws SQLException {
        target.setBytes(parameterIndex, x);
    }

    @Override
    public void setDate(int parameterIndex, Date x) throws SQLException {
        target.setDate(parameterIndex, x);
    }

    @Override
    public void setTime(int parameterIndex, Time x) throws SQLException {
        target.setTime(parameterIndex, x);
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
        target.setTimestamp(parameterIndex, x);
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
        target.setAsciiStream(parameterIndex, x, length);
    }

    @Override
    public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
        target.setUnicodeStream(parameterIndex, x, length);
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
        target.setBinaryStream(parameterIndex, x, length);
    }

    @Override
    public void clearParameters() throws SQLException {
        target.clearParameters();
    }

    @Override
    public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
        target.setObject(parameterIndex, x, targetSqlType);
    }

    @Override
    public void setObject(int parameterIndex, Object x) throws SQLException {
        target.setObject(parameterIndex, x);
    }

    @Override
    public boolean execute() throws SQLException {
        return target.execute();
    }

    @Override
    public void addBatch() throws SQLException {
        target.addBatch();
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
        target.setCharacterStream(parameterIndex, reader, length);
    }

    @Override
    public void setRef(int parameterIndex, Ref x) throws SQLException {
        target.setRef(parameterIndex, x);
    }

    @Override
    public void setBlob(int parameterIndex, Blob x) throws SQLException {
        target.setBlob(parameterIndex, x);
    }

    @Override
    public void setClob(int parameterIndex, Clob x) throws SQLException {
        target.setClob(parameterIndex, x);
    }

    @Override
    public void setArray(int parameterIndex, Array x) throws SQLException {
        target.setArray(parameterIndex, x);
    }

    @Override
    public ResultSetMetaData getMetaData() throws SQLException {
        return target.getMetaData();
    }

    @Override
    public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
        target.setDate(parameterIndex, x, cal);
    }

    @Override
    public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
        target.setTime(parameterIndex, x, cal);
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
        target.setTimestamp(parameterIndex, x, cal);
    }

    @Override
    public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
        target.setNull(parameterIndex, sqlType, typeName);
    }

    @Override
    public void setURL(int parameterIndex, URL x) throws SQLException {
        target.setURL(parameterIndex, x);
    }

    @Override
    public ParameterMetaData getParameterMetaData() throws SQLException {
        return target.getParameterMetaData();
    }

    @Override
    public void setRowId(int parameterIndex, RowId x) throws SQLException {
        target.setRowId(parameterIndex, x);
    }

    @Override
    public void setNString(int parameterIndex, String value) throws SQLException {
        target.setNString(parameterIndex, value);
    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
        target.setNCharacterStream(parameterIndex, value, length);
    }

    @Override
    public void setNClob(int parameterIndex, NClob value) throws SQLException {
        target.setNClob(parameterIndex, value);
    }

    @Override
    public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
        target.setClob(parameterIndex, reader, length);
    }

    @Override
    public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
        target.setBlob(parameterIndex, inputStream, length);
    }

    @Override
    public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
        target.setNClob(parameterIndex, reader, length);
    }

    @Override
    public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
        target.setSQLXML(parameterIndex, xmlObject);
    }

    @Override
    public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
        target.setObject(parameterIndex, x, targetSqlType, scaleOrLength);
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
        target.setAsciiStream(parameterIndex, x, length);
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
        target.setBinaryStream(parameterIndex, x, length);
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
        target.setCharacterStream(parameterIndex, reader, length);
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
        target.setAsciiStream(parameterIndex, x);
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
        target.setBinaryStream(parameterIndex, x);
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
        target.setCharacterStream(parameterIndex, reader);
    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
        target.setNCharacterStream(parameterIndex, value);
    }

    @Override
    public void setClob(int parameterIndex, Reader reader) throws SQLException {
        target.setClob(parameterIndex, reader);
    }

    @Override
    public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
        target.setBlob(parameterIndex, inputStream);
    }

    @Override
    public void setNClob(int parameterIndex, Reader reader) throws SQLException {
        target.setNClob(parameterIndex, reader);
    }

    @Override
    public void close() throws SQLException {
        target.close();
    }

    @Override
    public int getMaxFieldSize() throws SQLException {
        return target.getMaxFieldSize();
    }

    @Override
    public void setMaxFieldSize(int max) throws SQLException {
        target.setMaxFieldSize(max);
    }

    @Override
    public int getMaxRows() throws SQLException {
        return target.getMaxRows();
    }

    @Override
    public void setMaxRows(int max) throws SQLException {
        target.setMaxRows(max);
    }

    @Override
    public void setEscapeProcessing(boolean enable) throws SQLException {
        target.setEscapeProcessing(enable);
    }

    @Override
    public int getQueryTimeout() throws SQLException {
        return target.getQueryTimeout();
    }

    @Override
    public void setQueryTimeout(int seconds) throws SQLException {
        target.setQueryTimeout(seconds);
    }

    @Override
    public void cancel() throws SQLException {
        target.cancel();
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return target.getWarnings();
    }

    @Override
    public void clearWarnings() throws SQLException {
        target.clearWarnings();
    }

    @Override
    public void setCursorName(String name) throws SQLException {
        target.setCursorName(name);
    }

    @Override
    public boolean execute(String sql) throws SQLException {
        return target.execute(sql);
    }

    @Override
    public ResultSet getResultSet() throws SQLException {
        ResultSet rs = target.getResultSet();
        return (rs != null) ? new ProxyResultSet(rs, sql) : null;
    }

    @Override
    public int getUpdateCount() throws SQLException {
        return target.getUpdateCount();
    }

    @Override
    public boolean getMoreResults() throws SQLException {
        return target.getMoreResults();
    }

    @Override
    public void setFetchDirection(int direction) throws SQLException {
        target.setFetchDirection(direction);
    }

    @Override
    public int getFetchDirection() throws SQLException {
        return target.getFetchDirection();
    }

    @Override
    public void setFetchSize(int rows) throws SQLException {
        target.setFetchSize(rows);
    }

    @Override
    public int getFetchSize() throws SQLException {
        return target.getFetchSize();
    }

    @Override
    public int getResultSetConcurrency() throws SQLException {
        return target.getResultSetConcurrency();
    }

    @Override
    public int getResultSetType() throws SQLException {
        return target.getResultSetType();
    }

    @Override
    public void addBatch(String sql) throws SQLException {
        target.addBatch(sql);
    }

    @Override
    public void clearBatch() throws SQLException {
        target.clearBatch();
    }

    @Override
    public int[] executeBatch() throws SQLException {
        return target.executeBatch();
    }

    @Override
    public Connection getConnection() throws SQLException {
        return target.getConnection();
    }

    @Override
    public boolean getMoreResults(int current) throws SQLException {
        return target.getMoreResults(current);
    }

    @Override
    public ResultSet getGeneratedKeys() throws SQLException {
        return target.getGeneratedKeys();
    }

    @Override
    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
        return target.executeUpdate(sql, autoGeneratedKeys);
    }

    @Override
    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
        return target.executeUpdate(sql, columnIndexes);
    }

    @Override
    public int executeUpdate(String sql, String[] columnNames) throws SQLException {
        return target.executeUpdate(sql, columnNames);
    }

    @Override
    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
        return target.execute(sql, autoGeneratedKeys);
    }

    @Override
    public boolean execute(String sql, int[] columnIndexes) throws SQLException {
        return target.execute(sql, columnIndexes);
    }

    @Override
    public boolean execute(String sql, String[] columnNames) throws SQLException {
        return target.execute(sql, columnNames);
    }

    @Override
    public int getResultSetHoldability() throws SQLException {
        return target.getResultSetHoldability();
    }

    @Override
    public boolean isClosed() throws SQLException {
        return target.isClosed();
    }

    @Override
    public void setPoolable(boolean poolable) throws SQLException {
        target.setPoolable(poolable);
    }

    @Override
    public boolean isPoolable() throws SQLException {
        return target.isPoolable();
    }

    @Override
    public void closeOnCompletion() throws SQLException {
        target.closeOnCompletion();
    }

    @Override
    public boolean isCloseOnCompletion() throws SQLException {
        return target.isCloseOnCompletion();
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return target.unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return target.isWrapperFor(iface);
    }
}

ProxyResultSet.java

package com.example.jdbcproxy;

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

public class ProxyResultSet implements ResultSet {
    private final ResultSet target;
    private final String sql;
    private final List<Map<String, Object>> auditCache = new ArrayList<>();

    public ProxyResultSet(ResultSet target, String sql) {
        this.target = target;
        this.sql = sql;
    }

    @Override
    public boolean next() throws SQLException {
        boolean hasNext = target.next();
        if (hasNext) {
            ResultSetMetaData meta = target.getMetaData();
            int columnCount = meta.getColumnCount();
            Map<String, Object> row = new LinkedHashMap<>();
            for (int i = 1; i <= columnCount; i++) {
                row.put(meta.getColumnLabel(i), target.getObject(i));
            }
            auditCache.add(row);
        }
        return hasNext;
    }

    /** 获取审计数据 */
    public List<Map<String, Object>> getAuditData() {
        return Collections.unmodifiableList(auditCache);
    }

    /** 获取对应 SQL */
    public String getSql() {
        return sql;
    }

    // ================== 以下全部委托给 target ==================

    @Override
    public void close() throws SQLException { target.close(); }
    @Override
    public boolean wasNull() throws SQLException { return target.wasNull(); }
    @Override
    public String getString(int columnIndex) throws SQLException { return target.getString(columnIndex); }
    @Override
    public boolean getBoolean(int columnIndex) throws SQLException { return target.getBoolean(columnIndex); }
    @Override
    public byte getByte(int columnIndex) throws SQLException { return target.getByte(columnIndex); }
    @Override
    public short getShort(int columnIndex) throws SQLException { return target.getShort(columnIndex); }
    @Override
    public int getInt(int columnIndex) throws SQLException { return target.getInt(columnIndex); }
    @Override
    public long getLong(int columnIndex) throws SQLException { return target.getLong(columnIndex); }
    @Override
    public float getFloat(int columnIndex) throws SQLException { return target.getFloat(columnIndex); }
    @Override
    public double getDouble(int columnIndex) throws SQLException { return target.getDouble(columnIndex); }
    @Override
    public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { return target.getBigDecimal(columnIndex, scale); }
    @Override
    public byte[] getBytes(int columnIndex) throws SQLException { return target.getBytes(columnIndex); }
    @Override
    public Date getDate(int columnIndex) throws SQLException { return target.getDate(columnIndex); }
    @Override
    public Time getTime(int columnIndex) throws SQLException { return target.getTime(columnIndex); }
    @Override
    public Timestamp getTimestamp(int columnIndex) throws SQLException { return target.getTimestamp(columnIndex); }
    @Override
    public InputStream getAsciiStream(int columnIndex) throws SQLException { return target.getAsciiStream(columnIndex); }
    @Override
    public InputStream getUnicodeStream(int columnIndex) throws SQLException { return target.getUnicodeStream(columnIndex); }
    @Override
    public InputStream getBinaryStream(int columnIndex) throws SQLException { return target.getBinaryStream(columnIndex); }
    @Override
    public String getString(String columnLabel) throws SQLException { return target.getString(columnLabel); }
    @Override
    public boolean getBoolean(String columnLabel) throws SQLException { return target.getBoolean(columnLabel); }
    @Override
    public byte getByte(String columnLabel) throws SQLException { return target.getByte(columnLabel); }
    @Override
    public short getShort(String columnLabel) throws SQLException { return target.getShort(columnLabel); }
    @Override
    public int getInt(String columnLabel) throws SQLException { return target.getInt(columnLabel); }
    @Override
    public long getLong(String columnLabel) throws SQLException { return target.getLong(columnLabel); }
    @Override
    public float getFloat(String columnLabel) throws SQLException { return target.getFloat(columnLabel); }
    @Override
    public double getDouble(String columnLabel) throws SQLException { return target.getDouble(columnLabel); }
    @Override
    public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException { return target.getBigDecimal(columnLabel, scale); }
    @Override
    public byte[] getBytes(String columnLabel) throws SQLException { return target.getBytes(columnLabel); }
    @Override
    public Date getDate(String columnLabel) throws SQLException { return target.getDate(columnLabel); }
    @Override
    public Time getTime(String columnLabel) throws SQLException { return target.getTime(columnLabel); }
    @Override
    public Timestamp getTimestamp(String columnLabel) throws SQLException { return target.getTimestamp(columnLabel); }
    @Override
    public InputStream getAsciiStream(String columnLabel) throws SQLException { return target.getAsciiStream(columnLabel); }
    @Override
    public InputStream getUnicodeStream(String columnLabel) throws SQLException { return target.getUnicodeStream(columnLabel); }
    @Override
    public InputStream getBinaryStream(String columnLabel) throws SQLException { return target.getBinaryStream(columnLabel); }

    // ... 继续委托所有 JDBC ResultSet 方法(updateXXX、getObject、findColumn、getMetaData、etc.)
    // 由于 ResultSet 方法非常多,这里保持完整性:每个方法都直接调用 target 对应方法。

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException { return target.unwrap(iface); }
    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException { return target.isWrapperFor(iface); }
}

Agent.java

package com.example.jdbcproxy;

import java.lang.instrument.Instrumentation;
import java.sql.Driver;
import java.sql.DriverManager;
import java.util.Enumeration;

public class Agent {
    /**
     * premain 方法在 JVM 启动时执行,用于注册代理 Driver
     */
    public static void premain(String agentArgs, Instrumentation inst) throws Exception {
        System.out.println("[JDBC Proxy Agent] Starting...");

        // 获取当前已注册的所有 JDBC Driver
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver originalDriver = drivers.nextElement();

            // 注销原始 Driver
            DriverManager.deregisterDriver(originalDriver);

            // 注册代理 Driver
            ProxyDriver proxyDriver = new ProxyDriver(originalDriver);
            DriverManager.registerDriver(proxyDriver);

            System.out.println("[JDBC Proxy Agent] Registered proxy for driver: " + originalDriver.getClass().getName());
        }

        System.out.println("[JDBC Proxy Agent] Initialization complete.");
    }
}

功能说明

  • 无侵入:不修改业务代码,只需在 JVM 启动时挂载 Agent。
  • 工作原理:

    • premain() 会在应用启动前执行。
    • 遍历 DriverManager 已注册的所有 JDBC 驱动。
    • 注销原始驱动,注册我们的 ProxyDriver,它会返回代理 Connection,进而代理 Statement、PreparedStatement、ResultSet。

通过Gemini pro 3给了一版实现,ai直接否定了手动触发next方法,而是去监听getObject/getString这些方法,这样就不会出现手动移动游标的问题了,缺点就是如果你需要监控同一行的数据,自己得加额外的逻辑了
image.png

推荐问题