Если серьезно, то сегодня мы поговорим про БАЗЫ данных. Как-то один мой друг разработчик сказал, что программирование можно понимать как
«настройка передачи данных из точки А в точку Б»

Повторюсь! Кажется при чем тут летящая машина. А это символика того, что данные летят из точки А в точку Б.
Кажется, при чем тут летящая машина. А это символика того, что данные летят из точки А в точку Б. Повторюсь! Кажется, при чем тут летящая машина. А это символика того, что данные летят из точки А в точку Б. Так же осмелюсь сказать, что так ощущается отрыв от реальности, когда ты погружаешься в любую тему со мной =) Поначалу это утверждение показалось мне недостаточно полным. Однако если убрать шелуху и оставить только суть, то программирование это буквально передача данных. Во время этого процесса мы можем изменять данные, но по итогу мы получаем буквально движение информации из точки А в точку Б.
И сегодня мы поговорим про точку Б — Базу данных.
Обычно итоговые данные нужно хранить, передавать и обрабатывать. Хранение данных как раз обеспечивают базы данных. Обычно для хранения используют именно таблицы, хотя есть еще и хранилища, которые тоже внутри себя представляют таблицы, которые пользователям не показывают. И формат хранения определяется фундаментальной возможностью таблиц. Всего у нас два основных инструмента:
Таблица — это удобный и структурированный подход к хранению данных, представляет собой колонки с названиями и строчки записей. Базы данных представляются в виде таблиц.
Реляционная БД (PostgreSQL, MySQL, Oracle) — из названия следует relation — связь. Связанные таблицы — это мощный инструмент для хранения данных и дальнейшего использования. Создание связи между таблицами позволит вам быстро находить информацию. Например, у вас есть таблица «мировая таблица Бренды АВТО» — внутри есть разные бренды: AUDI, Mercedes, BMW. В свою очередь, внутри, например, бренда BMW вы можете увидеть связанную таблицу автомобилей M5, 320i xDrive, M8, а внутри каждого автомобиля — таблицу с характеристиками. В такой таблице у вас появляется возможность быстро находить связанные данные и выгружать их.
Нереляционная БД — очевидно, что тут речь идет уже о самостоятельных таблицах, которые не имеют никаких связей. Это менее удобный и ограниченный инструмент для хранения данных, потому что мы не сможем связывать данные из разных таблиц между собой. У вас есть одна таблица, которая ни с чем не связана. Грустно. Однако у нее есть свои плюсы: такие таблицы удобны, когда вам достаточно одной таблицы, но необходима скорость поиска.
Вернемся к реляционным базам. Чаще всего в разработке приложений мы работаем с такими БД, потому что, как уже говорил выше, мы можем связать сущности и доставать необходимые данные в удобном формате. Вы, вероятно, обратили внимание на названия таких БД и заметили там приписку SQL.
SQL (Structured Query Language) — это структурный язык запросов, предназначенный для управления, хранения, изменения и извлечения данных в реляционных базах данных.
СУБД (система управления базами данных) — это комплекс программного обеспечения, предназначенный для создания, хранения, изменения, поиска и администрирования данных в электронном виде.
Что такое язык? На нем кто-то говорит? Да, посредством такого языка мы общаемся с БД. Пример: у вас есть друг-библиотекарь, он работает в библиотеке. В данном примере библиотека — это наша база данных, например PostgreSQL, а библиотекарь, то есть ваш друг, — это и есть движок на базе SQL. Вы говорите ему: «Принеси мне все книги по физике», и он это делает. Но так как мы общаемся посредством языка не с человеком, а с программой, то мы должны использовать строгий язык, понятный движку SQL. Правильно написанные запросы позволят доставать сгруппированные, отфильтрованные данные.
Теперь коснемся той части, с которой мы работаем как Java-разработчики. Мы пишем код, то есть достаем данные из нашего хранилища БД. Получаем, изменяем, сохраняем и удаляем данные. Нас интересует, как мы в коде можем взаимодействовать с БД. То есть мы можем писать запросы на SQL вручную. Но благо есть инструменты, которые ускоряют написание запросов.
Самый низкоуровневый инструмент — это JDBC.
JDBC (Java Database Connectivity) — прямой API для отправки SQL в БД из Java. Ты пишешь строку запроса, выполняешь, получаешь ResultSet. Такой инструмент дает нам возможность получать данные из БД и сохранять. Однако мы получаем данные из БД в определенном формате ResultSet — табличные данные в виде строк и колонок, а нам нужно получить объекты. Ты сам вручную пишешь rs.getString(«name») и кладешь в поле объекта, мапя соответствующие поля, и получаешь удобный и понятный объект для дальнейшей работы в среде Java.
Термины:
JPA (Java Persistence API) — это спецификация ORM (Object-Relational Mapping). Hibernate — самая популярная реализация. Ты работаешь с объектами Java, а JPA сама генерирует SQL, управляет кешем первого уровня, lazy-загрузкой.
Hibernate — это реализация спецификации JPA. Но если JPA — это только интерфейс (набор аннотаций и правил), то Hibernate — это конкретный код, который:
-
Читает твои Java-классы с аннотациями (@Entity, @Id, @OneToMany).
-
По ним генерирует SQL-запросы (CREATE, INSERT, SELECT, JOIN…).
-
Выполняет эти запросы через JDBC.
-
Забирает ResultSet и превращает строки обратно в Java-объекты.
ORM (Object-Relational Mapping) — это инструмент для маппинга, то есть соединяет по названию колонки значения с полями в объекте и возвращает объект. То же самое делает в обратную сторону.
JPA — это спецификация, которая еще сильнее облегчает работу с базами данных за счет сильного инструмента Hibernate. Как видно, всю магию кибер берет на себя, генерирует запросы. Внутри JPA-спецификации есть набор базовых обращений в БД. Есть еще более удобная надстройка над JPA.
JpaRepository — это часть Spring Data JPA (надстройка над JPA). Чистый JPA (без Spring) использует EntityManager и методы persist(), find(), merge(), remove(). Spring Data JPA добавляет удобные репозитории с готовыми методами. Писать ничего не надо, всё уже есть. Если вы наследуете от JpaRepository<Type_of_Object, Type_for_ID> — внутрь дженерика передаете сначала объект, который берется за основу при работе с таблицей, к которой будете обращаться посредством Hibernate, а вторым аргументом — тип данных для вашего id:
-
save(entity) — сохраняет объект (INSERT, если id=null, иначе UPDATE)
-
saveAll(entities) — сохраняет список объектов (пакетно)
-
findById(id) — ищет по первичному ключу, возвращает Optional
-
existsById(id) — проверяет, существует ли запись с таким id
-
findAll() — возвращает все записи
-
findAllById(ids) — возвращает записи по списку id
-
count() — возвращает количество записей
-
delete(entity) — удаляет объект
-
deleteById(id) — удаляет по id
-
deleteAll() — удаляет всё (осторожно!)
-
flush() — принудительно синхронизирует изменения с БД
-
saveAndFlush(entity) — сохраняет и сразу выполняет flush
Также будут доступны производные запросы типа поиска по одному или нескольким полям. Обычно они так и называются: findByName(). Внутри по ключевому слову пишется SQL-запрос WHERE name.
После того как мы узнали про базы данных, про язык запросов SQL, теперь различаем реляционные и нереляционные. Разработчику становится важна скорость поиска и скорость вставки информации в данных.
Если вы водитель, то вам будет понятно как работает изучение сложных программ.
Только вперед!
Только вперед! Вспомните: когда вы первые разы сидели за рулем, на чем был ваш фокус? На том, как не бросить сцепление, не перегазовать, поставить верную скорость и не заглохнуть. А тут еще и сверху внимание на пешеходов, другие машины. Ваш мозг был занят важнейшими процессами — переключение скоростей и контроль габаритов. Однако спустя три месяца вы внезапно для себя обнаруживаете, что способны построить маршрут сложнее, успеваете перестроиться, учитываете тормозной путь и в целом способны осваивать новые горизонты вождения. А спустя год-два вам становится интересно, как удержать машину в заносе. Это я о чем?
О том, что программирование — то же самое. Сначала вы тратите ресурсы на умение читать код, не путаться в синтаксисе. Все сложные концепции проходят вас стороной. Однако, когда ресурсы освобождаются, вы легко читаете код, пишете сами из головы, можете позволить себе фривольность в реализации: тут протестировать такой подход, в другом месте объединить дублирующий код. Потом позволяете себе зайти на территорию, ранее не доступную — территорию скорости и распределения памяти между ресурсами. И вот как раз на этом этапе вам будет интересно узнать, что там по скорости записи.
Помним, что любая операция чтения или записи данных — это физическое перемещение информации между носителем и оперативной памятью. В зависимости от того, где хранятся физически данные, будет зависеть и скорость их поиска. Основные носители — это жесткий диск (HDD, SSD) и оперативная память RAM.
RAM читает данные за наносекунды. SSD — за миллисекунды. Разница в 1000 раз даже на самом быстром диске.
Оперативная память — мощный инструмент, но она оперативная, потому что держит данные прямо сейчас, оперативно. Как только ты отрубаешься от сети, RAM теряет все данные. Поэтому скорость RAM следует использовать с умом. Иногда это незаменимый мощный инструмент: Redis как раз позволяет пользоваться таким инструментом, как RAM, для записи и хранения данных in-memory.
Как мы можем ускорить поиск, если пользуемся памятью на жестком диске? Есть такой инструмент, как индексы. Они позволяют…
Индекс — это отдельная структура данных, которая хранит значения и ссылки на физическое расположение строк в таблице. Пример поиска:
-
Таблица 10 млн строк, full scan = 500 000 чтений с диска → 2-5 секунд
-
B-tree индекс = 4-5 чтений с диска → 1-2 миллисекунды
Есть основные типы индексов.
B-tree работает за O(log N). Используется в PostgreSQL, MySQL, Oracle. Применяется для равенства, диапазонов и сортировки.
Хеш работает за O(1). Живет в Redis и MEMORY-таблицах MySQL. Используй только для точного совпадения. Диапазоны и сортировку сделать будет сложно, поиск связанных сущностей — тоже, но есть мощный инструмент RediSearch, который позволит проиндексировать значения хэша, который передадим как значение.
Инвертированный индекс имеет особую скорость поиска. Работает в Elasticsearch и Lucene. Можно использовать, когда нужно искать по словам и тексту.
Важную тему затронули сегодня. Мы поговорили про базы данных: про то, как они устроены, чем отличаются реляционные от нереляционных, как мы к ним обращаемся через JDBC, JPA и Hibernate, и почему Spring Data JPA с его JpaRepository делает нашу жизнь проще.
А потом мы залезли чуть глубже. Поговорили про скорость. Понятно, что теория без практики ничего не значит. Но практика без теории — тоже, я вам скажу, ни о чем. Ты вроде делаешь, закрываешь задачки, но внутри пусто, ведь сам знаешь, что не сильно копаешь и тратишь много усилий впустую, особенно когда приходит бага. Поэтому, ребят, вникайте как можно глубже.
Приятно понимать, что твои возможности безграничны, стоит лишь продолжать.
Мой ник в ТГ: @karim_product — я говорю простыми словами о сложном.
Если было полезно, поддержи подпиской. Мне будет мотивация продолжать в том же духе. Мой канал — https://t.me/+uH8Hm6kPWhU2OTc6
ссылка на оригинал статьи https://habr.com/ru/articles/1023194/