Refactor storeMessages to improve batch handling and error recovery, add per-message fallback, and enhance logging.

This commit is contained in:
2026-08-25 22:44:36 +02:00
parent 776ea90d7d
commit 79ded52842
2 changed files with 126 additions and 22 deletions
@@ -40,33 +40,122 @@ public class ChatLogQueries {
}
}
public static @NotNull CompletableFuture<Boolean> storeMessages(HashMap<UUID, List<ChatLog>> chatMessages) {
String insertQuery = "INSERT INTO chat_log (uuid, time_stamp, server, type, channel, receiver, chat_message, mini_message, blocked) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
public static @NotNull CompletableFuture<Boolean> storeMessages(
HashMap<UUID, List<ChatLog>> chatMessages
) {
String insertQuery = """
INSERT INTO chat_log
(uuid, time_stamp, server, type, channel, receiver,
chat_message, mini_message, blocked)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""";
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = DatabaseConnection.createTransactionConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement(insertQuery);
try (Connection connection = DatabaseConnection.createTransactionConnection();
PreparedStatement preparedStatement = connection.prepareStatement(insertQuery)) {
try {
addMessagesToBatch(chatMessages, preparedStatement);
int[] updatedRowsCount = preparedStatement.executeBatch();
boolean isSuccess = Arrays.stream(updatedRowsCount)
.allMatch(i -> i >= 0 || i == Statement.SUCCESS_NO_INFO);
if (isSuccess) {
connection.commit();
return true;
}
connection.rollback();
} catch (BatchUpdateException exception) {
connection.rollback();
ALogger.error(
"Failed to store chat batch. Trying messages individually.",
exception
);
}
return storeMessagesIndividually(
connection,
preparedStatement,
chatMessages
);
} catch (SQLException exception) {
ALogger.error("Failed to store chat messages", exception);
throw new CompletionException(
"Failed to store chat messages",
exception
);
}
});
}
private static void addMessagesToBatch(
HashMap<UUID, List<ChatLog>> chatMessages,
PreparedStatement preparedStatement
) throws SQLException {
for (List<ChatLog> chatLogList : chatMessages.values()) {
for (ChatLog chatLog : chatLogList) {
chatLog.prepareStatement(preparedStatement);
preparedStatement.addBatch();
}
}
int[] updatedRowsCount = preparedStatement.executeBatch();
boolean isSuccess = Arrays.stream(updatedRowsCount).allMatch(i -> i >= 0);
}
if (isSuccess) {
connection.commit();
return true;
private static boolean storeMessagesIndividually(
Connection connection,
PreparedStatement preparedStatement,
HashMap<UUID, List<ChatLog>> chatMessages
) throws SQLException {
boolean storedAny = false;
for (List<ChatLog> chatLogList : chatMessages.values()) {
Iterator<ChatLog> iterator = chatLogList.iterator();
while (iterator.hasNext()) {
ChatLog chatLog = iterator.next();
if (storeMessage(preparedStatement, chatLog)) {
storedAny = true;
} else {
connection.rollback();
ALogger.warn("Failed to store messages");
iterator.remove();
}
}
}
connection.commit();
return storedAny;
}
private static boolean storeMessage(
PreparedStatement preparedStatement,
ChatLog chatLog
) {
try {
preparedStatement.clearParameters();
chatLog.prepareStatement(preparedStatement);
preparedStatement.executeUpdate();
return true;
} catch (SQLException exception) {
logInvalidMessage(chatLog, exception);
return false;
}
} catch (SQLException sqlException) {
ALogger.error("Failed to store chat messages", sqlException);
throw new CompletionException("Failed to store chat messages", sqlException);
}
});
private static void logInvalidMessage(
ChatLog chatLog,
SQLException exception
) {
ALogger.error(
"Failed to store chat message. Removing offending message: " + chatLog,
exception
);
}
public static @NotNull CompletableFuture<List<ChatLog>> retrieveMessages(ChatLogHandler chatLogHandler, UUID uuid, Duration duration, String server) {
@@ -43,4 +43,19 @@ public class ChatLog implements BatchInsertable {
);
preparedStatement.setInt(9, blocked ? 1 : 0);
}
@Override
public String toString() {
return "ChatLog{" +
"uuid=" + uuid +
", timestamp=" + timestamp +
", server='" + server + '\'' +
", type=" + type.name() +
", channel='" + channel + '\'' +
", receiver='" + receiver + '\'' +
", message='" + message + '\'' +
", miniMessage=" + (miniMessage == null ? null : GsonComponentSerializer.gson().serialize(miniMessage)) +
", blocked=" + blocked +
'}';
}
}