Pull to refresh

Comments 29

А в чём посыл статьи? Ну поворчать ТС поворчал. Дальше что? Предложения будут? Кому надо - Query. Кому надо - хранимые процедуры и функции. Но как-то оно с Entity уже привычно и покрывает довольно много задач. Больше бы конкретики к этим "фи", а так какой-то вброс, а не статья.

Spring Data JPA считается швейцарским ножом для работы с БД в Java. 

Да просто статья начинается вот с этого. Кем считается, почему считается? Непонятно, откуда взят данный постулат. Это раз.

И второе, наверное даже более важное: а автор вообще швейцарским ножом пытался когда-нибудь делать серьезную работу? Швейцарский нож - это карманный универсальный инструмент, который плохо делает почти все, что умеет. Хлеб им порезать проблематично, пассатижи там если и бывают - то гавно, отвертка - тоже. Поэтому само сравнение не совсем корректное - если предмет данной статьи делает хорошо хоть что-нибудь - то просто определите для себя, в каких задачах вы его употребляете, а для других задач найдите инструмент более подходящий, благо их существует достаточно много. Даже голый JDBC может быть осмысленным вариантом где-то (хотя я лично остановился на Spring Jdbc Template).

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

Ну мне посыл статьи не показался таким уж очевидным. А может сравнение как раз и смутило.

Spring Data это реально швейцарский нож, просто проблема, что ножом то как бы тоже можно неправильно пользоваться. Если у вас MSA с компактными сервисами то почему бы и нет, быстро и надежно, если монолит то возможно к использованию есть вопросы, но опять же очень много аспектов, не всё не так однозначно

Это старый добрый холивар: ORM vs no ORM?

С очень слабыми аргументами и отсутствием альтернатив.

Я так понимаю, вы против ORM (или hibernate/JPA) в целом, поскольку spring data это всего лишь обертка поверх. Тот же @Entity вообще в JPA спеке.

Написание SQL ручками? И вы утверждаете, что проблем:

  • нужно поменять порядка 10 файлов для того чтобы добавить/изменить хоть какую-то функциональность

  • Вся валидация исключительно в Runtime,

с чистым SQL нет?

Если у вас есть 10 запросов, и вы добавили поле, будьте добры все 10 запросов обновить, даже если вы поле всего лишь читаете, чтобы положить в POJO. А если забыли ещё запятую в конце добавить, то о проблеме вы тоже узнаете только в рантайме.

Кстати, как насчет запроса к промежуточной таблице в случае n:n связи?

А как насчёт этого запроса, если у вас промежуточная таблица - другой (микро)сервис? Или такую проблему уже решить нереально? А если реально, то почему такой же подход нельзя использовать для другой таблицы, но в том же приложении?

В общем: "нормально делай, нормально будет"

ПС я главный хейтер спринга в компании. И не то, чтобы фанат hibernate, но как говорится "Демократия ORM – наихудшая форма правления технология для работы с БД, если не считать всех остальных."

Демократия ORM – наихудшая форма правления технология для работы с БД, если не считать всех остальных

Jooq же. Отличная промежуточная технология.

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

Но как будто бы, количество бойлерплейта там всё равно слишком велико для меня. Но безусловно, она решает многие недостатки sql и выбор между jooq/orm сложнее, чем sql/orm.

Да что тут думать, hibernate как и spring data jpa НЕ нужны, это лишь дополнительный слой абстракции замедляющий работу. Видимо их используют те, кто не смог в чистый SQL (но как вы тогда собеседование прошли? Задачки по SQL теперь постоянно спрашивают на лайвкодинге)

Пока что лучший вариант для меня - spring jdbcTemplate, где можно писать нативные запросы

Можно использовать вместе Spring Data JDBC (заберёт большую часть рутины) и Spring JDBC (для всего остального).

Слышал про него, но у нас на проекте, к сожалению, не используется

Видимо их используют те, кто не смог в чистый SQL

Когда я только начинал карьеру программиста, я слышал похожую фразу: "джаву используют те, кто не сумел в c++". Степень адекватности фразы для меня одинаковая. Продолжая аналогию: "машину используют те, кто не сумел в ходьбу".

Я поработал на продукте, где люди придерживаются вашего мнения и пишут на sql. Проблемы орм мне показались цветочками:

  • На обновление модели: будь добр обновить все запросы

  • Если ты их не обновил, то когда-нибудь что нибудь на проде не будет работать. Но это не точно

  • Кривые и неоптимальные sql запросы. Потому что это так классно писать сложные запросы в бд, вместо нормальной архитектуры

Продолжая аналогию: "машину используют те, кто не сумел в ходьбу".

Так если бы машину... А то мне тут вместо ходьбы (SQL) предлагают трактор (spring data jpa). Нет уж, я лучше пешочком.

На обновление модели: будь добр обновить все запросы

Кривые и неоптимальные sql запросы.

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

Интересно было бы проверить у тех, кто топит за hibernate/data jpa, как у них со знанием SQL, есть подозрение что люди разучились ручками писать сложные запросы.

Сложные это какие? И как из одного следует другое?

JPA ограничен в выразительности, отсюда следует, что написать на нём некий произвольный запрос будет сложнее.

Да хотя бы любой select длиннее пяти строчек.

Знаю некоторых программистов, которые давно разучились и такое писать, поэтому используют/пропагандируют ORM (hibernate/spring data jpa)

Да хотя бы любой select длиннее пяти строчек.

Конкретный пример, пожалуйста. И пример кода на Spring Data JPA, который бы делал то же самое. Потом оценим насколько легко написать этот код и насколько сложно забыть как это сделать на чистом SQL.

SELECT o.id
       o.creation_date,
       o.number,
       o.contractor_id
  FROM order o
 WHERE o.id = :user_id

Пойдёт?

Я пропагандирую repository.findById(id) вместо этих 6 строк.

Вся и проблема имхо что основной массе нативный SQL использовать сложно ввиду того что мозги там надо поворачивать в сторону алгоритмизации работы с наборами данных а не с линейными алгоритмами обработки на фронтенде для бэкенда. Web испортил многих и кроме трех операторов SQL в ограниченном контексте мало кто знает сам SQL хорошо. Отсюда и растут ноги ORM. Для. Web приложений хватает и ладно. Хуже то что все эти web примочки пацаны тянут в приложения , которые работают в чистом локале, напихивают туда докеры, Джанги и проч., без необходимости, а потом тихо сваливают на сторону с указанием в резюме на "опыт", оставляя другим свои проблемы доработок, зависимостей и рефакторинга. Я имею ввиду не продажные приложения, а внутренний софт технологических и производственных компаний

Посыл правильный - Hiber устарел. ему давно пора на свалку истории.

Но с аргументацией проблемы.

Например, я не понял, чем плох Entity (точнее, я знаю, что там не так, но в статье про это не говорится).

Чем плох flyway? Чем плох рефлекшен и рантайм? проверять качество работы c SQL кроме как в рантайм не где.

Про проблемы работы с вложенными сущностями - ничего нет.

Про проблемы работы с кешем 2 уровня - ничего нет.

Про проблемы работы с java классами - тоже ничего нет.

Дальше, критикуются генерируемые методы в интерфейсах, но это не Hiber и не JPA, это spring data. Это другой фреймворк, на базе его есть Spring Data JDBC или Spring Data Mongo. Ну кто-то написал дикий г-код, мы же не критикуем за это язык программирования?

Дальше, чем не угодили слои приложения. Отделяйте мух от котлет, в хорошей крупной системе слои приложения очень даже нужны. Да, пример из спринга - плохой с точки зрения. ну давайте будем честными, многое в спринге написано отвратительно. Он не ОПП, не SOLID, а лишь имитирует их. Ну и что. Странно...

Итого, чтобы не было недомолвок. Я считаю, что на spring data jpa можно собрать приятный достойный проект. Но очень много старой функциональности, которая уже сильно устарела и надо сильно думать, что из JPA стоит использовать, а что пора выкинуть. Лично мне не нравится JPA с точки зрения парадигмы. На своих проектах, где я влияю на выбор технологий, я стараюсь его не использовать.

Посыл правильный - Hiber устарел. ему давно пора на свалку истории.

Даже интересно стало на что его нужно по-вашему заменить. Вдруг мне тоже надо.

Разбирал почту, увидел комментарии, поэтому отвечаю только сейчас )))

Ниже есть замечательнейший комментарий, который заменяет почти всю статью. Я бы даже свапнул, комментарий в статью, а статью в комментарий. https://habr.com/ru/articles/860038/comments/#comment_27604776

По поводу того, а чем заменить. Да чем угодно. Есть много хороших штук.

Spring Data Jdbc - остановились на нем в связке с базой на PG. Используем на третьем проекте в течении нескольких лет. Да, и к нему тоже есть вопросы. Какие-то вещи делаем через лайф-хаки. Есть баги. Есть явные глупости. Но всё же. Если, вдруг, что-то он не сможет, всегда есть возможность написать кастомный RowMapper.

Господи, на что люди готовы пойти лишь бы не использовать jooq. Даже на изучение jpa спецификации и всех неявных подводных камней хибера.

А вообще если время тратить некуда то лучше уж изучить sql и спокойно писать на jooq.

Ок, короткая шпаргалка, что такое JPA, зачем и как с ним работать.

JPA -- это Java Persistence API, фреймворк, который позволяет мапить Java объекты на реляционную базу данных.

Что это значит на практике? Допустим, у нас есть такой класс (и объекты -- экземпляры этого класса)

public class Order {
   private Map<Item, Integer> itemsAndTheirAmounts = new HashMap<>();
   private Set<PromoCode> promoCodes = new HashSet<>(); 
   private Instant createdAt = Instant.now();
   private Money totalPrice = Money.of(0, Monetary.getCurrency("RUB"));
}

... в Item и PromoCode, в свою очередь, наверчено ещё что-то и т.д.

Пока мы работаем с ними в оперативной памяти -- всё ок, у но как только начинаем работать с внешним миром, нужно их как-то различать, для этого добавляется ID.

Entity = доменный класс + ID

Поскольку оперативная память всё ещё ограничена и энергозависима, нужно сохранять (persist-ить) эти объекты где-то, например, в реляционной БД.

Далее, есть такой подход как ООП -- объектно-ориентированное программирование. Одной из его идей является инкапсуляция -- это когда поля закрыты для изменений напрямую, но есть методы, которые гарантируют бизнес-целостность объекта.

В нашем примере это могут быть addItem(...), applyPromoCode(...) и т.д., которые изменяют состояние объекта, в том числе и пересчитывая totalPrice, благодаря чему (и юнит-тестам на них, конечно) мы можем быть уверены, что объект всегда консистентен.

Ещё раз, если у нас есть addItem(...) и removeItem(...) с соответсвующими проверками, есть гарантия, что количество товаров положительное, а итоговая цена пересчитана правильно.

Далее, все изменения состояния объекта хорошо бы отобразить обратно в БД. Этим и занимается JPA, что-то вроде

class Service {
    @Transactional
    void someMethod(...) {
        var entity = repo.findById(id);
        entity.update(...);
        // нет, repo.save(entity) здесь не нужен
    }
}

Единственный ли это подход? Конечно же нет.

Ещё можно вызывать разные SQL команды, а можно вообще ничего не перегонять на бекэнд, а всё делать сразу в БД с помощью хранимых процедур. У каждого подхода есть свои преимущества и недостатки.

К преимуществам JPA можно отнести, что бизнес-логика пишется на высокоуровневом языке Java, проверяется unit-тестами, а DML операции будут произведены фреймворком.

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

Однако, тут есть диллема. Допустим, мы хотим увеличить зарплату (правильнее, конечно, говорить ставку) сотрудников некоторого отдела на 10%. Какой подход лучше

@Transactional
void increaseSalary(...) {
   List<Employee> employees = repo.findAllByDepartment(...);
   employees.forEach(
       e -> e.increaseSalaryByPercent(10);
   )
}

... т.е. "выгрузить данные из БД, изменить, записать обратно по одному" или bulk update

update emloyees set salary = salary * 1.1 where department_id = :department_id;

...?

Очевидно, что вторая команда быстрее.

Но кто сказал, что метод increaseSalaryByPercent(...) настолько прост? Там могут быть (сейчас или добавятся потом) проверки на граничные значения, правила округления и т.д.

Кроме того, написав sql update мы создали вторую точку изменения salary, теперь придётся всегда об этом помнить и держать их в согласованном состоянии.

Всё рассуждения выше были про изменение состояния объектов, JPA в первую очередь об этом.

Кроме этого обычное занятие приложения это отображать их состояние AKA "читать данные из базы".

С объектной точки зрения корректный путь здесь -- это загружать объекты из базы и преобразовывать их в DTO.

Однако, очевидно, реляционные отношения и SQL предоставляют больше возможности и производительности. Если необходимо этим можно и нужно пользоваться, плата за это -- как в примере выше с bulk update -- поддержание ДВУХ подходов в синхронизированном состоянии.

P.S. Spring PetClinic -- довольно плохой пример использования JPA. Так делать не надо.

Не могу не согласится с автором статьи, по теме жути, которая творится при использовании Spring JPA/Hiber, но поддержу и комментаторов, проблема не в них, я как-то делал свой ORM - это лучший способ понять почему hiber делает так как делает. Сначала все просто, но потом начинаешь добавлять нужную функциональность, обрабатывать краевые случаи, оптимизировать производительность и вот у тебя уже такой монстр с теми же проблемами. А в реальности все дело в JPA, как в спецификации, так что при разработке следующего метода получения данных из БД я от него ушел и копнул глубже. А дальше как в старом анекдоте "чертова гравитация"

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

Сильно все перемешано. Давайте определимся в терминах немного.

Spring Data - это просто абстрактный слой для доступа к данным. Не только к RDBMS. C ним нет никаких проблем.

JPA - спецификация для работы с RDBMS. Часть спецификаций JEE. К Spring не имеет никакого отношения. Вся критика в статье по большей части относится именно к JPA.

Híbernate - на данный момент, это одна из реализаций JPA (JPA-runtime). Нужно заметить что это не единственная реализация (есть еще EclipseLink, OpenJPA, может что-то еще). Понятно что в сторону от спецификации её реализация уходить не может (ну, почти). Так что Híbernate критиковать особо не за что. Более того, на данный момент это пожалуй лучший JPA-runtime из всех. К слову: хороший стиль при работе с JPA это нигде не опускаться до рантайма, в коде не должно быть никаких зависимостей от конкретного рантайма. В идеале. Почти никогда не достижимом идеале.

Spring Data JPA - по сути просто адаптер: Spring Data для JPA. Опять же с ним нет никаких особых проблем. Даже наоборот: он жизнь упрощает.

Что не так с JPA?

  1. Это ORM. ORM cам по себе не плох и не хорош. Это просто "технология программирования". У этого подхода есть плюсы, но есть и жирные минусы (object-relational impedance mismatch, N+1 и т.д.). Споров вокруг много и не просто так: как минимум 3 очень популярных Java библиотеки для упрощения работы с RDBMS которые не ORM (Jooq, JDBi, MyBatis) как бы намекают.

  2. JPA старая тема. И, как и некоторые другие спеки из JEE, основана на JavaBeans: мутабельность, конструктор без параметров, get/set - это все оттуда. Подход устаревший, мягко говоря. Так сейчас стараются не делать. Но... обратная совместимость с килотоннами софта написанного на JPA за 20 лет. Этот груз JPA тянет с собой и навязывает разработчикам, нравится вам это или нет. И это жирный минус.

  3. Но пусть. Вышеидущие пункты это серая зона. Вкусовщина в какой-то степени. Но самое главное - JPA это огромная и очень сложная спека. 600 страниц документации - это только спека и это вершинка айсберга. Спека ограниченная, кое-где не доконца однозначная, кое-что отдающая на откуп рантайму. Рантаймы как следствия подъезжают со своей докой не меньшего размера, c сотнями расширений (некоторые из которых критически полезны), с многочисленными особенностями конфигурации. Рантаймы, кстати, могут кое-где спеку нарушать или уходить в серую зону там, где спеку можно понять двояко. Многочисленные способы выстрелить себе в ногу в совершенно тривиальном коде (например в реализации equals). Различные API и способы сделать одно и тоже, которые внезапно могут выдавать разный результат. Вопросы с производительностью. Море багов (в EclipseLink, например, есть баги которые не фиксятся по 10 лет. :( ) Собственно сам рантайм - это много мегабайт дополнительных JARов в вашем приложении... Нужно много-много опыта и специфических знаний, чтобы умело с этим всем работать.

В реальности есть большой вопрос оправдана ли вся эта дополнительная сложность в конкретном приложении. Бывают, это правда, сценарии где оно действительно стоит того. Но сильно, сильно реже чем принято думать. JPA совершенно точно не лучший выбор "по умолчанию", но как-то так сложилось что это именно что выбор по умолчанию в Java-мире. Часто люди просто даже не задумываются что альтернативы есть и их много.

Замечание - просто огонь. Большушее вам спасибо за этот комментарий.

Sign up to leave a comment.

Articles