当前位置: 首页 > news >正文

Oracle-OracleConnection

提示:OracleConnection 主要负责与Oracle数据库的交互,特别针对CDC功能,提供了获取和处理数据库更改日志的能力,同时包含数据库连接管理、查询执行和结果处理的通用功能,与DB2Connection作用相似

文章目录

  • 前言
  • 一、核心功能
  • 二、代码分析
  • 总结

前言

提示:OracleConnection 类旨在简化与 Oracle 数据库的交互,提供了一套全面的数据库操作接口,特别是针对需要利用 Oracle的变更数据捕获能力的场景。通过这个类,开发者可以更方便地执行数据库操作,而无需直接处理复杂的 JDBC 连接和查询细节。


提示:以下是本篇文章正文内容

一、核心功能

核心功能详细说明

  1. 查询字符集 (getNationalCharacterSet 方法):

    • 功能: 查询 Oracle 数据库的 NLS_NCHAR_CHARACTERSET 参数设置。
    • 作用: 确定数据库使用的国家字符集。
    • 与 Debezium 的关联: 这对于确保 Debezium 正确地解析和处理字符串数据至关重要,特别是在处理多语言环境下的文本数据时。
  2. 清除日志文件 (removeAllLogFilesFromLogMinerSession 方法):

    • 功能: 从 LogMiner 会话中移除所有已注册的日志文件。
    • 作用: 维护 LogMiner 会话,通过移除已注册的日志文件。
    • 与 Debezium 的关联: 这对于维护 LogMiner 会话和确保 Debezium 能够正确跟踪数据库更改至关重要。LogMiner 是 Oracle 提供的一种机制,用于从重做日志中提取更改记录。
  3. 获取重做线程状态 (getRedoThreadState 方法):

    • 功能: 获取 Oracle 数据库的重做线程状态。
    • 作用: 监控重做线程的状态,这对于数据库复制和日志分析非常重要。
    • 与 Debezium 的关联: 这对于监控重做线程的状态,确保 Debezium 能够正确捕捉数据库更改事件至关重要。重做线程状态反映了数据库内部的活动情况,这对于 CDC 功能的稳定性和准确性至关重要。
  4. 获取 SQL 关键字 (getSQLKeywords 方法):

    • 功能: 从 JDBC 驱动程序获取支持的 SQL 关键字列表。
    • 作用: 获取数据库支持的关键字列表,帮助构建 SQL 查询时避免语法错误。
    • 与 Debezium 的关联: 这对于构建 SQL 查询时避免语法错误,确保 Debezium 正确地处理 SQL 语句至关重要。这对于处理复杂的数据库结构和优化查询性能很有帮助。

二、代码分析
 

// 定义一个方法,用于将当前会话切换到指定的 PDB
public void setSessionToPdb(String pdbName) {// 声明 Statement 变量,初始值为 nullStatement statement = null;// 尝试块,用于执行可能抛出异常的操作try {// 通过当前连接创建 Statement 对象statement = connection().createStatement();// 执行 SQL 语句,将当前会话的容器设置为指定的 PDB 名称statement.execute("alter session set container=" + pdbName);}// 捕获块,用于处理 SQLException 异常catch (SQLException e) {// 抛出一个新的 RuntimeException,将原始的 SQLException 作为其原因throw new RuntimeException(e);}// finally 块,用于执行无论 try 块是否成功都会执行的操作finally {// 检查 Statement 是否不为 nullif (statement != null) {// 尝试关闭 Statementtry {statement.close();}// 捕获块,用于处理关闭 Statement 时可能发生的 SQLException 异常catch (SQLException e) {// 输出错误日志,记录无法关闭 Statement 的异常LOGGER.error("Couldn't close statement", e);}}}
}

这个方法的作用是将当前会话切换到指定的 PDB。在 Oracle 多租户环境中,一个数据库容器可以包含多个 PDB。通过执行 "alter session set container=" + pdbName 语句,可以将当前会话的上下文切换到指定的 PDB,从而允许后续的数据库操作针对该 PDB 进行。
这种方法对于需要在不同的 PDB 之间切换执行数据库操作的场景非常有用,尤其是在使用 Debezium 这样的工具时,可能需要针对不同的 PDB 捕获变更数据 

// 定义一个方法,用于将当前会话切换回 CDB 的根容器 cdb$root
public void resetSessionToCdb() {// 声明 Statement 变量,初始值为 nullStatement statement = null;// 尝试块,用于执行可能抛出异常的操作try {// 通过当前连接创建 Statement 对象statement = connection().createStatement();// 执行 SQL 语句,将当前会话的容器设置为 cdb$rootstatement.execute("alter session set container=cdb$root");}// 捕获块,用于处理 SQLException 异常catch (SQLException e) {// 抛出一个新的 RuntimeException,将原始的 SQLException 作为其原因throw new RuntimeException(e);}// finally 块,用于执行无论 try 块是否成功都会执行的操作finally {// 检查 Statement 是否不为 nullif (statement != null) {// 尝试关闭 Statementtry {statement.close();}// 捕获块,用于处理关闭 Statement 时可能发生的 SQLException 异常catch (SQLException e) {// 输出错误日志,记录无法关闭 Statement 的异常LOGGER.error("Couldn't close statement", e);}}}
}

方法作用

这个方法的作用是将当前会话切换回 CDB 的根容器 cdb$root。在 Oracle 多租户环境中,一个 CDB 包含一个根容器 cdb$root 和一个或多个可插拔数据库 (PDBs)。通过执行 "alter session set container=cdb$root" 语句,可以将当前会话的上下文切换回 CDB 的根容器,从而允许后续的数据库操作针对整个 CDB 进行。

这种方法对于需要在不同的 PDB 之间切换执行数据库操作后回到 CDB 根容器的场景非常有用,尤其是在使用 Debezium 这样的工具时,可能需要针对整个 CDB 进行某些操作或配置。

     * 获取所有表的TableId集合* * @param catalogName 目录名称* @return 表的TableId集合* @throws SQLException 如果发生数据库异常*/protected Set<TableId> getAllTableIds(String catalogName) throws SQLException {// SQL查询语句,从all_tables表中获取owner和table_name,排除特定的表和索引组织表final String query = "select owner, table_name from all_tables " +"where table_name NOT LIKE 'MDRT_%' " +"and table_name NOT LIKE 'MDRS_%' " +"and table_name NOT LIKE 'MDXT_%' " +"and (table_name NOT LIKE 'SYS_IOT_OVER_%' and IOT_NAME IS NULL) " +"and nested = 'NO'" +"and table_name not in (select PARENT_TABLE_NAME from ALL_NESTED_TABLES)";// 使用HashSet存储查询到的TableIdSet<TableId> tableIds = new HashSet<>();// 执行查询并处理结果集query(query, (rs) -> {while (rs.next()) {// 将查询到的owner和table_name封装成TableId对象,添加到集合中tableIds.add(new TableId(catalogName, rs.getString(1), rs.getString(2)));}// 记录日志,输出查询到的TableIdsLOGGER.trace("TableIds are: {}", tableIds);});// 返回TableId集合return tableIds;}/*** 解析并返回数据库的目录名称* * @param catalogName 目录名称* @return 解析后的目录名称*/@Overrideprotected String resolveCatalogName(String catalogName) {// 从配置中获取pdb名称,如果不存在则使用数据库名称,并转换为大写final String pdbName = config().getString("pdb.name");return (!Strings.isNullOrEmpty(pdbName) ? pdbName : config().getString("dbname")).toUpperCase();}/*** 读取表的唯一索引列表* * @param metadata 数据库元数据* @param id 表的标识符* @return 唯一索引列表* @throws SQLException 如果发生数据库异常*/@Overridepublic List<String> readTableUniqueIndices(DatabaseMetaData metadata, TableId id) throws SQLException {// 调用父类方法,使用双引号包装表标识符return super.readTableUniqueIndices(metadata, id.toDoubleQuoted());}/*** 获取当前时间戳* * @return 当前时间戳* @throws SQLException 如果发生数据库异常*/@Overridepublic Optional<Instant> getCurrentTimestamp() throws SQLException {// 执行SQL查询,返回当前时间戳return queryAndMap("SELECT CURRENT_TIMESTAMP FROM DUAL",rs -> rs.next() ? Optional.of(rs.getTimestamp(1).toInstant()) : Optional.empty());}/*** 判断索引列是否包含在表的唯一索引中* * @param indexName 索引名称* @param columnName 列名称* @return 是否包含在唯一索引中*/@Overrideprotected boolean isTableUniqueIndexIncluded(String indexName, String columnName) {// 如果列名称不为空,且不匹配任何系统列名称模式,则返回trueif (columnName != null) {return !SYS_NC_PATTERN.matcher(columnName).matches()&& !ADT_INDEX_NAMES_PATTERN.matcher(columnName).matches()&& !MROW_PATTERN.matcher(columnName).matches();}// 列名称为空时,返回falsereturn false;}/*** 获取当前的系统更改编号(SCN)* * @return 当前的系统更改编号* @throws SQLException 如果发生数据库异常* @throws IllegalStateException 如果查询未返回至少一行数据*/public Scn getCurrentScn() throws SQLException {// 执行SQL查询并映射结果return queryAndMap("SELECT CURRENT_SCN FROM V$DATABASE", (rs) -> {if (rs.next()) {return Scn.valueOf(rs.getString(1));}// 如果未获取到SCN,抛出异常throw new IllegalStateException("Could not get SCN");});}/*** 生成给定表的DDL元数据* * @param tableId 表标识符,不应为null* @return 生成的DDL* @throws SQLException 如果获取DDL元数据时发生异常* @throws NonRelationalTableException 表不是关系表*/public String getTableMetadataDdl(TableId tableId) throws SQLException, NonRelationalTableException {try {// 查询ALL_ALL_TABLES表,确认表是关系表final String tableType = "SELECT COUNT(1) FROM ALL_ALL_TABLES WHERE OWNER=? AND TABLE_NAME=? AND TABLE_TYPE IS NULL";// 如果查询结果为0,抛出异常if (prepareQueryAndMap(tableType,ps -> {ps.setString(1, tableId.schema());ps.setString(2, tableId.table());},rs -> rs.next() ? rs.getInt(1) : 0) == 0) {throw new NonRelationalTableException("Table " + tableId + " is not a relational table");}// 设置DDL转换参数,排除存储和段属性executeWithoutCommitting("begin dbms_metadata.set_transform_param(DBMS_METADATA.SESSION_TRANSFORM, 'STORAGE', false); end;");executeWithoutCommitting("begin dbms_metadata.set_transform_param(DBMS_METADATA.SESSION_TRANSFORM, 'SEGMENT_ATTRIBUTES', false); end;");// 设置DDL转换参数,启用SQL终止符,以便在返回多个DDL语句时能够分别解析executeWithoutCommitting("begin dbms_metadata.set_transform_param(DBMS_METADATA.SESSION_TRANSFORM, 'SQLTERMINATOR', true); end;");// 执行查询,返回表的DDLreturn prepareQueryAndMap("SELECT dbms_metadata.get_ddl('TABLE',?,?) FROM DUAL",ps -> {ps.setString(1, tableId.table());ps.setString(2, tableId.schema());},rs -> {if (!rs.next()) {throw new DebeziumException("Could not get DDL metadata for table: " + tableId);}Object res = rs.getObject(1);// 返回DDL字符串return ((Clob) res).getSubString(1, (int) ((Clob) res).length());});}finally {// 重置DDL转换参数为默认值executeWithoutCommitting("begin dbms_metadata.set_transform_param(DBMS_METADATA.SESSION_TRANSFORM, 'DEFAULT'); end;");}}
    /*** 检查指定表是否存在。** @param tableId 表标识符,不应为空* @return 如果表存在则返回true,否则返回false* @throws SQLException 如果发生数据库异常*/public boolean isTableExists(TableId tableId) throws SQLException {if (Strings.isNullOrBlank(tableId.schema())) {return prepareQueryAndMap("SELECT COUNT(1) FROM USER_TABLES WHERE TABLE_NAME=?",ps -> ps.setString(1, tableId.table()),rs -> rs.next() && rs.getLong(1) > 0);}return prepareQueryAndMap("SELECT COUNT(1) FROM ALL_TABLES WHERE OWNER=? AND TABLE_NAME=?",ps -> {ps.setString(1, tableId.schema());ps.setString(2, tableId.table());},rs -> rs.next() && rs.getLong(1) > 0);}/*** 判断给定表是否为空。** @param tableId 表标识符,不应为空* @return 如果表没有记录则返回true,否则返回false* @throws SQLException 如果发生数据库异常*/public boolean isTableEmpty(TableId tableId) throws SQLException {return getRowCount(tableId) == 0L;}/*** 获取给定表中的行数。** @param tableId 表标识符,不应为空* @return 表中的行数* @throws SQLException 如果发生数据库异常*/public long getRowCount(TableId tableId) throws SQLException {return queryAndMap("SELECT COUNT(1) FROM " + tableId.toDoubleQuotedString(), rs -> {if (rs.next()) {return rs.getLong(1);}return 0L;});}/*** 执行查询并获取单个可选结果值。** @param <T> 结果类型* @param query 查询语句* @param extractor 结果集提取器* @return 查询结果或null* @throws SQLException 如果发生数据库异常*/public <T> T singleOptionalValue(String query, ResultSetExtractor<T> extractor) throws SQLException {return queryAndMap(query, rs -> rs.next() ? extractor.apply(rs) : null);}/*** 获取归档和重做日志中的第一个系统更改编号(SCN)。** @param archiveLogRetention 归档日志保留时间* @param archiveDestinationName 归档日志目的地名称* @return 最旧的系统更改编号(SCN)* @throws SQLException 如果发生数据库异常* @throws DebeziumException 如果由于没有可用的日志而无法找到最旧的系统更改编号*/public Optional<Scn> getFirstScnInLogs(Duration archiveLogRetention, String archiveDestinationName) throws SQLException {final String oldestFirstChangeQuery = SqlUtils.oldestFirstChangeQuery(archiveLogRetention, archiveDestinationName);final String oldestScn = singleOptionalValue(oldestFirstChangeQuery, rs -> rs.getString(1));if (oldestScn == null) {return Optional.empty();}LOGGER.trace("最旧的SCN在日志中是 '{}'", oldestScn);return Optional.of(Scn.valueOf(oldestScn));}/*** 验证日志位置是否有效。** @param partition 分区信息* @param offset 偏移量上下文* @param config 连接器配置* @return 如果日志位置有效则返回true,否则返回false*/public boolean validateLogPosition(Partition partition, OffsetContext offset, CommonConnectorConfig config) {final Duration archiveLogRetention = ((OracleConnectorConfig) config).getArchiveLogRetention();final String archiveDestinationName = ((OracleConnectorConfig) config).getArchiveLogDestinationName();final Scn storedOffset = ((OracleConnectorConfig) config).getAdapter().getOffsetScn((OracleOffsetContext) offset);try {Optional<Scn> firstAvailableScn = getFirstScnInLogs(archiveLogRetention, archiveDestinationName);return firstAvailableScn.filter(isLessThan(storedOffset)).isPresent();}catch (SQLException e) {throw new DebeziumException("无法获取最新的可用日志位置", e);}}/*** 创建一个判断SCN是否小于存储的SCN的谓词。** @param storedOffset 存储的SCN* @return 谓词*/private static Predicate<Scn> isLessThan(Scn storedOffset) {return scn -> scn.compareTo(storedOffset) < 0;}/*** 构建带有行限制的SQL查询语句。** @param tableId 表标识符* @param limit 查询结果的最大行数* @param projection 投影列* @param condition 条件表达式* @param additionalCondition 额外条件表达式* @param orderBy 排序依据* @return SQL查询字符串*/@Overridepublic String buildSelectWithRowLimits(TableId tableId,int limit,String projection,Optional<String> condition,Optional<String> additionalCondition,String orderBy) {final TableId table = new TableId(null, tableId.schema(), tableId.table());final StringBuilder sql = new StringBuilder("SELECT ");sql.append(projection).append(" FROM ");sql.append(quotedTableIdString(table));if (condition.isPresent()) {sql.append(" WHERE ").append(condition.get());if (additionalCondition.isPresent()) {sql.append(" AND ");sql.append(additionalCondition.get());}}else if (additionalCondition.isPresent()) {sql.append(" WHERE ");sql.append(additionalCondition.get());}if (getOracleVersion().getMajor() < 12) {sql.insert(0, " SELECT * FROM (").append(" ORDER BY ").append(orderBy).append(")").append(" WHERE ROWNUM <=").append(limit);}else {sql.append(" ORDER BY ").append(orderBy).append(" FETCH NEXT ").append(limit).append(" ROWS ONLY");}return sql.toString();}
    /*** 检查数据库是否处于归档日志模式。* * @return 如果数据库处于归档日志模式,则返回true;否则返回false。*/protected boolean isArchiveLogMode() {try {final String mode = queryAndMap("SELECT LOG_MODE FROM V$DATABASE", rs -> rs.next() ? rs.getString(1) : "");LOGGER.debug("LOG_MODE={}", mode);return "ARCHIVELOG".equalsIgnoreCase(mode);}catch (SQLException e) {throw new DebeziumException("Unexpected error while connecting to Oracle and looking at LOG_MODE mode: ", e);}}/*** 将系统改变编号(SCN)解析为时间戳,返回值处于数据库时区。* * SCN到时间戳的映射仅在闪回查询区域期间保留。这意味着最终这些值之间的映射不再由Oracle保持,* 使用一个已经过期的SCN值进行调用将导致ORA-08181错误。此函数显式检查此用例,如果抛出ORA-08181错误,* 则被视为不存在该值,返回一个空的可选值。* * @param scn 系统改变编号,不得为null* @return 一个可选的时间戳,表示系统改变编号发生的时间* @throws SQLException 如果发生数据库异常*/public Optional<Instant> getScnToTimestamp(Scn scn) throws SQLException {try {return queryAndMap("SELECT scn_to_timestamp('" + scn + "') FROM DUAL", rs -> rs.next()? Optional.of(rs.getTimestamp(1).toInstant()): Optional.empty());}catch (SQLException e) {if (e.getMessage().startsWith("ORA-08181")) {// ORA-08181 specified number is not a valid system change number// This happens when the SCN provided is outside the flashback area range// This should be treated as a value is not available rather than an errorreturn Optional.empty();}// Any other SQLException should be thrownthrow e;}}/*** 根据时间调整SCN。* * @param scn 原始SCN值* @param adjustment 要应用的时间调整(正或负)* @return 调整后的SCN值* @throws SQLException 如果发生数据库异常且无法计算调整后的SCN*/public Scn getScnAdjustedByTime(Scn scn, Duration adjustment) throws SQLException {try {final String result = prepareQueryAndMap("SELECT timestamp_to_scn(scn_to_timestamp(?) - (? / 86400000)) FROM DUAL",st -> {st.setString(1, scn.toString());st.setLong(2, adjustment.toMillis());},singleResultMapper(rs -> rs.getString(1), "Failed to get adjusted SCN from: " + scn));return Scn.valueOf(result);}catch (SQLException e) {if (e.getErrorCode() == 8181 || e.getErrorCode() == 8180) {// This happens when the SCN provided is outside the flashback/undo areareturn Scn.NULL;}throw e;}}/*** 检查指定的归档日志目标是否有效。* * @param archiveDestinationName 归档日志目标名称* @return 如果目标有效返回true,否则返回false* @throws SQLException 如果无法连接到数据库或目标名称无效*/public boolean isArchiveLogDestinationValid(String archiveDestinationName) throws SQLException {return prepareQueryAndMap("SELECT STATUS, TYPE FROM V$ARCHIVE_DEST_STATUS WHERE DEST_NAME=?",st -> st.setString(1, archiveDestinationName),rs -> {if (!rs.next()) {throw new DebeziumException(String.format("Archive log destination name '%s' is unknown to Oracle",archiveDestinationName));}return "VALID".equals(rs.getString("STATUS")) && "LOCAL".equals(rs.getString("TYPE"));});}/*** 检查是否只有一个归档日志目标有效。* * @return 如果只有一个归档日志目标有效,则返回true;否则返回false。* @throws SQLException 如果无法确定归档日志目标的数量*/public boolean isOnlyOneArchiveLogDestinationValid() throws SQLException {return queryAndMap("SELECT COUNT(1) FROM V$ARCHIVE_DEST_STATUS WHERE STATUS='VALID' AND TYPE='LOCAL'",rs -> {if (!rs.next()) {throw new DebeziumException("Unable to resolve number of archive log destinations");}return rs.getLong(1) == 1L;});}/*** 重写列编辑器,以在解析默认值之前调整列状态。* * 这允许在解析默认值之前覆盖列状态,从而使默认值的输出与列值具有相同的精度。* * @param column 待重写的列编辑器* @return 调整后的列编辑器*/@Overrideprotected ColumnEditor overrideColumn(ColumnEditor column) {// This allows the column state to be overridden before default-value resolution so that the// output of the default value is within the same precision as that of the column values.if (OracleTypes.TIMESTAMP == column.jdbcType()) {column.length(column.scale().orElse(Column.UNSET_INT_VALUE)).scale(null);}else if (OracleTypes.NUMBER == column.jdbcType()) {column.scale().filter(s -> s == ORACLE_UNSET_SCALE).ifPresent(s -> column.scale(null));}return column;}
     * 按需懒查询并缓存该值。** <a href="https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html#GUID-FE15E51B-52C6-45D7-9883-4DF47716A17D">NCHAR</a>* <a href="https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html#GUID-CC15FC97-BE94-4FA4-994A-6DDF7F1A9904">NVARCHAR2</a>** @return 字符集,只能是 {@code AL16UTF16} 或 {@code UTF8}。*/public CharacterSet getNationalCharacterSet() {final String query = "select VALUE from NLS_DATABASE_PARAMETERS where PARAMETER = 'NLS_NCHAR_CHARACTERSET'";try {final String nlsCharacterSet = queryAndMap(query, rs -> {if (rs.next()) {return rs.getString(1);}return null;});if (nlsCharacterSet != null) {switch (nlsCharacterSet) {case "AL16UTF16":return CharacterSet.make(CharacterSet.AL16UTF16_CHARSET);case "UTF8":return CharacterSet.make(CharacterSet.UTF8_CHARSET);}}throw new SQLException("检测到意外的 NLS_NCHAR_CHARACTERSET: " + nlsCharacterSet);}catch (SQLException e) {throw new DebeziumException("无法解析 Oracle 的 NLS_NCHAR_CHARACTERSET 属性", e);}}public void removeAllLogFilesFromLogMinerSession() throws SQLException {final Set<String> fileNames = queryAndMap("SELECT FILENAME AS NAME FROM V$LOGMNR_LOGS", rs -> {final Set<String> results = new HashSet<>();while (rs.next()) {results.add(rs.getString(1));}return results;});for (String fileName : fileNames) {LOGGER.debug("从 LogMiner 会话中移除文件 {}。", fileName);final String sql = "BEGIN SYS.DBMS_LOGMNR.REMOVE_LOGFILE(LOGFILENAME => '" + fileName + "');END;";try (CallableStatement statement = connection(false).prepareCall(sql)) {statement.execute();}}}public RedoThreadState getRedoThreadState() throws SQLException {final String query = "SELECT * FROM V$THREAD";try {return queryAndMap(query, rs -> {RedoThreadState.Builder builder = RedoThreadState.builder();while (rs.next()) {// 尽管这个字段实际上不应该为 NULL,但数据库元数据允许这样做final int threadId = rs.getInt("THREAD#");if (!rs.wasNull()) {RedoThreadState.RedoThread.Builder threadBuilder = builder.thread().threadId(threadId).status(rs.getString("STATUS")).enabled(rs.getString("ENABLED")).logGroups(rs.getLong("GROUPS")).instanceName(rs.getString("INSTANCE")).openTime(readTimestampAsInstant(rs, "OPEN_TIME")).currentGroupNumber(rs.getLong("CURRENT_GROUP#")).currentSequenceNumber(rs.getLong("SEQUENCE#")).checkpointScn(readScnColumnAsScn(rs, "CHECKPOINT_CHANGE#")).checkpointTime(readTimestampAsInstant(rs, "CHECKPOINT_TIME")).enabledScn(readScnColumnAsScn(rs, "ENABLE_CHANGE#")).enabledTime(readTimestampAsInstant(rs, "ENABLE_TIME")).disabledScn(readScnColumnAsScn(rs, "DISABLE_CHANGE#")).disabledTime(readTimestampAsInstant(rs, "DISABLE_TIME"));if (getOracleVersion().getMajor() >= 11) {threadBuilder = threadBuilder.lastRedoSequenceNumber(rs.getLong("LAST_REDO_SEQUENCE#")).lastRedoBlock(rs.getLong("LAST_REDO_BLOCK")).lastRedoScn(readScnColumnAsScn(rs, "LAST_REDO_CHANGE#")).lastRedoTime(readTimestampAsInstant(rs, "LAST_REDO_TIME"));}if (getOracleVersion().getMajor() >= 12) {threadBuilder = threadBuilder.conId(rs.getLong("CON_ID"));}builder = threadBuilder.build();}}return builder.build();});}catch (SQLException e) {throw new DebeziumException("无法读取 Oracle 数据库的重做线程状态", e);}}public List<String> getSQLKeywords() {try {return Arrays.asList(connection().getMetaData().getSQLKeywords().split(","));}catch (SQLException e) {LOGGER.debug("无法从 JDBC 驱动程序获取 SQL 关键字。", e);return Collections.emptyList();}}private static Scn readScnColumnAsScn(ResultSet rs, String columnName) throws SQLException {final String value = rs.getString(columnName);return Strings.isNullOrEmpty(value) ? Scn.NULL : Scn.valueOf(value);}private static Instant readTimestampAsInstant(ResultSet rs, String columnName) throws SQLException {final Timestamp value = rs.getTimestamp(columnName);return value == null ? null : value.toInstant();}

总结

  1. getNationalCharacterSet:

    • 查询并返回数据库的 NLS_NCHAR_CHARACTERSET 设置。
    • 返回值只能是 AL16UTF16UTF8
    • 如果查询结果不是预期中的字符集,则抛出异常。
  2. removeAllLogFilesFromLogMinerSession:

    • 从 LogMiner 会话中移除所有日志文件。
    • 首先查询所有日志文件名。
    • 遍历这些文件名,并执行 PL/SQL 块来移除每个文件。
  3. getRedoThreadState:

    • 获取重做线程的状态信息。
    • 查询 V$THREAD 表以获取重做线程详情。
    • 构建并返回一个表示重做线程状态的对象。
  4. getSQLKeywords:

    • 通过 JDBC 获取数据库支持的所有 SQL 关键字。
    • 返回关键字列表。
  5. 辅助方法:

    • readScnColumnAsScn: 将结果集中特定列的字符串值转换为 Scn 对象。
    • readTimestampAsInstant: 将结果集中的 Timestamp 转换为 Instant
    • 定义了两个函数式接口 ContainerWorkObjectIdentifierConsumer 供其他代码使用。

相关文章:

Oracle-OracleConnection

提示&#xff1a;OracleConnection 主要负责与Oracle数据库的交互&#xff0c;特别针对CDC功能&#xff0c;提供了获取和处理数据库更改日志的能力&#xff0c;同时包含数据库连接管理、查询执行和结果处理的通用功能&#xff0c;与DB2Connection作用相似 文章目录 前言一、核心…...

基于hadoop的网络流量分析系统的研究与应用

目录 摘要 1 Abstract 2 第1章 绪论 3 1.1 研究背景 3 1.2 研究目的和意义 4 1.2.1 研究目的 4 1.2.2 研究意义 6 1.3 国内外研究现状分析 7 1.3.1 国内研究现状 7 1.3.2 国外研究现状 9 1.4 研究内容 11 第2章 Hadoop技术及相关组件介绍 12 2.1 HDFS的工作原理及…...

【C# WPF WeChat UI 简单布局】

创建WPF项目 VS创建一个C#的WPF应用程序: 创建完成后项目目录下会有一个MainWindow.xaml文件以及MainWindow.cs文件,此处将MainWindow.xaml文件作为主页面的布局文件,也即为页面的主题布局都在该文件进行。 布局和数据 主体布局 Wechat的布局可暂时分为三列, 第一列为菜…...

关于docker的几个概念(二)

目录 1. 为何Docker CentOS镜像比传统CentOS镜像小得多&#xff1f;2. 镜像的分层结构及其优势3. 讲一下容器的copy-on-write特性&#xff0c;修改容器里面的内容会修改镜像吗&#xff1f;4. 简单描述一下Dockerfile的整个构建镜像过程 1. 为何Docker CentOS镜像比传统CentOS镜…...

JAVA集中学习第五周学习记录(一)

系列文章目录 第一章 JAVA集中学习第一周学习记录(一) 第二章 JAVA集中学习第一周项目实践 第三章 JAVA集中学习第一周学习记录(二) 第四章 JAVA集中学习第一周课后习题 第五章 JAVA集中学习第二周学习记录(一) 第六章 JAVA集中学习第二周项目实践 第七章 JAVA集中学习第二周学…...

JavaSE 网络编程

什么是网络编程 计算机与计算机之间通过网络进行数据传输 两种软件架构 网络编程3要素 IP IPv4 IPv6 Testpublic void test01() throws UnknownHostException { // InetAddress.getByName 可以是名字或ipInetAddress address InetAddress.getByName("LAPTOP-7I…...

ubuntu24.04 编译安装PHP7.4

ubuntu24.04 编译安装PHP7.4 先安装依赖包&#xff08;原本是centos上安装依赖&#xff0c;让chatgpt转换了下对应的ubutnu下包名&#xff0c;如果编译过程有缺失&#xff0c;按报错提示再安装下&#xff09; apt install zlib1g zlib1g-dev libpcre3 libpcre3-dev libfreety…...

Tied and Anchored Stereo Attention Network for Cloud Removal in Optical

论文名称 基于固定锚定立体注意力网络的光学遥感图像去云方法代码运行 论文代码 https://github.com/ningjin00/TASANet?tabreadme-ov-file 论文地址 1环境创建 模型环境给了这几个包&#xff0c;如果你自带环境 那就运行代码 提示缺哪个装哪个 python 3.12rasterio 1.3.10…...

云开发微信小程序--即时聊天(单人聊天,多人聊天室)

云开发微信小程序–即时聊天 介绍&#xff1a;本小程序包含欢迎界面&#xff0c;注册&#xff0c;登录&#xff0c;一对一聊天&#xff0c;群聊&#xff0c;好友添加请求验证过程&#xff0c;修改好友备注以及删除好友&#xff0c;退出群聊&#xff0c;特殊角色卡片展示&#…...

Leetcod编程基础0到1-基础实现内容(个人解法)(笔记)

以下为个人解法&#xff0c;欢迎提供不同思路 1768. 交替合并字符串 题目&#xff1a;给你两个字符串 word1 和 word2 。请你从 word1 开始&#xff0c;通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长&#xff0c;就将多出来的字母追加到合并后字符串的末尾&…...

仲阳天王星运维实习一面

自我介绍&#xff1f; 略谈谈你对“仲阳天王星”的理解&#xff1f; 略实习时间怎么安排&#xff0c;后续时间是怎么规划的&#xff1f; 略给你一个装满水的8升满壶和两个分别是5升、3升的空壶&#xff0c;请想个办法&#xff0c;使得其中一个水壶恰好装4升水&#xff0c;每一步…...

排序算法详解

​ &#x1f48e;所属专栏&#xff1a;数据结构与算法学习 &#x1f48e; 欢迎大家互三&#xff1a;2的n次方_ &#x1f341;1. 插入排序 &#x1f341;1.1 直接插入排序 插入排序是一种简单直观的排序算法&#xff0c;它的原理是通过构建有序序列&#xff0c;对于未排序数…...

vxe-table树形结构使用setCheckboxRow卡顿--已解决

项目场景&#xff1a; vxe-table树形结构使用setCheckboxRow进行部分节点选中 问题描述 vxe-table树形结构使用setCheckboxRow&#xff0c;在数据较多时卡顿 原因分析&#xff1a; setCheckboxRow内部进行了多次的循环遍历&#xff0c;导致速度慢 解决方案&#xff1a; 设…...

配置错误和 IAM 弱点是云安全的主要隐患

根据云安全联盟发布的《2024 年云计算最大威胁》报告&#xff0c;通常与云服务提供商 (CSP) 相关的传统云安全问题的重要性正在持续下降。 配置错误、IAM 弱点和 API 风险仍然至关重要 这些发现延续了 2022 年报告中首次发现的轨迹&#xff0c;同时&#xff0c;诸如错误配置的…...

Redis系列之Redis Cluster

概述 Redis 2.8版本发布稳定版Redis Sentinel&#xff0c;不过Sentinel集群版存在一些问题&#xff1a; 高可用性&#xff1a;Sentinel集群对Redis既有的主从集群提供有限的高可用保障&#xff1b;在线扩容&#xff1a;节点下线&#xff0c;触发选举&#xff0c;选举涉及两个…...

网站证书过期导致WordPress后台无法登录问题解决,页面样式丢失

1、首先打开网站目录文件\wp-includes\functions.php&#xff0c;找到代码&#xff0c;应该就是就在在第8行。 require( ABSPATH . WPINC . /option.php ); 在下面添加以下代码&#xff0c;作用就是把http替换为https add_filter(script_loader_src, agnostic_script_loader…...

LeetCode刷题笔记第191题:位1的个数

LeetCode刷题笔记第191题&#xff1a;位1的个数 题目&#xff1a; 想法&#xff1a; 通过位运算判断二级制形式中有多少个1&#xff0c;代码及解释如下&#xff1a; class Solution:def hammingWeight(self, n: int) -> int:return sum(1 for i in range(32) if n & …...

C语言—函数栈帧

函数&#xff0c;一般都有返回值&#xff0c;函数名&#xff0c;参数&#xff0c;再下来还有什么mian函数&#xff0c;函数写出来就是要被调用的&#xff0c;上面图片上的代码&#xff0c;main函数和myadd函数&#xff0c;都要在自己的栈结构什么形成自己的栈&#xff0c;可以帮…...

IDEA 2022.1.4用前需知

目录 一、配置国内源 二、正确再次创建新项目方式 IDEA 2022.1.4下载地址 一、配置国内源 1、查看本地仓库地址 2、设置国内源-添加Setting.xml文件内容 3、修改目录&#xff08;考虑到当前硬盘空间大小&#xff0c;英文目录名&#xff09; 1&#xff09;创建你要移动过去…...

Python数据可视化案例——折线图

目录 json介绍&#xff1a; Pyecharts介绍 安装pyecharts包 构建一个基础的折线图 配置全局配置项 综合案例&#xff1a; 使用工具对数据进行查看 &#xff1a; 数据处理 json介绍&#xff1a; json是一种轻量级的数据交互格式&#xff0c;采用完全独立于编程语言的文…...

Ubuntu虚拟机安装及汉化

一、安装 1.勾选典型(推荐)(T)——点击下一步 2.点击浏览找到光盘映像文件打开&#xff08;此文件很重要安装好后安装包不要卸载&#xff0c;放在不容易被删除的地方&#xff09;——点击下一步 3.将信息补充完整——点击下一步 4.点击浏览选择要将虚拟机安装在哪个路径&…...

记2024-08原生微信小程序开发

继2024.08 最近需要开发一个微信小程序的一个功能模块&#xff0c;但是之前在学的时候都是好几年前的东东了&#xff0c;然后重新快速过了一遍b站大学的教程&#xff0c;这篇文章就是基于教程进行的一些总结&#xff0c;和自己开发过程当中使用到的一些点和一些技巧什么的吧。 …...

嵌入式linux系统镜像制作day1

点击上方"蓝字"关注我们 01、前言 嵌入式设备&#xff08;例如心电图检测仪&#xff0c;售票系统等&#xff09;。尽管&#xff0c;嵌入式设备像那些智能手机一样&#xff0c;绝大多数都使用同样的硬件和软件&#xff0c;包括系统芯片SoC、储存、连接和多媒体接口、…...

【相机与图像】2. 相机内外参的标定的代码示例

1 摄像头内参的标定 【相机标定具体操作】 使用将要标定的摄像头&#xff0c;以不同的角度采集棋盘格&#xff0c;要保证视野内出现完整的棋盘格。采集图片数量约15张左右即可。 以11*8的棋盘格为例&#xff0c;具体流程如下&#xff1a; step 1. 设置棋盘格3D点&#xff1b;通…...

重启人生计划-拒绝内耗

&#x1f973;&#x1f973;&#x1f973; 茫茫人海千千万万&#xff0c;感谢这一刻你看到了我的文章&#xff0c;感谢观赏&#xff0c;大家好呀&#xff0c;我是最爱吃鱼罐头&#xff0c;大家可以叫鱼罐头呦~&#x1f973;&#x1f973;&#x1f973; 如果你觉得这个【重启人生…...

盘点电脑开机慢的几大高频原因

常规的话一台电脑正常我们都要用个2年以上的时间,有的可能更长,5年的都有,而电脑目前占多数的主流操作系统就是微软的Windows。那么随着使用年限的增加,无论是系统还是电脑硬件,都会随着使用次数和使用的时间的增加而有损耗,系统软件上就是文件越来越臃肿,空间越来越小,…...

2-64 基于matlab的Consensus-Based Bundle Algorithm (CBBA)算法

基于matlab的Consensus-Based Bundle Algorithm (CBBA)算法&#xff0c;可为异构代理网络上的多代理多任务分配问题提供良好的解决方案。支持具有有效时间窗口的任务、异构代理-任务兼容性要求&#xff0c;以及平衡任务奖励和燃料成本的得分函数。奖励和燃料成本的分数函数。程…...

Win10 去掉桌面右上角 了解有关此图片的信息

1. 进入注册表 Win R运行regedit 2. 找到以下路径 计算机\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel 3. 新建 DWORD&#xff08;32位&#xff09;值&#xff08;D&#xff09; 右击 NewStartPanel新建 DWORD…...

tcpdump入门——抓取三次握手数据包

1. 使用docker启动一个tcp应用 参考&#xff1a;https://blog.csdn.net/LONG_Yi_1994/article/details/141175526 2. 获取容器id docker ps |grep gochat 3. 获取容器的 PID 首先&#xff0c;你需要获得容器的进程 ID&#xff08;PID&#xff09;。可以使用 docker inspect…...

漏洞复现-GitLab任意读取文件(CVE-2023-2825)

1.漏洞描述 GitLab是一个用于仓库管理系统的开源项目&#xff0c;其使用Git作为代码管理工具&#xff0c;可通过Web界面访问公开或私人项目。据悉,该漏洞影响 GitLab社区版(CE)和企业版(EE)的 16.0.0 版本,其它更早的版本几乎都不受影响。 该漏洞存在于GitLab CE/EE版本16.0.0…...