Spring JDBC线程下Oracle Session变了
Spring JDBC Oracle Session changed under the thread
我想在 Spring 中使用 ORACLE DBMS_ALERT 包来接收来自数据库的信号。
在 pl/sql 中,如果在 10 秒内可用,以下代码会收到一条消息:
DECLARE
name VARCHAR2(4000 CHAR);
message VARCHAR2(1800 CHAR);
status NUMBER;
BEGIN
DBMS_ALERT.REGISTER('mytopic');
DBMS_ALERT.WAITANY(name, message, status, 10);
DBMS_OUTPUT.put_line('name: '||name);
DBMS_OUTPUT.put_line('status: '||status);
DBMS_OUTPUT.put_line('message: "'||message||'"');
END;
在我的 Java 代码中,我尝试使用以下代码接收消息:
private static final String TOPIC = "NAME";
private static final String MESSAGE = "MESSAGE";
private static final String STATUS = "STATUS";
private static final String TIMEOUT = "TIMEOUT";
public static final String CALL_DBMS_ALERT_REGISTER = "{ CALL DBMS_ALERT.REGISTER(?) }";
public static final String CALL_DBMS_ALERT_WAITANY = "{ CALL DBMS_ALERT.WAITANY(?, ?, ?, ?) }";
private final JdbcTemplate jdbcTemplate;
public DbmsAlertRepository(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void dbmsAlertRegister(final String topic) {
final List<SqlParameter> parameters = new ArrayList();
parameters.add(new SqlParameter(TOPIC, Types.VARCHAR));
final Map<String, Object> resultData = jdbcTemplate.call(connection -> {
final CallableStatement callableStatement = connection.prepareCall(CALL_DBMS_ALERT_REGISTER);
callableStatement.setString(1, topic);
return callableStatement;
}, parameters);
logger.trace("DBMS_ALERT.REGISTER output: {}", resultData);
}
public Optional<DbmsAlertMessage> dbmsAlertWaitAny(final int timeout) {
final List<SqlParameter> parameters = new ArrayList();
final Optional<DbmsAlertMessage> result;
parameters.add(new SqlOutParameter(TOPIC, Types.VARCHAR));
parameters.add(new SqlOutParameter(MESSAGE, Types.VARCHAR));
parameters.add(new SqlOutParameter(STATUS, Types.NUMERIC));
parameters.add(new SqlParameter(TIMEOUT, Types.NUMERIC));
final Map<String, Object> resultData = jdbcTemplate.call(connection -> {
final CallableStatement callableStatement = connection.prepareCall(CALL_DBMS_ALERT_WAITANY);
callableStatement.registerOutParameter(1, Types.VARCHAR);
callableStatement.registerOutParameter(2, Types.VARCHAR);
callableStatement.registerOutParameter(3, Types.NUMERIC);
callableStatement.setInt(4, timeout);
return callableStatement;
}, parameters);
logger.trace("DBMS_ALERT.WAITANY output: {}", resultData);
final int status = BigDecimal.class.cast(resultData.get(STATUS)).intValue();
if (status == 1) {
result = Optional.empty();
} else {
result = Optional.of(
new DbmsAlertMessage(
String.class.cast(resultData.get(TOPIC)),
String.class.cast(resultData.get(MESSAGE))
)
);
}
return result;
}
然后我在后台线程中通过无限循环调用上面的代码:
public void run() {
dbmsAlertRepository.dbmsAlertRegister("mytopic");
while (!Thread.currentThread().isInterrupted()) {
final Try<Optional<DbmsAlertMessage>> message = Try.of(() -> dbmsAlertRepository.dbmsAlertWaitAny(DBMS_ALERT_TIMEOUT));
message.onSuccess(msg -> {
msg.ifPresent(alertMessage -> {
final DbmsAlertMessageProcessTask task = new DbmsAlertMessageProcessTask(alertMessage);
threadPoolTaskExecutor.execute(task);
});
});
message.onFailure(e -> {
logger.error(e.getMessage(), e);
});
}
}
有效,但有时会出现以下错误:
2020-12-23 09:52:57.199 DEBUG 20206 --- [s-alert-watch-1] o.s.jdbc.core.JdbcTemplate : Calling stored procedure
2020-12-23 09:52:57.294 INFO 20206 --- [s-alert-watch-1] h.e.common.service.DbmsAlertService : Message arrived: Failure(org.springframework.jdbc.UncategorizedSQLException: CallableStatementCallback; uncategorized SQLException; SQL state [72000]; error code [20000]; ORA-20000: ORU-10024: there are no alerts registered.
ORA-06512: a(z) "SYS.DBMS_ALERT", helyen a(z) 295. sornál
ORA-06512: a(z) helyen a(z) 1. sornál
; nested exception is java.sql.SQLException: ORA-20000: ORU-10024: there are no alerts registered.
ORA-06512: a(z) "SYS.DBMS_ALERT", helyen a(z) 295. sornál
ORA-06512: a(z) helyen a(z) 1. sornál
在我看来,Oracle 会话已更改。我怎样才能避免它?
会话不可避免地会由于某种原因而结束。例如,如果 RDBMS crashes/restarts,那么你的会话是无效的。虽然您不能确保会话过期不会发生,但您可以确保代码恢复。正如我们所见,这段代码有自己的线程。因此,运行线程的代码的另一部分可以同步,并且每当该线程加入父线程时,您将重新启动它。
这意味着您用作此线程父线程的线程肯定不是主线程,因此,您可能需要稍微更改线程策略。
我想在 Spring 中使用 ORACLE DBMS_ALERT 包来接收来自数据库的信号。
在 pl/sql 中,如果在 10 秒内可用,以下代码会收到一条消息:
DECLARE
name VARCHAR2(4000 CHAR);
message VARCHAR2(1800 CHAR);
status NUMBER;
BEGIN
DBMS_ALERT.REGISTER('mytopic');
DBMS_ALERT.WAITANY(name, message, status, 10);
DBMS_OUTPUT.put_line('name: '||name);
DBMS_OUTPUT.put_line('status: '||status);
DBMS_OUTPUT.put_line('message: "'||message||'"');
END;
在我的 Java 代码中,我尝试使用以下代码接收消息:
private static final String TOPIC = "NAME";
private static final String MESSAGE = "MESSAGE";
private static final String STATUS = "STATUS";
private static final String TIMEOUT = "TIMEOUT";
public static final String CALL_DBMS_ALERT_REGISTER = "{ CALL DBMS_ALERT.REGISTER(?) }";
public static final String CALL_DBMS_ALERT_WAITANY = "{ CALL DBMS_ALERT.WAITANY(?, ?, ?, ?) }";
private final JdbcTemplate jdbcTemplate;
public DbmsAlertRepository(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void dbmsAlertRegister(final String topic) {
final List<SqlParameter> parameters = new ArrayList();
parameters.add(new SqlParameter(TOPIC, Types.VARCHAR));
final Map<String, Object> resultData = jdbcTemplate.call(connection -> {
final CallableStatement callableStatement = connection.prepareCall(CALL_DBMS_ALERT_REGISTER);
callableStatement.setString(1, topic);
return callableStatement;
}, parameters);
logger.trace("DBMS_ALERT.REGISTER output: {}", resultData);
}
public Optional<DbmsAlertMessage> dbmsAlertWaitAny(final int timeout) {
final List<SqlParameter> parameters = new ArrayList();
final Optional<DbmsAlertMessage> result;
parameters.add(new SqlOutParameter(TOPIC, Types.VARCHAR));
parameters.add(new SqlOutParameter(MESSAGE, Types.VARCHAR));
parameters.add(new SqlOutParameter(STATUS, Types.NUMERIC));
parameters.add(new SqlParameter(TIMEOUT, Types.NUMERIC));
final Map<String, Object> resultData = jdbcTemplate.call(connection -> {
final CallableStatement callableStatement = connection.prepareCall(CALL_DBMS_ALERT_WAITANY);
callableStatement.registerOutParameter(1, Types.VARCHAR);
callableStatement.registerOutParameter(2, Types.VARCHAR);
callableStatement.registerOutParameter(3, Types.NUMERIC);
callableStatement.setInt(4, timeout);
return callableStatement;
}, parameters);
logger.trace("DBMS_ALERT.WAITANY output: {}", resultData);
final int status = BigDecimal.class.cast(resultData.get(STATUS)).intValue();
if (status == 1) {
result = Optional.empty();
} else {
result = Optional.of(
new DbmsAlertMessage(
String.class.cast(resultData.get(TOPIC)),
String.class.cast(resultData.get(MESSAGE))
)
);
}
return result;
}
然后我在后台线程中通过无限循环调用上面的代码:
public void run() {
dbmsAlertRepository.dbmsAlertRegister("mytopic");
while (!Thread.currentThread().isInterrupted()) {
final Try<Optional<DbmsAlertMessage>> message = Try.of(() -> dbmsAlertRepository.dbmsAlertWaitAny(DBMS_ALERT_TIMEOUT));
message.onSuccess(msg -> {
msg.ifPresent(alertMessage -> {
final DbmsAlertMessageProcessTask task = new DbmsAlertMessageProcessTask(alertMessage);
threadPoolTaskExecutor.execute(task);
});
});
message.onFailure(e -> {
logger.error(e.getMessage(), e);
});
}
}
有效,但有时会出现以下错误:
2020-12-23 09:52:57.199 DEBUG 20206 --- [s-alert-watch-1] o.s.jdbc.core.JdbcTemplate : Calling stored procedure
2020-12-23 09:52:57.294 INFO 20206 --- [s-alert-watch-1] h.e.common.service.DbmsAlertService : Message arrived: Failure(org.springframework.jdbc.UncategorizedSQLException: CallableStatementCallback; uncategorized SQLException; SQL state [72000]; error code [20000]; ORA-20000: ORU-10024: there are no alerts registered.
ORA-06512: a(z) "SYS.DBMS_ALERT", helyen a(z) 295. sornál
ORA-06512: a(z) helyen a(z) 1. sornál
; nested exception is java.sql.SQLException: ORA-20000: ORU-10024: there are no alerts registered.
ORA-06512: a(z) "SYS.DBMS_ALERT", helyen a(z) 295. sornál
ORA-06512: a(z) helyen a(z) 1. sornál
在我看来,Oracle 会话已更改。我怎样才能避免它?
会话不可避免地会由于某种原因而结束。例如,如果 RDBMS crashes/restarts,那么你的会话是无效的。虽然您不能确保会话过期不会发生,但您可以确保代码恢复。正如我们所见,这段代码有自己的线程。因此,运行线程的代码的另一部分可以同步,并且每当该线程加入父线程时,您将重新启动它。
这意味着您用作此线程父线程的线程肯定不是主线程,因此,您可能需要稍微更改线程策略。