Публикую продолжение сборника вопросов-ответов с собеседований на Backend-Java-разработчика. В первой части мы прошлись по Java и Spring. А в этой поговрим о Hibernate, базах данных, паттернах и практиках разработки, об одной популярной библиотеке, поддержке и сопровождении наших приложений, а также посмотрим на альтернативные шпаргалки и подведём итоги. GitHub-репозиторий с полной шпаргалкой тут, а Хабр всё ещё торт. 3 уровня кеширования: Подробнее: Углубиться в Hibernate нам всегда поможет Vlad Mihalcea: Вопрос также связан с проблемой "N+1" и может плавно перетечь в её обсуждение. Почитать подробнее и с примерами можно в блоге Vlad Mihalcea: раз, два и про LazyInitializationException три. Проблема N+1 может возникнуть не только при использовании Hibernate, но и других библиотек и фреймворков для доступа к данным. В общем случае говорят о проблеме N+1 запроса, когда фреймворк выполняет N дополнительных запросов выборки данных, когда можно было обойтись всего одним. Соответственно от размера N зависит влияние проблемы на время ответа нашего приложения. Эту ситуацию нельзя обнаружить с помощью slow query log, ибо сами по себе запросы могут выполняться быстро, но их количество окажется большим или даже огромным. На такое можно нарваться даже при использовании plain sql (jdbc, JOOQ), когда у нас одна сущность (и соответственно таблица) связана с другой. И вот мы подгрузили одним запросом просто список из первых, а потом пошли и в цикле для каждой подгрузили связанную по одному запросу. "Да как вы это допустили!?". Да просто по запарке кто-то в цикле начал вызывать метод, у которого в глубине где-то делается запрос и привет. Как исправить? Использовать JOIN со связанной таблицей при чтении списка. Тогда понадобиться лишь один запрос. Теперь к Hibernate. Если на странице документации поискать "N+1", то можно обнаружить несколько упоминаний данной проблемы. Тут опишу самые явные и распространённые. Например, возьмём стратегию выборки FetchType.EAGER. Она склонна к порождению N+1. А в отношении @ManyToOne по умолчанию используется именно она. Забыли в своём JPQL запросе заиспользовать JOIN FETCH и привет. А если нам и не нужны были связанные сущности, то тогда стоит задать стратегию FetchType.LAZY. Если уж упомянули FetchType.LAZY, то сразу стоит сказать, что одно её наличие не гарантирует отсутствие проблемы N+1. При выборке списка сущностей, связанные автоматически не подгрузились. А мы потом пошли в цикле по загруженному списку и стали обращаться к полям связанной сущности — и снова здравствуйте. Всё тот же JOIN FETCH нас спасёт и в этой ситуации. Но JOIN FETCH во многих случаях нас может привести к декартовому произведению, и тогда будет совсем bonjour. Для отношения @OneToMany это можно решить с помощью FetchMode.SUBSELECT — будет 2 запроса, но во втором запросе на получение списка связанных сущностей в условии выборки будет подзапрос на получение идентификаторов родительских сущностей. Т.е. запрос практически повторяется и он может быть тяжеловесным. Есть вариант лучше — вычитывать связанные сущности пачками. Мы можем добавить аннотацию @BatchSize и указать размер подгружаемой пачки записей в одном запросе. Ещё варианты: Чтобы обнаружить проблему N+1, нужно писать тесты с использованием библиотеки db-util от Vlad Mihalcea. Подробнее можно прочитать у него же в блоге. А вот JOOQ умеет обнаруживать N+1 автоматически, послушать об этом можно в 17-м эпизоде (01:16:36) подкаста Паша+Слава. Подробнее в документации JOOQ. Углубиться в проблему можно: На всякий случай: составной ключ — первичный ключ, состоящий из двух и более атрибутов. Вообще про ключи есть большая статья на Хабре с ценными комментариями. Чтобы описать составной ключ при использовании Hibernate, нам необходимо создать под этот ключ отдельный класс с необходимыми полями и добавить ему аннотацию @Embeddable. Кроме того, он должен быть Serializable и иметь реализацию equals и hashcode. В самой же сущности, для которой мы описываем составной ключ, добавляем поле только что созданного класса ключа и вешаем на него аннотацию @EmbeddedId. Посмотреть примеры и углубиться в тему можно в статье Vlad Mihalcea или в документации Hibernate. Есть 4 способа отобразить наследование на БД с помощью JPA (Hibernate): Почитать ещё по теме с примерами можно: Кроме того, можно заглянуть в JavaDoc с аннотациями: Или почитать спецификацию JPA 2.2. Ещё есть книжка с толкованием данного pdf. Есть исчерпывающий ответ на этот вопрос в документации к Postgresql. Вот его перевод с сайта Postgres Pro: Но вас также может заинтересовать: Краткий конспект статьи из Википедии: На Хабре есть несколько статей по теме на любой вкус: Индекс БД — это структура данных, которая повышает скорость операций поиска данных в таблице БД за счет дополнительных операций записи и хранения для поддержания структуры данных индекса. Индексы используются для быстрого поиска данных без необходимости поиска каждой строки в таблице базы данных каждый раз при обращении к таблице БД. Ключом индекса выступает значение индексируемого столбца/столбцов. Некоторые БД позволяют строить индексы по выражениям. В основе индексов чаще всего лежат следующие структуры данных: Классификацию индексов можно разделить по различным критериям. Следует уточнить или предложить интервьюверу какой именно критерий имеется в виду.
Например, по порядку сортировки: Подробнее: Блокировка (lock) в СУБД — отметка о захвате объекта транзакцией в ограниченный или исключительный доступ с целью предотвращения коллизий и поддержания целостности данных. Пессимистическая блокировка — накладывается перед предполагаемой модификацией данных на все строки, которые такая модификация предположительно затрагивает. Во время действия такой блокировки исключена модификация данных из сторонних сессий, данные из блокированных строк доступны согласно уровню изолированности транзакции. По завершению предполагаемой модификации гарантируется непротиворечивая запись результатов. Недостатком является то, что записи могут быть заблокированы на очень долгое время, тем самым замедляя общую реакцию системы.
Пессимистическая блокировка может быть двух типов: Оптимистическая блокировка — не ограничивает модификацию обрабатываемых данных сторонними сессиями, однако перед началом предполагаемой модификации запрашивает значение некоторого выделенного атрибута каждой из строк данных (обычно используется наименование VERSION и целочисленный тип с начальным значением 0). Перед записью модификаций в базу данных перепроверяется значение выделенного атрибута, и если оно изменилось, то транзакция откатывается или применяются различные схемы разрешения коллизий. Если значение выделенного атрибута не изменилось — производится фиксация модификаций с одновременным изменением значения выделенного атрибута (например, инкрементом) для сигнализации другим сессиям, что данные изменились. Основное отличие в том, что оптимистическая блокировка приводит к накладным расходам только в случае конфликта, в то время как пессимистическая блокировка уменьшает накладные расходы на конфликт. Поэтому оптимистическая блокировка лучше всего подходит в случае, когда большинство транзакций не конфликтуют. Много статей про блокировки с кодом и картинками можно прочитать в блоге Vlad Mihalcea: Подробнее на Хабре: Подробнее в других источниках: Обычно просто просят расшифровать, что каждая буква означает с минимальным пояснением. Никто из моих интервьюеров не стал размусоливать или обсуждать. Один раз спросили, зачем оно вообще надо. Но, я думаю, чем больше сеньорности в вакансии, тем глубже и интереснее дискуссия может возникнуть. Первоисточником, сформулировавшим данные принципы, является дядюшка "Свет наш чистый код" Боб Мартин, а акроним придумал один из его читателей. Хотите погрузиться глубже? Тогда читайте часть 3 его книги "Чистая архитектура". Книжка годная, читается легко, не пожалеете. Однако пропускайте всё через свой опыт и оценивайте критически, мало ли что :) Сама по себе тема богатая и холиворная, ибо многие начинают трактовать эти принципы по-своему. На этой почве может случиться недопонимание с собеседующим, будьте аккуратны и конструктивны. Достаточно поискать статьи на Хабре по SOLID или каким-нибудь принципам в отдельности и заглянуть в комментарии. Кому-то SOLID вообще не по душе. Также, могу посоветовать, самостоятельно разобрать каждый принцип на практическом примере, ибо за это тоже могут спросить. Или заглянуть на Baeldung. Обычно советуют и в вакансиях пишут про книжку GoF. Но, на мой взгляд, сейчас есть вариант даже лучше — refactoring.guru. Там и сравнение, и на родной мове (русский, украинский, английский, китайский), и примеры на вашем любимом ЯП, прекрасные иллюстрации и диаграммы. Там также можно купить книжку по паттернам. Автору большой-большой респект. Coupling (Зацепление) — степень зависимости между программными компонентами. Должна быть низкой. Cohesion (Связность) — степень сфокусированности методов класса. Должна быть высокой. Подробнее почитать и посмотреть картинки можно тут на английском или тут на русском. Тема периодически затрагивается на Хабре в контексте построения хорошей архитектуры и написания качественного кода. Как и всегда, особую ценность составляют комментарии: Также coupling и cohesion часто упоминаются или ведут к обсуждению GRASP. Об этом можно почитать на Хабре: раз и два. Куб масштабирования (scale cube, из книги The Art of Scalability) является наглядным изображением трёх ортогональных способов увеличения производительности приложения: sharding, mirrorring и microservices. Наглядно: Почитать подробнее: Как и в случае с ООП-паттернами, существует книжка с одноимённым названием. Переведена на русский. На Хабре опубликована статья-затравочка от ИД "Питер". И есть другая книжка — Microservice Patterns and Best Practices. На Medium имеется на неё обзор. На microservices.io можно найти наглядные и интерактивные схемы паттернов микросервисной архитектуры: тут или здесь. Каждый блок на схемах — это ссылка на статью по данному паттерну. Строго рекомендую. Также на Хабре есть статья по теме. Первым же комментарием к ней советуют тоже интересную книжку — Enterprise Integration Patterns и список паттернов из неё. Теорема CAP — эвристическая теорема, утверждающая, что любая распределённая система может удовлетворять не более двум из трёх свойств: MongoDB, по мнению большинства (например), это — CP, но вот есть статья… Держим в уме, что теорема эвристическая и носит упрощательный характер. Также вызывает много споров. Про большинство БД можно найти ответы в статьях по дополнительным ссылкам ниже. Подробности: Сервис-ориентированная архитектура (SOA) — независимый от технологий, компаний и программных продуктов подход к разработке программного обеспечения на основе распределённых, слабосвязанных заменяемых компонентов с чётко определёнными интерфейсами и протоколами взаимодействия. Область охвата SOA — это всё предприятие, где происходит взаимодействие между приложениями. SOA делает ставку на доступ к бизнес-функциям предприятия через повторно используемые интерфейсы.
Сервис в данной архитектуре: Можно найти несколько источников, где MSA представляется как нечто новое и хорошее, а SOA — старое и умирающее. Часто это ещё и подаётся под соусом технологий. ИМХО — это больше маркетинговая туфта, чем хорошее сравнение. MSA и SOA не завязаны на технологии и не ограничивают в их использовании. Более того, оба подхода можно совмещать с пользой для бизнеса. Подробнее: Достоинства: Недостатки: Подробнее: Посмотреть: Композиция (composition) — отношение "является частью" (HAS-A Relationship), при котором целое явно контролирует время жизни своей составной части. Агрегация (aggregation) — отношение "является частью" (HAS-A Relationship), при котором целое хоть и содержит свою составную часть, время их жизни не связано. Подробнее: Тестовый объект (Test Double) — объекты, которые необходимы в тестах для подмены внешних зависимостей тестируемого кода
Типы тестовых объектов: Из данного набора заглушек только Mock используется для верификации поведения, остальные — для верификации состояния тестируемого объекта . Подробнее: В общем виде определение полиморфизма приводит Бенджамин Пирс в своей книге Типы в языках программирования: Полиморфизм бывает следующих видов: Безотносительно к Java полиморфизм хорошо разобран в статье Полиморфизм простыми словами.
В других источниках: REST – это архитектурный стиль, который можно применить для реализации взаимодействия web-сервисов.
Вот несколько признаков REST, которые хотят услышать большинство интервьюеров: Чаще всего, на моей практике, спрашивают какой HTTP-метод для чего должен использоваться или как назвать URI, чтобы API было RESTful. Однако, никакой спецификации или стандарта под это нет. И вообще, строго говоря, изначально REST — это архитектурный стиль, непривязанный ни к каким протоколам. Тема раскрыта в этой статье или в этой на Хабре — комментарии сочные. Вот принципы REST-архитектуры по Филдингу: SOAP — стандартизованный протокол обмена данными.
Основные характеристики SOAP: Тема горячая, интересная всем web-разработчикам, поэтому на Хабре имеется уйма статей на любой вкус.
Теоритическо-практические: Холиварно-дискусионные: Осторожно, холиворный вопрос! Возможны ожоги и прочие неприятности! На Хабре найдётся много больше одной статьи демонстрирующей народную любовь к Lombok — ведь он знаменитый победитель бойлерплейтов, сократитель кода, ускорятель кодонабора, любимый сундучок исцеляющих костылей и прочее, и прочее. О прелести Lombok вам расскажут из любого утюга.
Но как там с подводными камнями? Что с надводными вилами? Этот вопрос на собеседовании о том, как хорошо вы знаете недостатки своего любимого инструмента. И на сдачу — тест на совместимость. А ответ на вопрос стоит начать с переворота стрелочки — с хитрым ленинским прищуром спросите у интервьюера: "А как в вашем стайл-гайде написано?". Заодно узнаете о его отсутствии наличии. Ибо холиварные вопросы решить нужно однажды на сходе Архитектурного Синода и на весь простор компании сии решения распространить, увековечив их в стайл-гайде, дабы раскола избежать и не соблазнять неокрепшие умы неофитов спорами на рабочем месте посреди спринта горящего. Самая лайтовая и, возможно, притянутая за уши причина не использовать Lombok — плагин постоянно отваливается с выходом новой версии IDEA. А если отваливается плагин, то работа летит коту под хвост. Но это уже уходит в прошлое! С версии 2020.3 Lombok-плагин будет поставляться вместе с IDEA. Большинство из проблем с Lombok связаны с особенностями и нюансами реализации как самих аннотаций библиотеки, так и сторонних фреймворков и библиотек. Например, эксперты по Hibernate советуют (например тут) избегать использования Lombok в entity-классах. Хотя бы исключить использование @EqualsAndHashCode, @ToString и @Data, потому что генерируемый ими код для методов equals, hashCode и toString может либо отработать неверно, либо привести к потере производительности или исключениям в некоторых определённых случаях для entities. А как нужно правильно реализовывать данные методы для entities написано тут или тут. Интересная дискуссия, затянувшаяся на два дня, была по данному вопросу в чатике подкаста "Паша+Слава" 7-8 марта — начало тут. В ней asm0dey задаёт каверзные вопросы по использованию Lombok и рассказывает почему он забанил Lombok как тимлид. Например: "Что произойдёт если библиотека, которую ты используешь, использует @SneakyThrows и ты её вызываешь в рамках транзакции?" (об этом можно почитать тут), "Тебе наверное нравится аннотация @Data. Как она строит equals для коллекций и для массивов?", "Знаешь ли ты что @EqualsAndHashCode может вызвать StackOverflowException и тебе даже нечего будет дебажить?". Главная проблема Lombok — простота хуже воровства — его волшебная лёгкость провоцирует не читать документацию, что потом может повлечь за собой горе и страдания. Но может и не повлечь — тут как задача в джиру ляжет. Не даром не заросла к Lombok народная тропа — можно ограничиться использованием некоторого минимального джентельменского набора из Lombok, следить за собой. Но всё-таки мойте руки, предохраняйтесь читайте документацию перед активным использованием ваших инструментов, ведь они могут иметь неочевидные нюансы и сюрпризы в реализации. P.S. У Lombok есть занятная экспериментальная фича onX — у неё интересный синтаксис (@__ правда только для Java 7) и его описание в документации. А вот тут рассказывается зачем оно вам может понадобиться, например в Spring-приложении. Подробнее: Ответ на этот вопрос писали в интернете ещё тогда, когда я балду в школе пинал (нулевые), поэтому у читателя есть широкий выбор.
Мне импонирует заметка на OpenNET — кратко и чётко, суше Сахары.
Для любителей смотреть в окна есть статейка на MSDN.
Вишенка на торте — олдовая статья с Хабра. Благодаря нагрузочному тестированию появляется возможность оценить производительность приложения при различных нагрузках от действий определённого количества пользователей приложения.
Существует несколько решений для проведения подобного рода тестирования: Подробнее: Виртуальная машина (Virtual Machine — VM) — программная и/или аппаратная система, эмулирующая аппаратное обеспечение и исполняющее приложения некоторой целевой платформы на платформе хоста. Иными словами — это виртуальный компьютер с виртуальными устройствами и независимой операционной системой, управлением памятью и другими компонентами. Т.о. на одном реальном компьютере может существовать несколько виртуальных, изолированных друг от друга, со своими ОС. Запускаемые внутри виртуальной машины приложения не имеют понятия о её виртуальности и взаимодействуют с ней как с реальной. Хотя VM изолирована от реального хоста, она может иметь доступ к его диску и периферийным устройствам. Существует возможность сделать backup'ы VM в виде текущего состояния системы и содержимого дисков для возможности последующего восстановления системы в исходное состояние.
Каждая VM несёт дополнительные расходы на эмуляцию оборудования и работу своей ОС.
Управляет виртуальными машинами на реальном железе гипервизор — это программное и/или аппаратное решение, процесс, который отделяет операционную систему компьютера и приложения от базового физического оборудования.
Самими популярными решениями виртуализации являются KVM с QEMU, XEN, решения от VMWare и Hyper-V. Docker — программное решение, использующее виртуализацию на уровне операционной системы для доставки, развёртывания, изоляции и упаковки приложения со всеми его зависимостями в т.н. контейнер.
Контейнеры расходуют меньше места на диске и ресурсов хоста, потому что переиспользуют большее количество общих ресурсов хоста и обеспечивает виртуализацию на уровне ОС. Каждый из контейнеров работает как отдельный процесс основной ОС, у которого есть своё собственное виртуальное адресное пространство. Изоляция контейнеров достигается с помощью linux namespaces. Ограничить потребление системных ресурсов контейнером можно через cgroups.
Контейнер создаётся из образа, который в свою очередь основан на Dockerfile проекта, и представляет собой набор файловых систем (слоёв) наслоённых друг на друга и сгруппированных вместе, доступных только для чтения. Контейнеры всегда создаются из образов, добавляя свой собственный доступный для записи верхний слой, в который вносятся необходимые для контейнера в процессе работы изменения. Это позволяет нескольким контейнерам переиспользовать один образ.
Docker-контейнеры используются различными системами оркестрации и CI/CD.
Для существует большой общедоступный репозиторий Docker-образов — Docker-Hub
Кроме Docker, существуют и другие контейнеры. На Хабре есть множество статей на тему Docker: А также на тему виртуализации: Кроме того, можно углубиться в других источниках: Контейнер сервлетов — это компонент веб-сервера для взаимодействия с Java-сервлетами — обеспечивает их жизненный цикл, сопоставление URL-адреса к определённому сервлету и имеет ли отправитель запроса по данному URL необходимые права доступа и т.п.
Примеры (также являются самостоятельными веб-серверами, но НЕ серверами приложений (реализуют неполный стек Java EE)): Исчерпывающий ответ на вопрос "Зачем нужны сервера приложений, если есть Tomcat" можно найти в одноимённой статье. Собеседование Backend-Java-разработчика охватывает широкий диапазон используемых в работе практик и инструментов. Судя по дополнительным источникам, которые я приводил в ответах, на Хабре можно отыскать статьи по каждому вопросу, однако много источников уровня "Нетленная классика" лежит за его пределами. Например, в докладах с конференций JUG.RU. Если у вас есть интересный вопрос с интерьвю, которым вы хотите поделиться, то вы можете написать его в комментариях или открыть PR в репозитории на GitHub с данной шпаргалкой. Внимание! Будьте бдительны и осторожны! Во всех подобных статьях (моя не исключение) и сборниках могут прятаться ошибки. Помните, что лучшие источники знаний — это исходный код и документация. Кроме того, зазубривание ответов на вопросы выше не даёт гарантий успешного трудоустройства, хорошой работы и шелковистости волос.