Sobes.tech
Senior
55

Υπάρχει ένα σύστημα που δίνει στους χρήστες τη δυνατότητα να εργάζονται με αρχεία στον browser. Η τυπική τεχνολογία: Java, Spring, React, Postgres. Τα αρχεία αποθηκεύονται στο σύστημα αρχείων στο backend, τα μεταδεδομένα των αρχείων στη βάση δεδομένων. Η ομάδα υλοποίησε μια λειτουργία: @Transactional public void process(String oldName, String newName) { Long id = exec("select id from file where name='" + oldName + "'"); // εκτέλεση ερωτήματος στη βάση δεδομένων processFile(oldName, newName); // μετονομασία αρχείου στον δίσκο exec("update file set name='" + newName + "' where id = " + id); // εκτέλεση ερωτήματος στη βάση δεδομένων }

Εταιρείες όπου ρωτήθηκαν
солюшин

Απάντηση από AI

sobes.tech AI

В представленном коде есть несколько проблем, связанных с транзакционностью и согласованностью данных между базой и файловой системой:

  1. Аннотация @Transactional обеспечивает атомарность только для операций с базой данных, но не для операций с файловой системой. Если переименование файла на диске (processFile) упадет, то изменения в базе не будут отменены, и наоборот — если обновление в базе упадет, файл уже будет переименован.

  2. Использование конкатенации строк для SQL-запросов ведет к риску SQL-инъекций. Лучше использовать параметризованные запросы.

  3. Логика смешивает работу с БД и файловой системой в одном методе, что усложняет обработку ошибок и откат.

Рекомендации:

  • Разделить операции: сначала проверить возможность переименования файла, затем выполнить переименование файла, и только после успешного переименования обновить БД.
  • Для обеспечения согласованности можно использовать паттерн "двухфазного коммита" или компенсирующие транзакции, так как файловая система не поддерживает транзакции.
  • Обернуть переименование файла в отдельный сервис с обработкой ошибок.
  • Использовать параметризованные запросы для безопасности.

Пример улучшенного подхода:

@Transactional
public void process(String oldName, String newName) {
    Long id = jdbcTemplate.queryForObject(
        "select id from file where name = ?", Long.class, oldName);

    try {
        fileService.renameFile(oldName, newName); // переименование файла на диске
    } catch (IOException e) {
        throw new RuntimeException("Не удалось переименовать файл", e);
    }

    jdbcTemplate.update("update file set name = ? where id = ?", newName, id);
}

Если переименование файла не удалось, транзакция не дойдет до обновления БД. Но если обновление БД упадет после переименования файла, то придется реализовать логику компенсации (например, вернуть имя файла обратно).

Для более надежной системы стоит рассмотреть хранение файлов в специализированных хранилищах с поддержкой транзакций или использовать очередь для синхронизации изменений.