• Как работать с изображениями в веб-приложениях на Java?

    azerphoenix
    @azerphoenix Куратор тега Java
    Java Software Engineer
    Добрый день!

    Как хранить изображение? Насколько я знаю, их можно запихивать прямо в PostgreSQL, но я склоняюсь в сторону того, что изображения надо сохранять в ресурсы, а в бд хранить только ссылки.

    Не самая лучшая идея хранить картинки в виде binary в БД. Лучше, как вы отметили в БД хранить ссылку на объект, а ресурс в файловой системе. Можно хранить файлы в распределенных файловых системах (AWS, Google), но вряд ли вы располагаете таким бюджетом.

    Вот, простая реализация хранения картинок в FS.

    Entity
    @Entity
    @Data
    @NoArgsConstructor
    @Table(name = "attachments")
    public class Attachment {
    
      @Id
      @GeneratedValue(strategy = GenerationType.SEQUENCE)
      private Long attachId;
    
      private String attachTitle;
    
      @Column(nullable = false, updatable = false)
      private LocalDate uploadDate;
    
      private String extension;
    
      private String downloadLink;
    
    }


    Repository
    @Repository
    public interface AttachmentRepository extends JpaRepository<Attachment, Long> {}


    Service
    public interface AttachmentService {
    
      /**
       * Загрузить новый файл
       *
       * @param file
       * @param user
       * @throws IOException
       */
      Attachment addAttachment(MultipartFile file, User user) throws IOException;
    
    
      /**
       * Найти Вложение по его ID
       *
       * @param attachId
       * @return
       */
      Attachment findAttachById(Long attachId);	
    
      /**
       * Скачать файл
       *
       * @param uploadYear
       * @param fileName
       * @return
       * @throws MalformedURLException
       */
      Resource loadFileAsResource(String uploadYear, String fileName) throws MalformedURLException;
    
    }


    ServiceImpl
    @Service
    @RequiredArgsConstructor
    public class AttachmentServiceImpl implements AttachmentService {
    
      private final AttachmentRepository attachmentRepository;
      private final AppProperties appProperties;
      private final FileTools fileTools;
    
      /**
       * Загрузить новый файл
       *
       * @param file
       * @param user
       * @throws IOException
       */
      @Override
      public Attachment addAttachment(MultipartFile file, User user) throws IOException {
        // Создаем директорию если ее не существует
        File uploadDir = new File(appProperties.getUploadPath());
        // Если директория uploads не существует, то создаем ее
        if (!uploadDir.exists()) {
          uploadDir.mkdirs();
        }
        String curDate = LocalDateTime.now().toString();
        // Создаем уникальное название для файла и загружаем файл
        String fileName =
            "attach_" + curDate + "_" + file.getOriginalFilename().toLowerCase().replaceAll(" ", "-");
        file.transferTo(new File(uploadDir + "/" + fileName));
        Attachment attachment = Attachment.builder()
            .attachTitle(fileName)
            .uploadDate(LocalDate.now())
            .extension(fileTools.getFileExtension(file.getOriginalFilename()))
            .downloadLink("/attachments/get/" + Year.now() + "/" + fileName)
            .build();
        attachmentRepository.save(attachment);
        return attachment;
      }
    
    
      /**
       * Найти Вложение по его ID
       *
       * @param attachId
       * @return
       */
      @Override
      public Attachment findAttachById(Long attachId) {
        return attachmentRepository
            .findById(attachId)
            .orElseThrow(() -> new AttachmentNotFoundException("Attachment not found!"));
      }
    
    
      /**
       * Скачать файл
       *
       * @param fileName
       * @return
       * @throws MalformedURLException
       */
      @Override
      public Resource loadFileAsResource( String fileName)
          throws MalformedURLException {
        Path fileStorageLocation =
            Paths.get(appProperties.getUploadPath()).toAbsolutePath().normalize();
        Path filePath = fileStorageLocation.resolve(fileName).normalize();
        return new UrlResource(filePath.toUri());
      }
    
    }


    Conroller
    @Controller
    @RequiredArgsConstructor
    @RequestMapping("/attachments")
    public class AttachmentController {
    
      private final AttachmentService attachmentService;
      private final UserService userService;
    
      /**
       * Загрузить новое вложение
       *
       * @param file
       * @return
       * @throws IOException
       */
      @PostMapping(value = "/add", produces = "application/json")
      @ResponseBody
      public ResponseEntity<Map<String, String>> uploadAttachment(
          @RequestPart(value = "file") MultipartFile file)
          throws IOException {
        Attachment attachment = attachmentService.addAttachment(file);
        Map<String, String> attachmentStatus = new HashMap<>();
        attachmentStatus.put("status", "ok");
        attachmentStatus.put("attachId", attachment.getAttachId().toString());
        return ResponseEntity.ok(attachmentStatus);
      }
    
      /**
       * Получить ссылку на скачивание загруженного файла
       *
       * @param filename
       * @param request
       * @return
       * @throws IOException
       */
      @GetMapping("/get/{filename:.+}")
      public ResponseEntity<Resource> serveFile(
           @PathVariable String filename, HttpServletRequest request)
          throws IOException {
        Resource resource = attachmentService.loadFileAsResource(filename);
        String contentType;
        contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
        if (contentType == null) {
          contentType = "application/octet-stream";
        }
        return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType(contentType))
            .header(
                HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename=\"" + resource.getFilename() + "\"")
            .body(resource);
      }
    }
    Ответ написан
    4 комментария
  • По какому принципу Spring DataJPA обновляет записи в связанных таблицах?

    azerphoenix
    @azerphoenix Куратор тега Java
    Java Software Engineer
    Приветствую, коллега!
    Позволь, внести небольшую лепту в ваш код и отметить следующее:
    CrudCourseRepository

    Наверное, указывать Crud в названии не нужно, так как в репозиторий в будущем могут быть добавлены другие методы и соответственно, с точки зрения clean code название не будет корректным.

    Как JpaRepository производит обновление связей в таблице student_courses?

    Как говорится, лучше один раз увидеть, чем сто раз услышать. Так что лучше включить в properties
    spring.jpa.show-sql=true
    spring.jpa.properties.hibernate.format_sql=true


    Предположу, что вы увидите примерно следующее:
    Запрос на SELECT (выборку) из двух таблиц вместе с JOIN, а далее запрос на вставку новой записи (INSERT)

    Насколько я понимаю, если я попытаюсь обновить сущность Student, передав в CrudStudentRepository#save() студента с пустым списком курсов, то в таблице student_courses будут удалены все записи, в которых фигурирует обновляемый студент, но по моему опыту этого не происходит. Как мне тогда удалять/обновлять эти записи?

    Тут пожалуй, вам стоит обратить внимание на CascadeType.

    Например, если в связи OneToMany вы постараетесь сохранить список со стороны самой сущности, то элементы списка будут сохранены, но id у них будет null, так как сама сущность еще не сохранена и ей не присвоен идентификатор. Чтобы избежать этого можно как вариант, указать CascadeType.All или PERSIST & MERGE

    В случае с ManyToMany, думаю, что будет корректным указать тип каскада. Например, CascadeType.PERSIST CascadeType.MERGE

    К слову, есть хороший источник, откуда я также время от времени черпаю информацию. Вот ссылка - link
    Можешь глянуть.
    Ответ написан
    3 комментария
  • Возможно ли работать одновременно по JDBC и DATAJPA?

    leahch
    @leahch
    3D специалист. Dолго, Dорого, Dерьмово.
    Да, возможно, но это очень плохо! JPA обычно кеширует данные и запросы, а JDBC - нет. В результате есть возможность нарваться на неприятности, оcобенно, если сделали update через JDBC и сразу за этим выборку через JPA. Можно получить кота Шредингера....
    Да и зачем, если в JPA есть createNativeQuery (блин, я еще что-то помню...) - https://thorben-janssen.com/jpa-native-queries/
    Ответ написан
    7 комментариев
  • Что означает слово layout из уст "программиста css\html"?

    @deleted-webter
    О себе вот тут такой рассказ. Бит среди терабайтов
    Поднеси к его виску пистолет и скажи:
    layout это прослойка между пользователем и сервером, как прослойка пота между дулом и виском
    Ответ написан
    Комментировать
  • Что такое анонимная функция и с чем ее едят?

    baskerville42
    @baskerville42
    Учусь работать (Junior)
    Анонимная функция это функция без имени
    function() { ... }
    function funcWithName() { ... }


    Вот самый лучший пример использования
    $( document ).ready(function() {
      // Handler for .ready() called.
    });


    Про замыкания с помощью анонимной функции не слыхал...
    Ответ написан
    6 комментариев
  • Что такое SPI (Интерфейс поставщика услуг)?

    xez
    @xez Куратор тега Java
    TL Junior Roo
    Ответ написан
    Комментировать
  • Почему все любят Windows 10?

    @nehrung
    Не забывайте кликать кнопку "Отметить решением"!
    По большей части претензии надуманные, ПМСМ.
    1, 2. Да, есть лишнее, и что? Где-то лежит, пить-есть не просит. Я в своё время (ещё на Win8.1) тоже скулил насчёт метро-приложений, пытался их снести... Теперь (уже на Win10) я их просто не замечаю. Кстати, есть версия Windows 10 LTSC, из которой сам производитель вырезал многое из того, что вам не нравится. Но простым пользователям она не продаётся.
    3. Нынешний Edge не надо удалять! Как известно, 60...70% пользователей всё равно в качестве основного браузера пользуют Хром. Так вот, нынешний Edge - это тот же Хром, только с чуть изменённой мордой. MS сделала то, что давно сделали остальные производители браузеров - переписала Edge на ядре Хрома. Теперь ставить себе ещё один отдельный Хром не имеет смысла.
    4. Сколь могу судить по окружающим меня компам, со скоростью всё наоборот. Современные "тяжёлые" сайты с большим трудом и фризами открываются на Win7, а на Win10 - без проблем. Причина понятна: авторы кода этих сайтов учитывают нарастание производительности железа и утяжеляют сайты. А на быстрое железо старую ОС не поставить - нет драйверов. Конечно, пользователь может не принимать это в расчёт, но тогда ему остаётся только плакаться в собственную жилетку.
    5. Отключаемо, как из самой Винды, так и сторонними программами.
    6. Насчёт усложнения настроек - опять наоборот. Кроме новой Панели управления с большим количеством новых настроек, в Win10 оставлена прежняя панель с прежними апплетами, для консерваторов. Только она лежит чуть в стороне: Этот компьютер (ПКМ)->Свойства->Все элементы панели управления (в адресной строке).
    7. И основной вопрос- реальные плюсы. По-моему, это удобство и защищённость. Удобство, потому что MS автоматизировала всё, что можно и нельзя. Вообще можно ничего не трогать с момента установки и продолжать пользоваться с настройками по умолчанию. А защищённость - ну, это понятно: достаточно жёсткие политики безопасности, вполне приличный встроенный антивирус, отъедающий не слишком много ресурсов, и плюс к тому все уязвимости отслеживаются и оперативно затыкаются. Наверное, в Win8.1 это тоже делается, но подозреваю, не в таком объёме. А в Win7 не делается вообще (там даже встроенный Защитник Windows перестали снабжать свежими антивирусными базами).
    Ответ написан
    3 комментария
  • Почему все любят Windows 10?

    paran0id
    @paran0id
    Умный, но ленивый
    По необходимости ставил десятку на компы 2008 года сборки. Докинул только памяти до 4 гигов и SSD воткнул. Для офисных нужд хватает, работает пристойно.

    Интерфейс привычный, меню пуск лучше, чем в 8.1. Пользователей пересаживать на 10 не страшно, даже после ХР адаптируются.

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

    Из явных недостатков назвал бы только регулярно калечащие обновления, сам испытывал проблемы со звуком после выхода версии 2004, и бестолковые приложеньки из п.1
    Ответ написан
    Комментировать
  • Как делается фронтенд для Java-приложений?

    sergey-gornostaev
    @sergey-gornostaev Куратор тега Java
    Седой и строгий
    Это вопрос не специфичный для Java. На Java, как и на любом другом языке, используются оба подхода. Когда вы разрабатываете web-сайт, например коллективный блог, типа Хабра, развитый фронтенд вам не нужен, вы можете не тратить ресурсы на его разработку, вам будет проще продвигать его в поисковых системах. Когда вы разрабатываете web-приложение, например онлайн-банкинг, типа online.sberbank.ru, вам нужен фронтенд, вам нужен SPA, так как пользователь ожидает такой же UX, как от десктопного приложения.
    Ответ написан
    Комментировать
  • Как делается фронтенд для Java-приложений?

    azerphoenix
    @azerphoenix Куратор тега Java
    Java Software Engineer
    Здравствуйте!
    Я думаю, что все в первую очередь зависит от бюджета выделенного на проект.
    Конечно же 1-й вариант (jsp, gsp, thymeleaf, mustache, freemarker) обходится дешевле, чем полноценная разработка фронта на javascript фреймворках (Angular, React, VueJS и др).
    В первом случае на Java делается бекенд на обычных контроллерах (если речь идет о Spring). Во втором случае разрабатывается RESTful сервис, который будет взаимодействовать с js-фреймворком..
    Ответ написан
    5 комментариев
  • Java для бэкенда?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега Веб-разработка
    За серьёзным бэкендом стоят серьёзные разработчики, а не серьёзные языки. Учите парадигмы, высокоуровневые (паттерны) и низкоуровневые (работа памяти, GC, сети) вещи и смежные дисциплины, а не языки программирования.
    Ответ написан
    4 комментария
  • Насколько безопасно использование google fonts в email?

    sharnirio
    @sharnirio
    Front-end developer
    Насчет того будет ли правильно отображаться во всех трех браузерах неподскажу, так как это лучше точечно протестировать в каждом отдельно, но вот вспомнил про вот эту статью - link там есть пример как правильно подключать кастомные шрифты с того же Google fonts, так как подключение через link не сработает.
    Ответ написан
    Комментировать
  • Какое разрешение должно быть у мобильной версии сайта?

    Basitkhan
    @Basitkhan
    full-stack developer, ux/ui designer
    Бросай фотошоп и переходи на Figma.
    Возможностей конечно меньше, но он как раз запилен под построение интерфейсов.
    Не надо мучиться с размерами (там и так есть размеры), установишь на телефон Figma mirror и сразу тестишь на телефоне, это векторная графика, поэтому тебе не придётся работать с разрешением, просто отправляешь артбоарды в зеплин разраба, а потом он там уже е*ошит!)))
    Ответ написан
    Комментировать
  • Можно ли двигаться дальше по карьерной лестнице в web разработке?

    astec
    @astec
    Разработчик https://debtstracker.io/
    Не утрируете, но это мало кому интересно.

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

    Востребованы будете. Думаю по ЗП тоже будет лучше, особенно если готовы к переезду. Про то что не работали с системами контроля версий лучше не говорить, а подучить Git и немного поиспользовать хотя бы для себя, а ещё лучше внедрить в компании.

    В разных компаниях происходит по разному. Много где бардак и много где хорошо. Но точно нигде не идеально.
    Ответ написан
    Комментировать
  • Чем спецификация отличается от API?

    sergey-gornostaev
    @sergey-gornostaev Куратор тега Hibernate
    Седой и строгий
    В данном случае отличается тем, что это стандартная спецификация. Разработчик Hibernate может как угодно и когда угодно поменять своё API, но JPA меняется только в процессе многолетнего согласования примерно полусотни крупных компаний.
    Ответ написан
    2 комментария
  • Как правильно размещать дублирующиеся страницы на сайте?

    Grinvind
    @Grinvind
    Помогаю увеличивать трафик с поисковых систем
    Плюсую к предыдущему ответу, от себя добавлю, что хлебные крошки к url привязывать не стоит, а все товары лучше положить в отдельную директорию, например, /tovary/ или /product/. Таким образом вы не будете размывать краулинговый бюджет на товары (если, конечно, у вас не преобладает товарный спрос над категорийным).
    Ответ написан
    Комментировать
  • Как правильно размещать дублирующиеся страницы на сайте?

    alams_stoyne
    @alams_stoyne
    Full Stack Developer - #PHP #CSS #JS #DB
    У товара должен быть "Основной каталог" к которому он относится.

    Например основной URL товара это "каталог/подкаталог/товар" и пофиг с какой раздающей ты переходишь на страницу товара, путь это будет "каталог" или "каталог/подкаталог" или даже "акции/супер предложения" адрес у страницы товара должен быть всегда один и тот же (т.к дубли это зло).

    Даже если товар в нескольких категориях например "каталог/подкаталог1" и "каталог/подкаталог2" адрес меняться не должен.
    Ответ написан
    Комментировать
  • Нужен ли фриланс верстальщику?

    @d-sem
    Если есть на что жить, то лучше сосредоточиться на развитии. Тем более вся жизнь впереди. Потому развивайтесь как разработчик, а не верстальщик и идите на работу в команду для дальнейшего развития.
    Ответ написан
    Комментировать
  • Что значит запись: Collection collection = new ArrayList();?

    Maksclub
    @Maksclub
    maksfedorov.ru
    Изучите ковариантность и контрвариативность типов.
    А также Liskov Substitution Principle из SOLID — объяснит мотивацию такого написания.
    Должно стать понятнее.

    Пример:
    Когда вы говорите, что во дворе стоит авто (Тип переменной), полученная (оператор присваивания) покупкой Nissan Quashkai, то выражение верно логически по соблюдению типов.
    Наоборот — нет, тк при покупке машины во дворе не появится Quashkai гарантированно :)


    Иными словами ArrayList является также Collection и потому все переменные этого типа являются корректными коллекциями,
    Также как все Ниссан кашкаи являются корректными автомобилями
    Ответ написан
    Комментировать
  • Что такое монада?

    azerphoenix
    @azerphoenix Куратор тега Java
    Java Software Engineer
    1 комментарий