Oracle-OracleConnection
提示:Oracle
Connection
主要负责与Oracle数据库的交互,特别针对CDC功能,提供了获取和处理数据库更改日志的能力,同时包含数据库连接管理、查询执行和结果处理的通用功能,与DB2Connection作用相似
文章目录
- 前言
- 一、核心功能
- 二、代码分析
- 总结
前言
提示:OracleConnection
类旨在简化与 Oracle 数据库的交互,提供了一套全面的数据库操作接口,特别是针对需要利用 Oracle的变更数据捕获能力的场景。通过这个类,开发者可以更方便地执行数据库操作,而无需直接处理复杂的 JDBC 连接和查询细节。
提示:以下是本篇文章正文内容
一、核心功能
核心功能详细说明
-
查询字符集 (
getNationalCharacterSet
方法):- 功能: 查询 Oracle 数据库的
NLS_NCHAR_CHARACTERSET
参数设置。 - 作用: 确定数据库使用的国家字符集。
- 与 Debezium 的关联: 这对于确保 Debezium 正确地解析和处理字符串数据至关重要,特别是在处理多语言环境下的文本数据时。
- 功能: 查询 Oracle 数据库的
-
清除日志文件 (
removeAllLogFilesFromLogMinerSession
方法):- 功能: 从 LogMiner 会话中移除所有已注册的日志文件。
- 作用: 维护 LogMiner 会话,通过移除已注册的日志文件。
- 与 Debezium 的关联: 这对于维护 LogMiner 会话和确保 Debezium 能够正确跟踪数据库更改至关重要。LogMiner 是 Oracle 提供的一种机制,用于从重做日志中提取更改记录。
-
获取重做线程状态 (
getRedoThreadState
方法):- 功能: 获取 Oracle 数据库的重做线程状态。
- 作用: 监控重做线程的状态,这对于数据库复制和日志分析非常重要。
- 与 Debezium 的关联: 这对于监控重做线程的状态,确保 Debezium 能够正确捕捉数据库更改事件至关重要。重做线程状态反映了数据库内部的活动情况,这对于 CDC 功能的稳定性和准确性至关重要。
-
获取 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();}
总结
-
getNationalCharacterSet
:- 查询并返回数据库的
NLS_NCHAR_CHARACTERSET
设置。 - 返回值只能是
AL16UTF16
或UTF8
。 - 如果查询结果不是预期中的字符集,则抛出异常。
- 查询并返回数据库的
-
removeAllLogFilesFromLogMinerSession
:- 从 LogMiner 会话中移除所有日志文件。
- 首先查询所有日志文件名。
- 遍历这些文件名,并执行 PL/SQL 块来移除每个文件。
-
getRedoThreadState
:- 获取重做线程的状态信息。
- 查询
V$THREAD
表以获取重做线程详情。 - 构建并返回一个表示重做线程状态的对象。
-
getSQLKeywords
:- 通过 JDBC 获取数据库支持的所有 SQL 关键字。
- 返回关键字列表。
-
辅助方法:
readScnColumnAsScn
: 将结果集中特定列的字符串值转换为Scn
对象。readTimestampAsInstant
: 将结果集中的Timestamp
转换为Instant
。- 定义了两个函数式接口
ContainerWork
和ObjectIdentifierConsumer
供其他代码使用。
相关文章:
Oracle-OracleConnection
提示:OracleConnection 主要负责与Oracle数据库的交互,特别针对CDC功能,提供了获取和处理数据库更改日志的能力,同时包含数据库连接管理、查询执行和结果处理的通用功能,与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镜像小得多?2. 镜像的分层结构及其优势3. 讲一下容器的copy-on-write特性,修改容器里面的内容会修改镜像吗?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 先安装依赖包(原本是centos上安装依赖,让chatgpt转换了下对应的ubutnu下包名,如果编译过程有缺失,按报错提示再安装下) 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环境创建 模型环境给了这几个包,如果你自带环境 那就运行代码 提示缺哪个装哪个 python 3.12rasterio 1.3.10…...

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

Leetcod编程基础0到1-基础实现内容(个人解法)(笔记)
以下为个人解法,欢迎提供不同思路 1768. 交替合并字符串 题目:给你两个字符串 word1 和 word2 。请你从 word1 开始,通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长,就将多出来的字母追加到合并后字符串的末尾&…...
仲阳天王星运维实习一面
自我介绍? 略谈谈你对“仲阳天王星”的理解? 略实习时间怎么安排,后续时间是怎么规划的? 略给你一个装满水的8升满壶和两个分别是5升、3升的空壶,请想个办法,使得其中一个水壶恰好装4升水,每一步…...

排序算法详解
💎所属专栏:数据结构与算法学习 💎 欢迎大家互三:2的n次方_ 🍁1. 插入排序 🍁1.1 直接插入排序 插入排序是一种简单直观的排序算法,它的原理是通过构建有序序列,对于未排序数…...

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

配置错误和 IAM 弱点是云安全的主要隐患
根据云安全联盟发布的《2024 年云计算最大威胁》报告,通常与云服务提供商 (CSP) 相关的传统云安全问题的重要性正在持续下降。 配置错误、IAM 弱点和 API 风险仍然至关重要 这些发现延续了 2022 年报告中首次发现的轨迹,同时,诸如错误配置的…...
Redis系列之Redis Cluster
概述 Redis 2.8版本发布稳定版Redis Sentinel,不过Sentinel集群版存在一些问题: 高可用性:Sentinel集群对Redis既有的主从集群提供有限的高可用保障;在线扩容:节点下线,触发选举,选举涉及两个…...
网站证书过期导致WordPress后台无法登录问题解决,页面样式丢失
1、首先打开网站目录文件\wp-includes\functions.php,找到代码,应该就是就在在第8行。 require( ABSPATH . WPINC . /option.php ); 在下面添加以下代码,作用就是把http替换为https add_filter(script_loader_src, agnostic_script_loader…...

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

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

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

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

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...