diff --git a/.gitignore b/.gitignore
index 77fbb7e..e333297 100755
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,7 @@ target
.classpath
.project
.claude
+.vscode
+.DS_Store
+*.log
+plans
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index addbfe1..927a3bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,7 +43,7 @@
false
- cz.kamma.fabka.httpserver.HttpServerApplication
+ cz.kamma.fabka.app.HttpServerApplication
diff --git a/src/main/java/cz/kamma/fabka/repository/ChatRepository.java b/src/main/java/cz/kamma/fabka/repository/ChatRepository.java
index 9faf84a..6e78c3c 100644
--- a/src/main/java/cz/kamma/fabka/repository/ChatRepository.java
+++ b/src/main/java/cz/kamma/fabka/repository/ChatRepository.java
@@ -9,6 +9,7 @@ import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import cz.kamma.fabka.repository.model.ChatLine;
import cz.kamma.fabka.repository.model.ChatVoteStats;
@@ -83,23 +84,26 @@ public class ChatRepository {
if (chatId <= 0) {
return new ChatVoteStats("", "");
}
+ long startTime = System.nanoTime();
+ ChatVoteStats result = null;
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(CHAT_VOTES_USERS_SQL)) {
ps.setLong(1, chatId);
ps.setLong(2, chatId);
try (ResultSet rs = ps.executeQuery()) {
- if (!rs.next()) {
- return new ChatVoteStats("", "");
+ if (rs.next()) {
+ result = new ChatVoteStats(
+ valueOrDefault(rs.getString("thumbup"), ""),
+ valueOrDefault(rs.getString("thumbdown"), "")
+ );
}
- return new ChatVoteStats(
- valueOrDefault(rs.getString("thumbup"), ""),
- valueOrDefault(rs.getString("thumbdown"), "")
- );
}
} catch (Exception ex) {
ex.printStackTrace();
- return new ChatVoteStats("", "");
}
+ long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
+ System.out.println("[DEBUG] CHAT_VOTES_USERS_SQL: chatId=" + chatId + ", duration=" + durationMs + "ms");
+ return result != null ? result : new ChatVoteStats("", "");
}
public void addChatMessage(long userId, String message) {
@@ -123,6 +127,7 @@ public class ChatRepository {
return lines;
}
int safeLimit = limit <= 0 ? 40 : limit;
+ long startTime = System.nanoTime();
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(CHAT_LINES_SQL)) {
ps.setLong(1, currentUserId);
@@ -146,6 +151,8 @@ public class ChatRepository {
} catch (Exception ex) {
ex.printStackTrace();
}
+ long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
+ System.out.println("[DEBUG] CHAT_LINES_SQL: userId=" + currentUserId + ", limit=" + safeLimit + ", lines=" + lines.size() + ", duration=" + durationMs + "ms");
return lines;
}
diff --git a/src/main/java/cz/kamma/fabka/repository/ForumRepository.java b/src/main/java/cz/kamma/fabka/repository/ForumRepository.java
index ccb4569..eca52c7 100644
--- a/src/main/java/cz/kamma/fabka/repository/ForumRepository.java
+++ b/src/main/java/cz/kamma/fabka/repository/ForumRepository.java
@@ -16,6 +16,7 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import cz.kamma.fabka.repository.model.AttachmentData;
import cz.kamma.fabka.repository.model.ForumAttachment;
@@ -30,6 +31,16 @@ public class ForumRepository {
private static final ZoneId APP_ZONE = ZoneId.of("Europe/Prague");
private static final String FORUM_DETAIL_SQL =
"SELECT id, name, description, countdown FROM forum WHERE id=? AND active=1 LIMIT 1";
+ private static final String FORUM_MESSAGES_SQL_DEBUG =
+ "SELECT fi.id, fi.text, fi.created, fi.quoteitem, fi.sticky, ua.id AS author_id, ua.username, ua.created AS author_created, ua.city, " +
+ " (SELECT COUNT(*) FROM forum_items fi2 WHERE fi2.createdby=ua.id AND fi2.deleted=0) AS author_posts, " +
+ " COALESCE((SELECT SUM(votevalue) FROM voting v WHERE v.forumitemid=fi.id), 0) AS vvalue, " +
+ " (SELECT COUNT(*) FROM voting v WHERE v.forumitemid=fi.id AND v.votevalue=1) AS vote_yes, " +
+ " (SELECT COUNT(*) FROM voting v WHERE v.forumitemid=fi.id AND v.votevalue=-1) AS vote_no, " +
+ " (SELECT GROUP_CONCAT(ua2.username SEPARATOR ',') FROM voting v JOIN user_accounts ua2 ON ua2.id=v.voteby WHERE v.forumitemid=fi.id AND v.votevalue=1) AS vote_yes_users, " +
+ " (SELECT GROUP_CONCAT(ua2.username SEPARATOR ',') FROM voting v JOIN user_accounts ua2 ON ua2.id=v.voteby WHERE v.forumitemid=fi.id AND v.votevalue=-1) AS vote_no_users " +
+ "FROM forum_items fi JOIN user_accounts ua ON ua.id=fi.createdby " +
+ "WHERE fi.forumid=? AND fi.deleted=0 ORDER BY fi.created DESC";
private static final String QUOTED_ITEM_SQL =
"SELECT fi.text, ua.username FROM forum_items fi JOIN user_accounts ua ON ua.id=fi.createdby WHERE fi.id=? LIMIT 1";
private static final String ATTACHMENTS_SQL =
@@ -104,6 +115,7 @@ public class ForumRepository {
public List listActiveForums() {
List forums = new ArrayList<>();
+ long startTime = System.nanoTime();
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(FORUM_SQL);
ResultSet rs = ps.executeQuery()) {
@@ -122,6 +134,8 @@ public class ForumRepository {
} catch (Exception ex) {
ex.printStackTrace();
}
+ long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
+ System.out.println("[DEBUG] FORUM_SQL: forums=" + forums.size() + ", duration=" + durationMs + "ms");
return forums;
}
@@ -144,6 +158,7 @@ public class ForumRepository {
if (userId <= 0) {
return result;
}
+ long startTime = System.nanoTime();
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(NEW_MESSAGES_COUNT_SQL)) {
ps.setLong(1, userId);
@@ -159,6 +174,8 @@ public class ForumRepository {
} catch (Exception ex) {
ex.printStackTrace();
}
+ long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
+ System.out.println("[DEBUG] NEW_MESSAGES_COUNT_SQL: userId=" + userId + ", forums=" + result.size() + ", duration=" + durationMs + "ms");
return result;
}
@@ -166,24 +183,27 @@ public class ForumRepository {
if (forumId <= 0) {
return null;
}
+ long startTime = System.nanoTime();
+ ForumDetail result = null;
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(FORUM_DETAIL_SQL)) {
ps.setLong(1, forumId);
try (ResultSet rs = ps.executeQuery()) {
- if (!rs.next()) {
- return null;
+ if (rs.next()) {
+ result = new ForumDetail(
+ rs.getLong("id"),
+ rs.getString("name"),
+ rs.getString("description"),
+ rs.getString("countdown")
+ );
}
- return new ForumDetail(
- rs.getLong("id"),
- rs.getString("name"),
- rs.getString("description"),
- rs.getString("countdown")
- );
}
} catch (Exception ex) {
ex.printStackTrace();
- return null;
}
+ long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
+ System.out.println("[DEBUG] FORUM_DETAIL_SQL: forumId=" + forumId + ", duration=" + durationMs + "ms");
+ return result;
}
public List listMessagesByForumId(long forumId) {
@@ -191,46 +211,103 @@ public class ForumRepository {
if (forumId <= 0) {
return messages;
}
- if (!loadMessages(messages, forumId, FORUM_MESSAGES_SQL)) {
+ long startTime = System.nanoTime();
+ if (!loadMessages(messages, forumId, FORUM_MESSAGES_SQL_DEBUG)) {
loadMessages(messages, forumId, FORUM_MESSAGES_SQL_NO_STICKY);
}
+ long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
+ System.out.println("[DEBUG] FORUM_MESSAGES_SQL: forumId=" + forumId + ", rows=" + messages.size() + ", duration=" + durationMs + "ms");
return messages;
}
private boolean loadMessages(List out, long forumId, String sql) {
out.clear();
+ long queryStartTime = System.nanoTime();
+
+ // First, fetch all messages and collect item IDs for batch attachment loading
+ List itemIds = new ArrayList<>();
+ List createdTimes = new ArrayList<>();
+ List authorIds = new ArrayList<>();
+ List usernames = new ArrayList<>();
+ List cities = new ArrayList<>();
+ List texts = new ArrayList<>();
+ List authorPostCounts = new ArrayList<>();
+ List vvalues = new ArrayList<>();
+ List voteYes = new ArrayList<>();
+ List voteNo = new ArrayList<>();
+ List voteYesUsers = new ArrayList<>();
+ List voteNoUsers = new ArrayList<>();
+ List ids = new ArrayList<>();
+ List quoteItemIds = new ArrayList<>();
+ List stickies = new ArrayList<>();
+
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, forumId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
- Timestamp createdTs = rs.getTimestamp("created");
- long quoteItemId = rs.getLong("quoteitem");
- QuotedTextItem quotedItem = quoteItemId > 0 ? findQuotedItem(quoteItemId) : null;
- out.add(new ForumMessage(
- rs.getLong("id"),
- rs.getLong("author_id"),
- valueOrDefault(rs.getString("username"), "N/A"),
- formatTs(rs.getTimestamp("author_created")),
- valueOrDefault(rs.getString("city"), ""),
- rs.getLong("author_posts"),
- createdTs == null ? 0L : createdTs.getTime(),
- rs.getInt("vvalue"),
- rs.getInt("vote_yes"),
- rs.getInt("vote_no"),
- valueOrDefault(rs.getString("vote_yes_users"), ""),
- valueOrDefault(rs.getString("vote_no_users"), ""),
- quoteItemId,
- quotedItem,
- formatTs(createdTs),
- valueOrDefault(rs.getString("text"), ""),
- listAttachmentsByForumItemId(rs.getLong("id")),
- rs.getInt("sticky") == 1
- ));
+ ids.add(rs.getLong("id"));
+ itemIds.add(rs.getLong("id"));
+ createdTimes.add(rs.getTimestamp("created"));
+ authorIds.add(rs.getLong("author_id"));
+ usernames.add(rs.getString("username"));
+ cities.add(rs.getString("city"));
+ authorPostCounts.add(rs.getLong("author_posts"));
+ texts.add(rs.getString("text"));
+ vvalues.add(rs.getInt("vvalue"));
+ voteYes.add(rs.getInt("vote_yes"));
+ voteNo.add(rs.getInt("vote_no"));
+ voteYesUsers.add(rs.getString("vote_yes_users"));
+ voteNoUsers.add(rs.getString("vote_no_users"));
+ quoteItemIds.add(rs.getLong("quoteitem"));
+ stickies.add(rs.getInt("sticky"));
}
}
+
+ // Batch load attachments for all items
+ long attachmentsStartTime = System.nanoTime();
+ Map> attachmentsByItemId = new LinkedHashMap<>();
+ if (!itemIds.isEmpty()) {
+ attachmentsByItemId = loadAttachmentsBatch(itemIds);
+ }
+ long attachmentsDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - attachmentsStartTime);
+ System.out.println("[DEBUG] Batch attachment load: " + attachmentsDurationMs + "ms, " + itemIds.size() + " items");
+
+ // Build message objects
+ for (int i = 0; i < ids.size(); i++) {
+ long messageId = ids.get(i);
+ long quoteItemId = quoteItemIds.get(i);
+ QuotedTextItem quotedItem = quoteItemId > 0 ? findQuotedItem(quoteItemId) : null;
+ List attachments = attachmentsByItemId.getOrDefault(messageId, new ArrayList<>());
+
+ out.add(new ForumMessage(
+ messageId,
+ authorIds.get(i),
+ valueOrDefault(usernames.get(i), "N/A"),
+ formatTs(createdTimes.get(i)),
+ valueOrDefault(cities.get(i), ""),
+ authorPostCounts.get(i),
+ createdTimes.get(i) == null ? 0L : createdTimes.get(i).getTime(),
+ vvalues.get(i),
+ voteYes.get(i),
+ voteNo.get(i),
+ valueOrDefault(voteYesUsers.get(i), ""),
+ valueOrDefault(voteNoUsers.get(i), ""),
+ quoteItemId,
+ quotedItem,
+ formatTs(createdTimes.get(i)),
+ valueOrDefault(texts.get(i), ""),
+ attachments,
+ stickies.get(i) == 1
+ ));
+ }
+
+ long totalDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - queryStartTime);
+ System.out.println("[DEBUG] loadMessages completed: total=" + totalDurationMs + "ms, rows=" + out.size());
return true;
} catch (Exception ex) {
+ System.err.println("[DEBUG] loadMessages ERROR: " + ex.getMessage());
+ ex.printStackTrace();
return false;
}
}
@@ -374,22 +451,25 @@ public class ForumRepository {
if (messageId <= 0) {
return null;
}
+ long startTime = System.nanoTime();
+ QuotedTextItem result = null;
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(QUOTED_ITEM_SQL)) {
ps.setLong(1, messageId);
try (ResultSet rs = ps.executeQuery()) {
- if (!rs.next()) {
- return null;
+ if (rs.next()) {
+ result = new QuotedTextItem(
+ valueOrDefault(rs.getString("username"), "N/A"),
+ valueOrDefault(rs.getString("text"), "")
+ );
}
- return new QuotedTextItem(
- valueOrDefault(rs.getString("username"), "N/A"),
- valueOrDefault(rs.getString("text"), "")
- );
}
} catch (Exception ex) {
ex.printStackTrace();
- return null;
}
+ long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
+ System.out.println("[DEBUG] QUOTED_ITEM_SQL: messageId=" + messageId + ", duration=" + durationMs + "ms");
+ return result;
}
public long addReply(long forumId, long userId, String message, Long quoteItem) {
@@ -509,6 +589,7 @@ public class ForumRepository {
if (forumItemId <= 0) {
return out;
}
+ long startTime = System.nanoTime();
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(ATTACHMENTS_SQL)) {
ps.setLong(1, forumItemId);
@@ -525,6 +606,10 @@ public class ForumRepository {
} catch (Exception ex) {
ex.printStackTrace();
}
+ long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
+ if (durationMs > 1) {
+ System.out.println("[DEBUG] ATTACHMENTS_SQL: forumItemId=" + forumItemId + ", count=" + out.size() + ", duration=" + durationMs + "ms");
+ }
return out;
}
@@ -559,6 +644,36 @@ public class ForumRepository {
}
}
+ private Map> loadAttachmentsBatch(List itemIds) {
+ Map> result = new LinkedHashMap<>();
+ if (itemIds.isEmpty()) {
+ return result;
+ }
+ String sql = "SELECT forumitemsid, id, name, ispicture, width, size FROM attachments WHERE forumitemsid IN (" +
+ String.join(",", java.util.Collections.nCopies(itemIds.size(), "?")) + ") ORDER BY forumitemsid, id";
+ try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ for (int i = 0; i < itemIds.size(); i++) {
+ ps.setLong(i + 1, itemIds.get(i));
+ }
+ try (ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ long forumItemId = rs.getLong("forumitemsid");
+ ForumAttachment attachment = new ForumAttachment(
+ rs.getLong("id"),
+ valueOrDefault(rs.getString("name"), "attachment"),
+ rs.getInt("ispicture") == 1,
+ rs.getInt("width")
+ );
+ result.computeIfAbsent(forumItemId, k -> new ArrayList<>()).add(attachment);
+ }
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ return result;
+ }
+
private static String valueOrDefault(String value, String defaultValue) {
return value == null || value.isBlank() ? defaultValue : value;
}
diff --git a/src/main/java/cz/kamma/fabka/repository/PrivateMessageRepository.java b/src/main/java/cz/kamma/fabka/repository/PrivateMessageRepository.java
index a0eeb9a..24846d5 100644
--- a/src/main/java/cz/kamma/fabka/repository/PrivateMessageRepository.java
+++ b/src/main/java/cz/kamma/fabka/repository/PrivateMessageRepository.java
@@ -10,6 +10,7 @@ import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import cz.kamma.fabka.repository.model.PrivateMessageItem;
import cz.kamma.fabka.repository.model.PrivateMessageStats;
@@ -56,6 +57,7 @@ public class PrivateMessageRepository {
"where (m.to_user=? or m.from_user=?) and m.deleted=0 and m.reply_to is null " +
"order by " + order;
+ long startTime = System.nanoTime();
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, userId);
@@ -77,6 +79,8 @@ public class PrivateMessageRepository {
} catch (Exception ex) {
ex.printStackTrace();
}
+ long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
+ System.out.println("[DEBUG] PM listThreads: userId=" + userId + ", threads=" + out.size() + ", duration=" + durationMs + "ms");
return out;
}