Паттерн «Сценарий транзакции» при загрузке файлов в Spring MVC

Ранее мы рассматривали загрузку файлов в web приложении на основе Spring MVC с использованием форм. Но, на механизме загрузки процесс на самом деле не заканчивается. Необходимо обеспечить корректность его работы. Чтобы было соответствие между файлами и хранящимися на сервере и записями в базе данных, которые содержат информацию о них.

Согласитесь, что если на сервере будет храниться файл, так или иначе связанный с бизнес логикой приложения, о котором оно «ничего не знает» или когда в базе данных есть записи о файле, но самого файла на жёстком диске физически нет, это неправильно. И чтобы предотвратить возникновение подобных ситуации, мы усовершенствуем рассмотренный ранее механизм загрузки при помощи паттерна «Сценарий транзакции».

В теоретические подробности вдаваться не будем, так как они достаточно хорошо описаны в литературе [1], а сразу приступим к практике.

Сразу отметим, что вследствие разнообразия возможных ситуации реализация данного паттерна для того или иного конкретного случая может сильно отличаться, но принцип, всегда один и тот же.

За основу мы возьмём пример, который рассматривался нами в статье «Работа с формами в Spring MVC». Напомним, что представлял собой метод контроллера, который обрабатывал данные формы при загрузке файлов:

protected String addManPost(MansForm mansForm) throws UnsupportedEncodingException {
    String manName = man.setName(mansForm.getName());
    if (mansForm.getAvatar() != null) {
        MultipartFile file = mansForm.getAvatar();
        try {
            String fileName = basePath + file.getOriginalFilename();
            file.transferTo(new File(fileName));
        } catch (IOException ex) {
            Logger.getLogger(mansController.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    return "redirect:/addman";
}

В этом методе уже заложена основа для реализации паттерна «Сценарий транзакции», так как блок try-catch позволяет отследить факт успешного сохранения файла. Но для того, чтобы реализовать его полностью нам требуется контролировать не только сохранение файла, но и соответствующей записи в базе данных.

Вначале объединим оба процесса в одном методе. Для разнообразия «аватар» и предыдущего примера переименуем в «фотографию».

protected String addManPost(MansForm mansForm) throws UnsupportedEncodingException {
    Mans man = new Mans();
    man.setName(mansForm.getName());
    if (mansForm.getPhoto() != null) {
        MultipartFile file = mansForm.getPhoto();
        try {
            // Сохранение файла
            String fileName = basePath + file.getOriginalFilename();
            file.transferTo(new File(fileName));
            man.setPhoto(fileName);
            // Сохранение записи ва БД
            mansDao.add(man);
        } catch (IOException ex) {
            Logger.getLogger(indexController.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    return "redirect:/addman";
}

Подобная конструкция позволяет обеспечить корректность процесса сохранения в целом, но не для отдельных его этапов.

Добавим проверку существования файла фотографии при помощи стандартного класса File.

protected String addManPost(MansForm mansForm/ throws UnsupportedEncodingException {
    Mans man = new Mans();
    man.setName(mansForm.getName());
    if (mansForm.getPhoto() != null) {
        MultipartFile file = mansForm.getPhoto();
        try {
            // Сохранение файла
            String fileName = basePath + file.getOriginalFilename();
            file.transferTo(new File(fileName));
            man.setPhoto(fileName);
            // Сохранение записи ва БД
            File photoFile = new File(fileName);
            if (photoFile.exists()) {
                    mansDao.add(man);
            }
        } catch (IOException ex) {
            Logger.getLogger(indexController.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    return "redirect:/addman";
}

Теперь запись в будет сохраняться в базе данных только если файл фотографии успешно загружен. Но, это только половина задачи, так как подобная проверка не препятствует наличию файлов, записи о которых по различным причинам в базе данных не сохранились.

Для устранения этого недостатка введём проверку на сохранение в базе данных при помощи блока try-catch и в случае возникновения исключения при сохранении записи будем удалять файл.

protected String addManPost(MansForm mansForm) throws UnsupportedEncodingException {
    Mans man = new Mans();
    man.setName(mansForm.getName());
    if (mansForm.getPhoto() != null) {
        MultipartFile file = mansForm.getPhoto();
        try {
            // Сохранение файла
            String fileName = basePath + file.getOriginalFilename();
            file.transferTo(new File(fileName));
            man.setPhoto(fileName);
            // Сохранение записи ва БД
            File photoFile = new File(fileName);
            if (photoFile.exists()) {
                try {
                    mansDao.add(man);
                } catch (Exception ex) {
                    Logger.getLogger(indexController.class.getName()).log(Level.SEVERE, null, ex);
                    photoFile.delete();
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(indexController.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    return "redirect:/addman";
}

Таким образом мы добились того, что при сохранении записи в базе данных обязательно будет сохраняться связанный с ней файл и наоборот. Тем самым используя паттерн «Сценарий транзакции» мы обеспечили корректность работы приложения.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *