БАЗЫ ДАННЫХ db. SQL, REDIS, СУБД

от автора

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

«настройка передачи данных из точки А в точку Б»

Повторюсь! Кажется при чем тут летящая машина. А это символика того, что данные летят из точки А в точку Б.

Кажется, при чем тут летящая машина. А это символика того, что данные летят из точки А в точку Б. Повторюсь! Кажется, при чем тут летящая машина. А это символика того, что данные летят из точки А в точку Б. Так же осмелюсь сказать, что так ощущается отрыв от реальности, когда ты погружаешься в любую тему со мной =) Поначалу это утверждение показалось мне недостаточно полным. Однако если убрать шелуху и оставить только суть, то программирование это буквально передача данных. Во время этого процесса мы можем изменять данные, но по итогу мы получаем буквально движение информации из точки А в точку Б.

И сегодня мы поговорим про точку Б — Базу данных.

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

Таблица — это удобный и структурированный подход к хранению данных, представляет собой колонки с названиями и строчки записей. Базы данных представляются в виде таблиц.

Реляционная БД (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/