ORM — отвратительный анти-паттерн
От автора перевода: Написанный далее текст может не совпадать с мнением автора перевода. Все высказывания идут от лица оригинального автора, просьба воздержаться от неоправданных минусов. Оригинальная статья выпущена в 2014 году, поэтому некоторые фрагменты кода могут быть устаревшими или «нежелаемыми».
Вступление
ORM — это ужасный анти-паттерн, который нарушает все принципы объектно-ориентированного программирования, разбирая объекты на части и превращая их в тупые и пассивные пакеты данных. Нет никаких оправданий существованию ORM в любом приложении, будь то небольшое веб-приложение или система корпоративного размера с тысячами таблиц и манипуляциями CRUD с ними. Какова альтернатива? Объекты, говорящие на языке SQL (SQL-speaking objects).
Как работают ORM
Object-relational mapping (ORM) — это способ (он же шаблон проектирования) доступа к реляционной базе данных с помощью объектно-ориентированного языка (например, Java). Существует несколько реализаций ORM почти на каждом языке, например: Hibernate для Java, ActiveRecord для Ruby on Rails, Doctrine для PHP и SQLAlchemy для Python. В Java ORM даже стандартизирован как JPA.
Во-первых, рассмотрим на примере как работает ORM. Давайте использовать Java, PostgreSQL и Hibernate. Допустим, у нас есть единственная таблица в базе данных, называемая post:
+-----+------------+--------------------------+ | id | date | title | +-----+------------+--------------------------+ | 9 | 10/24/2014 | How to cook a sandwich | | 13 | 11/03/2014 | My favorite movies | | 27 | 11/17/2014 | How much I love my job | +-----+------------+--------------------------+
Теперь мы хотим манипулировать этой таблицей CRUD-методами из нашего Java-приложения (CRUD расшифровывается как create, read, update и delete). Для начала мы должны создать класс Post (извините, что он такой длинный, но это лучшее, что я могу сделать):
@Entity @Table(name = "post") public class Post < private int id; private Date date; private String title; @Id @GeneratedValue public int getId() < return this.id; >@Temporal(TemporalType.TIMESTAMP) public Date getDate() < return this.date; >public String getTitle() < return this.title; >public void setDate(Date when) < this.date = when; >public void setTitle(String txt) < this.title = txt; >>
Перед любой операцией с Hibernate мы должны создать SessionFactory:
SessionFactory factory = new AnnotationConfiguration() .configure() .addAnnotatedClass(Post.class) .buildSessionFactory();
Эта фабрика будет выдавать нам “сеансы” каждый раз, когда мы захотим работать с объектами Post. Каждая манипуляция с сеансом должна быть заключена в этот блок кода:
Session session = factory.openSession(); try < Transaction txn = session.beginTransaction(); // your manipulations with the ORM, see below txn.commit(); >catch (HibernateException ex) < txn.rollback(); >finally
Когда сеанс будет готов, вот так мы получаем список всех записей из этой таблицы:
List posts = session.createQuery("FROM Post").list(); for (Post post : (List) posts)
Я думаю, вам ясно, что здесь происходит. Hibernate — это большой, мощный движок, который устанавливает соединение с базой данных, выполняет необходимые SELECT запросы и извлекает данные. Затем он создает экземпляры класса Post и заполняет их данными. Когда объект приходит к нам, он заполняется данными, и чтобы получить доступ к ним, необходимо использовать геттеры, как пример getTitle() выше.
Когда мы хотим выполнить обратную операцию и отправить объект в базу данных, мы делаем все то же самое, но в обратном порядке. Мы создаем экземпляр класса Post, заполняем его данными и просим Hibernate сохранить его:
Post post = new Post(); post.setDate(new Date()); post.setTitle("How to cook an omelette"); session.save(post);
Так работает почти каждая ORM. Основной принцип всегда один и тот же — объекты ORM представляют собой немощные/анемичные (прямой перевод слова anemic) оболочки с данными. Мы разговариваем с ORM фреймворком, а фреймворк разговаривает с базой данных. Объекты только помогают нам отправлять запросы в ORM framework и понимать его ответ. Кроме геттеров и сеттеров, у объектов нет других методов. Они даже не знают, из какой базы данных они пришли.
Вот как работает object-relational mapping.
Что в этом плохого, спросите вы? Все!
Что не так с ORM?
Серьезно, что не так? Hibernate уже более 10 лет является одной из самых популярных библиотек Java. Почти каждое приложение в мире с интенсивным использованием SQL использует его. В каждом руководстве по Java будет упоминаться Hibernate (или, возможно, какой-либо другой ORM, такой как TopLink или OpenJPA) для приложения, подключенного к базе данных. Это стандарт де-факто, и все же я говорю, что это неправильно? Да.
Я утверждаю, что вся идея, лежащая в основе ORM, неверна. Его изобретение было, возможно, второй большой ошибкой в ООП после NULL reference.
ORM, вместо того чтобы инкапсулировать взаимодействие с базой данных внутри объекта, извлекает его, буквально разрывая на части прочный и сплоченный живой организм.
На самом деле, я не единственный, кто говорит что-то подобное, и определенно не первый. Многое на эту тему уже опубликовано очень уважаемыми авторами, в том числе OrmHate автора Martin Fowler (не против ORM, но в любом случае стоит упомянуть), Object-Relational Mapping is the Vietnam of Computer Science от Jeff Atwood, The Vietnam of Computer Science автора Ted Neward, ORM Is an Anti-Pattern от Laurie Voss и многие другие.
Однако мои аргументы отличаются от того, что они говорят. Несмотря на то, что их доводы практичны и обоснованны, например, “ORM работает медленно” или “обновление базы данных затруднено”, они упускают главное. Вы можете увидеть очень хороший, практический ответ на эти практические аргументы, от Bozhidar Bozhanov в его блоге ORM Haters Don’t Get It.
Суть в том, что ORM вместо того, чтобы инкапсулировать взаимодействие с базой данных внутри объекта, извлекает его, буквально разрывая на части прочный и сплоченный живой организм. Одна часть объекта хранит данные, в то время как другая, реализованная внутри механизма ORM ( sessionFactory ), знает, как обращаться с этими данными, и передает их в реляционную базу данных. Посмотрите на эту картинку; она иллюстрирует, что делает ORM.
Я, будучи читателем сообщений, должен иметь дело с двумя компонентами: 1) ORM и 2) возвращенный мне объект “ob-truncated”. Предполагается, что поведение, с которым я взаимодействую, должно предоставляться через единую точку входа, которая является объектом в ООП. В случае ORM я получаю такое поведение через две точки входа — механизм ORM и “предмет”, который мы даже не можем назвать объектом.
Из-за этого ужасного и оскорбительного нарушения объектно-ориентированной парадигмы у нас есть много практических проблем, уже упомянутых в уважаемых публикациях. Я могу добавить еще только несколько.
SQL Не Скрыт. Пользователи ORM должны говорить на SQL (или его диалекте, например, HQL). Смотрите пример выше; мы вызываем session.CreateQuery(«FROM Post») , чтобы получить все сообщения. Несмотря на то, что это не SQL, он очень похож на него. Таким образом, реляционная модель не инкапсулируется внутри объектов. Вместо этого он доступен для всего приложения. Каждому, с каждым объектом, неизбежно приходится иметь дело с реляционной моделью, чтобы что-то получить или сохранить. Таким образом, ORM не скрывает и не переносит SQL, а загрязняет им все приложение.
Трудно протестировать. Когда какой-либо объект работает со списком записей, ему необходимо иметь дело с экземпляром SessionFactory . Как мы можем замокать эту зависимость? Мы должны создать имитацию этого? Насколько сложна эта задача? Посмотрите на приведенный выше код, и вы поймете, насколько подробным и громоздким будет этот модульный тест. Вместо этого мы можем написать интеграционные тесты и подключить все приложение к тестовой версии PostgreSQL. В этом случае нет необходимости имитировать SessionFactory , но такие тесты будут довольно медленными, и, что еще более важно, наши объекты, не имеющие ничего общего с базой данных, будут протестированы на экземпляре базы данных. Ужасный замысел.
Позвольте мне еще раз повторить. Практические проблемы ORM — это всего лишь последствия. Фундаментальный недостаток заключается в том, что ORM разрывает объекты на части, ужасно и оскорбительно нарушая саму идею того, что такое объект.
SQL-speaking объекты
Какова альтернатива? Позвольте мне показать вам это на примере. Давайте попробуем спроектировать класс Post. Нам придется разбить его на два класса: Post и Posts , единственное и множественное число. Я уже упоминал в одной из своих предыдущих статей, что хороший объект — это всегда абстракция реальной сущности. Вот как этот принцип работает на практике. У нас есть две сущности: таблица базы данных и строка таблицы. Вот почему мы создадим два класса. Posts будет представлять таблицу, а Post будет представлять строку.
Как я также упоминал в этой статье, каждый объект должен работать по контракту и реализовывать интерфейс. Давайте начнем наш дизайн с двух интерфейсов. Конечно, наши объекты будут неизменяемыми. Вот как будут выглядеть Posts :
interface Posts < Iterableiterate(); Post add(Date date, String title); >
Вот как будет выглядеть один Post :
interface Post
Вот так мы будем перечислять все записи в таблице базы данных:
Posts posts = // we'll discuss this right now for (Post post : posts.iterate())
Вот так создаётся новый Post :
Posts posts = // we'll discuss this right now posts.add(new Date(), "How to cook an omelette");
Как вы видите, теперь у нас есть настоящие объекты. Они отвечают за все операции, и они прекрасно скрывают детали их реализации. Нет никаких транзакций, сеансов или фабрик. Мы даже не знаем, действительно ли эти объекты взаимодействуют с PostgreSQL или они хранят все данные в текстовых файлах. Все, что нам нужно от Posts — это возможность перечислить все записи для нас и создать новую. Детали реализации идеально скрыты внутри. Теперь давайте посмотрим, как мы можем реализовать эти два класса.
Я собираюсь использовать jcabi-jdbc в качестве оболочки JDBC, но вы можете использовать что-то другое, например jOOQ, или просто JDBC, если хотите. На самом деле это не имеет значения. Важно то, что ваши взаимодействия с базой данных скрыты внутри объектов. Давайте начнем с Posts и реализуем его в классе PgPosts (“pg” означает PostgreSQL):
final class PgPosts implements Posts < private final Source dbase; public PgPosts(DataSource data) < this.dbase = data; >public Iterable iterate() < return new JdbcSession(this.dbase) .sql("SELECT id FROM post") .select( new ListOutcome( new ListOutcome.Mapping() < @Override public Post map(final ResultSet rset) < return new PgPost( this.dbase, rset.getInt(1) ); >> ) ); > public Post add(Date date, String title) < return new PgPost( this.dbase, new JdbcSession(this.dbase) .sql("INSERT INTO post (date, title) VALUES (?, ?)") .set(new Utc(date)) .set(title) .insert(new SingleOutcome(Integer.class)) ); > >
Далее давайте реализуем интерфейс Post в классе PgPost :
final class PgPost implements Post < private final Source dbase; private final int number; public PgPost(DataSource data, int id) < this.dbase = data; this.number = id; >public int id() < return this.number; >public Date date() < return new JdbcSession(this.dbase) .sql("SELECT date FROM post WHERE .set(this.number) .select(new SingleOutcome(Utc.class)); > public String title() < return new JdbcSession(this.dbase) .sql("SELECT title FROM post WHERE .set(this.number) .select(new SingleOutcome(String.class)); > >
Вот как будет выглядеть сценарий полного взаимодействия с базой данных с использованием только что созданных нами классов:
Posts posts = new PgPosts(dbase); for (Post post : posts.iterate()) < System.out.println("Title: " + post.title()); >Post post = posts.add( new Date(), "How to cook an omelette" ); System.out.println("Just added post #" + post.id());
Вы можете увидеть полный практический пример здесь. Это веб—приложение с открытым исходным кодом, которое работает с PostgreSQL, используя точный подход, описанный выше, — объекты, говорящие на SQL.
Как насчет производительности?
Я слышу, как вы спрашиваете: “А как же производительность?” В этом сценарии, приведенном несколькими строками выше, мы совершаем множество избыточных обходов базы данных. Сначала мы извлекаем идентификаторы записей с помощью SELECT id , а затем, чтобы получить их заголовки, мы выполняем дополнительный вызов SELECT title для каждой записи. Это неэффективно или, проще говоря, слишком медленно.
Не беспокойтесь, это объектно-ориентированное программирование, а это значит, что оно гибкое! Давайте создадим декоратор PgPost , который будет принимать все данные в своем конструкторе и кэшировать их внутри навсегда:
final class ConstPost implements Post < private final Post origin; private final Date dte; private final String ttl; public ConstPost(Post post, Date date, String title) < this.origin = post; this.dte = date; this.ttl = title; >public int id() < return this.origin.id(); >public Date date() < return this.dte; >public String title() < return this.ttl; >>
Обратите внимание: этот декоратор ничего не знает о PostgreSQL или JDBC. Он просто декорирует объект типа Post и предварительно кэширует дату и заголовок. Как обычно, этот декоратор также неизменяем.
Теперь давайте создадим другую реализацию Posts , которая будет возвращать “постоянные” объекты:
final class ConstPgPosts implements Posts < // . public Iterableiterate() < return new JdbcSession(this.dbase) .sql("SELECT * FROM post") .select( new ListOutcome( new ListOutcome.Mapping() < @Override public Post map(final ResultSet rset) < return new ConstPost( new PgPost( ConstPgPosts.this.dbase, rset.getInt(1) ), Utc.getTimestamp(rset, 2), rset.getString(3) ); >> ) ); > >
Теперь все записи, возвращаемые iterate() этого нового класса, предварительно снабжены датами и заголовками, полученными за одно обращение к базе данных.
Используя декораторы и несколько реализаций одного и того же интерфейса, вы можете создать любую функциональность, которую пожелаете. Что наиболее важно, так это то, что, хотя функциональность расширяется, сложность дизайна не возрастает, потому что классы не увеличиваются в размерах. Вместо этого мы вводим новые классы, которые остаются сплоченными и прочными, потому что они маленькие.
Что касается транзакций
Каждый объект должен иметь дело со своими собственными транзакциями и инкапсулировать их так же, как запросы SELECT или INSERT . Это приведет к вложенным транзакциям, что вполне нормально при условии, что сервер базы данных их поддерживает. Если такой поддержки нет, создайте объект транзакции для всего сеанса, который будет принимать “вызываемый” класс. Например:
final class Txn < private final DataSource dbase; public T call(Callable callable) < JdbcSession session = new JdbcSession(this.dbase); try < session.sql("START TRANSACTION").exec(); T result = callable.call(); session.sql("COMMIT").exec(); return result; >catch (Exception ex) < session.sql("ROLLBACK").exec(); throw ex; >> >
Затем, когда вы хотите обернуть несколько манипуляций с объектами в одну транзакцию, сделайте это следующим образом:
new Txn(dbase).call( new Callable() < @Override public Integer call() < Posts posts = new PgPosts(dbase); Post post = posts.add( new Date(), "How to cook an omelette" ); post.comments().post("This is my first comment!"); return post.id(); >> );
Этот код создаст новую запись и опубликует комментарий к ней. Если один из вызовов завершится неудачей, вся транзакция будет откачена.
Мне этот подход кажется объектно-ориентированным. Я называю это “объектами, говорящими на SQL”, потому что они знают, как разговаривать на SQL с сервером базы данных. Это их мастерство, идеально заключенное в их границах.
Доступ к данным через объектно-реляционный маппинг (ORM)
Spring Framework поддерживает интеграцию с Java Persistence API (JPA) и поддерживает нативный Hibernate для управления ресурсами, реализации объектов доступа к данным (DAO) и стратегии транзакций. Например, для Hibernate имеются первоклассные средства поддержки с несколькими удобными IoC-функциями, которые решают многие типичные проблемы интеграции Hibernate. Можно сконфигурировать все поддерживаемые функции для инструментов OR (объектно-реляционного) отображения с помощью внедрения зависимостей. Они могут участвовать в управлении ресурсами и транзакциями Spring, а также они соответствуют типизированным иерархиям транзакций и исключений DAO в Spring. Рекомендуемый стиль интеграции – кодировать DAO вместо простых API-интерфейсов Hibernate или JPA.
Spring привносит значительные средства, расширяющие функционал, в выбранный вами уровень ORM при создании приложений доступа к данным. Можно использовать столько средств поддержки интеграции, сколько пожелаете, и затем сравните эти усилия по интеграции со стоимостью и риском создания аналогичной инфраструктуры собственными силами. Можно использовать большую часть средств поддержки ORM так же, как и библиотеки, независимо от технологии, потому что все разработано в виде набора многократно используемых JavaBeans. ORM в IoC-контейнере Spring облегчает конфигурирование и развертывание. Таким образом, в большинстве примеров в данном разделе показана конфигурация внутри контейнера Spring.
Преимуществами использования Spring Framework для создания ORM DAO-объектов являются:
- Упрощенное тестирование. IoC-подход Spring позволяет с легкостью менять местами реализации и конфигурации экземпляров SessionFactory из Hibernate, экземпляров DataSource из JDBC, диспетчеров транзакций и реализаций отображенных объектов (при необходимости). Это, в свою очередь, значительно облегчает тестирование каждой части кода, связанного с постоянным хранением, в отдельности.
- Общие исключения доступа к данным. Spring может обернуть исключения из вашего ORM-инструмента, преобразуя их из собственных (потенциально проверяемых) исключений в общую иерархию DataAccessException . Эта функция позволяет обрабатывать большинство исключений постоянного хранения, которые не подлежат восстановлению, только на соответствующих уровнях, без раздражающих стереотипных перехватов, генерации и объявления исключений. Но все еще можно отлавливать и обрабатывать исключения по мере необходимости. Помните, что исключения JDBC (включая диалекты, специфичные для БД) также преобразуются в ту же иерархию, что означает, что можно выполнять некоторые операции с JDBC в рамках модели согласованного программирования.
- Общее управление ресурсами. Контексты приложений Spring могут обрабатывать местоположение и конфигурацию экземпляров SessionFactory из Hibernate, экземпляров EntityManagerFactory из JPA, экземпляров DataSource из JDBC и других связанных ресурсов. Это упрощает управление и изменение этих значений. Spring предлагает эффективную, простую и безопасную работу с ресурсами постоянного хранения. Например, в зависимом коде, использующем Hibernate, обычно нужно использовать одну и ту же Session из Hibernate для обеспечения эффективности и надлежащей обработки транзакций. Spring позволяет легко создать и привязать Session к текущему потоку прозрачным образом, открывая текущую Session через SessionFactory из Hibernate. Таким образом, Spring решает многие хронические проблемы типичного пользования Hibernate для любого локального или транзакционного JTA-окружения.
- Интегрированное управление транзакциями. Можно обернуть ORM-код декларативным перехватчиком методов на основе аспектно-ориентированного программирования (АОП) либо с помощью аннотации @Transactional , либо явным образом сконфигурировав АОП-Advice для транзакций в XML-файле конфигурации. В обоих случаях семантика транзакций и обработка исключений (откат и т.д.) выполняются за вас. Как было описано в разделе » Управление ресурсами и транзакциями» , также можно менять различные диспетчеры транзакций, не затрагивая код, связанный с ORM. Например, можно переключаться между локальными транзакциями и JTA, при этом в обоих сценариях будут доступны одни и те же полноценные службы (например, декларативные транзакции). Кроме того, код, связанный с JDBC, может полностью транзакционно интегрироваться с кодом, который используется для ORM. Это полезно для такой организации доступа к данным, которая не подходит для ORM (например, пакетная обработка и потоковая передача BLOB), но при этом необходимо совместно использовать общие транзакции вместе с операциями ORM.
Для обеспечения более полной поддержки ORM, включая поддержку альтернативных технологий баз данных, таких как MongoDB, стоит обратить внимание на набор проектов Spring Data. Если вы являетесь пользователем JPA, руководство «Начало работы с доступом к данным с помощью JPA» с сайта https://spring.io будет отличным введением.
Общие подходы к интеграции ORM
В этом разделе рассматриваются рекомендации, применимые ко всем ORM-технологиям. Раздел, посвященный Hibernate, содержит более подробную информацию, а также демонстрирует эти возможности и конфигурации в конкретном контексте.
Основной целью интеграции ORM в Spring является четкая компоновка приложений (при любой технологии доступа к данным и транзакциях) и свободное связывание объектов приложения – больше никаких зависимостей бизнес-служб от стратегии доступа к данным или транзакций, никакого жестко закодированного поиска ресурсов, никаких труднозаменяемых объектов-одиночек, а также никаких кастомных реестров служб. Цель в том, чтобы организовать один простой и последовательный подход к обнаружению и связыванию объектов приложения, в максимально возможной мере обеспечивая возможность их повторного использования и свободу от зависимостей от контейнеров. Все отдельные функции доступа к данным можно использовать порознь, но также они прекрасно взаимодействуют с концепцией контекста приложения из Spring, предоставляя конфигурацию на основе XML и перекрестные ссылки на обычные экземпляры JavaBean, которые не обязательно должны быть совместимы со Spring. В типичном приложении Spring многие важные объекты являются JavaBeans: шаблоны доступа к данным, объекты доступа к данным, диспетчеры транзакций, бизнес-службы, использующие объекты доступа к данным и диспетчеры транзакций, распознаватели веб-представлений, веб-контроллеры, использующие бизнес-службы, и так далее.
Управление ресурсами и транзакциями
В типичных бизнес-приложениях существует нагромождение повторяющегося кода для управления ресурсами. Многие проекты пытаются придумать собственные решения, иногда жертвуя надлежащим устранением сбоев в угоду удобства программирования. Spring выступает за простые решения для надлежащей обработки ресурсов, а именно IoC через шаблонизацию в случае JDBC и применение перехватчиков АОП для ORM-технологий.
Инфраструктура обеспечивает надлежащую обработку ресурсов и соответствующее преобразование специфических исключений API-интерфейсов в непроверяемую иерархию исключений инфраструктуры. Spring вводит иерархию исключений DAO, применимую к любой стратегии доступа к данным. В случае прямого JDBC класс JdbcTemplate , упомянутый в предыдущем разделе, обеспечивает обработку соединений и правильное преобразование SQLException в иерархию DataAccessException , включая преобразование специфических для базы данных кодов ошибок SQL в осмысленные классы исключений. В случае ORM-технологий см. следующий раздел о том, как воспользоваться теми же преимуществами преобразования исключений.
Если дело доходит до управления транзакциями, класс JdbcTemplate подключается к средствам поддержки транзакций Spring и поддерживает транзакции JTA и JDBC через соответствующие диспетчеры транзакций Spring. Что касается поддерживаемых ORM-технологий, Spring предлагает поддержку Hibernate и JPA через менеджеры транзакций Hibernate и JPA, а также поддержку JTA. Подробнее о поддержке транзакций см. в главе «Управление транзакциями».
Преобразование исключений
Если Hibernate или JPA используются в DAO, то нужно решить, каким образом обрабатывать нативные классы исключений технологии поддержки постоянного хранения. DAO генерирует подкласс HibernateException или PersistenceException в зависимости от технологии. Все эти исключения являются исключениями времени выполнения и не должны быть объявлены или перехвачены. Возможно, также придется столкнуться с IllegalArgumentException и IllegalStateException . Это означает, что вызывающие программы могут работать только с теми исключениями, которые считаются критическими, если нужно избежать зависимости от собственной структуры исключений технологии поддержки постоянного хранения. Выяснить конкретные причины (таких как сбой оптимистической блокировки) невозможно без привязки вызывающей программы к стратегии реализации. Такой компромисс может быть приемлем для приложений, которые в значительной степени основаны на ORM или не нуждаются в особом обращении с исключениями (или и то, и другое). Однако Spring позволяет прозрачно применять преобразование исключений через аннотацию @Repository . В следующих примерах (один для конфигурации на Java и один для конфигурации на XML) показано, как это сделать:
@Repository public class ProductDaoImpl implements ProductDao < // тело класса здесь. >
Инструменты разработчика на Java: Spring, Hibernate, MyBatis и другие
В статье наши Java-разработчики рассказали про основные фреймворки и библиотеки, которые пригодятся в работе как тем, кто только приступает к изучению языка или уже давно разрабатывает на нем.
Spring Framework / ярослав
Что это такое
Начнем с главного: что такое Spring Framework? Это сервер приложений, то есть платформа (фреймворк), на основе которой разрабатываются приложения и которая предоставляет интерфейс для взаимодействия с внешними ресурсами, повышая уровень абстракции. Это позволяет разработчику приложения не задумываться над техническими деталями и сфокусироваться только на бизнес-логике.
Простым языком: есть сервер приложений — с одной стороны он работает с БД, с другой с фронтом, по середине ваше приложение, которое с помощью удобного интерфейса получает запросы от фронта и что-то пишет/читает из БД.
В каком случае использовать
Spring больше подходит для небольших приложений или приложений на микросервисной архитектуре. Он относительно простой, сам по себе не требует серьезных мощностей для работы. При выборе также стоит ориентироваться на доступные модули.
Основное выгодное отличие Spring в том, что можно использовать библиотеку Spring Data. Это крутая абстракция над Hibernate, которая значительно упрощает жизнь разработчика: быстрее и удобнее аналогичных решений. К тому же у нее есть несколько киллер фич. Первая — управления транзакциями через аннотации и область видимости метода. Вторая — работа с БД через «репозитории», что позволяет за 5 минут создать интерфейс с правильно названными методами, а дальше Spring сгенерирует реализацию для CRUD-операций с данными.
Когда не подходит
При разработке больших монолитных приложений. В таком случае лучше использовать JakartaEE, о котором я расскажу дальше.
Альтернативы
Помимо Spring есть и другие сервера приложений. Например, WildFly от RedHat, WebSphere от IBM, GlassFish, поддерживаемый Oracle, и тому подобное. Их всех можно поделить на сам Spring и реализации JakartaEE.
JakartaEE — это спецификация, которая дает архитектурные рекомендации по тому, как осуществлять взаимодействия приложения, например, с БД. Spring тоже можно косвенно отнести к одной из таких реализаций, но он реализует те же вещи по-своему, далеко не всегда следуя этим рекомендациям.
Советы в работе
В работе с Spring я бы посоветовал использовать Spring Boot. Это автоконфигурация для Spring, которая позволяет буквально в 3 клика запустить приложение без дополнительных настроек. Если нужно, она с легкостью перекрывается ручной конфигурацией, поэтому дилеммы «использовать или нет» не возникает.
Если вы хотите изучить все тонкости работы с фреймворком, рекомендуем наш курс разработки на Spring. Старт занятий 20 сентября, обучение бесплатное. Программа и регистрация по ссылке.
Hibernate / Иван
Что это такое
Hibernate — библиотека для Java, предназначенная для решение задач объектно-реляционного отображения. В своем начальном выпуске, в раннем 2001 году, Hibernate решал задачи ORM фреймворка, не имея даже реализации такого инструмента как Criteria API. Как бы то ни было Hibernate является реализацией спецификации JPA, которая в свою очередь является частью набора спецификаций Java EE (в современной манере Jakarta EE).
На текущее время, Hibernate решает не только задачи объектно-реляционного отображения (как и от SQL к Java-типам, так и наоборот), но и расширяет множество иных возможностей. Например:
- Использование в приложениях с Java SE, Jakarta EE, Enterprise OSGi;
- Доступность и легкость описания сущностей, связанных с таблицами в ООП стилистике;
- Возможность использования наследования, полиморфизма, абстракции и инкапсуляции, а также коллекций и так далее без необходимости что-то дополнительно подключать и настраивать:
- Высокая продуктивность. Поддержка так называемой «ленивой» инициализации, множество иных стратегий выборки данных, поддержка блокировок, автоматического версионирования и генерации временных меток;
- Масштабируемость. Позволяет безболезненно применяться в кластере серверов, в случае расширения архитектуры и так далее.
В каком случае использовать
В основе своей для разработчика Hibernate решает проблему рутинного описания собственной логики работы с SQL, однако при всем при этом, он не ограничивает вас от использования всех мощностей SQL.
Hibernate прекрасно подходит для использования в бизнес-слое приложения, избавляя от необходимости использования хранимых процедур или написания нативного SQL. Поддерживает автоматический маппинг и генерацию сущностей (Entity), представляющих собой объектное отображение реляционных таблиц. Собственно, Hibernate может применяться не только для для реляционных СУБД, но и NoSQL.
Так называемый Hibernate OGM (Object/Grid Maper — инструмент для работы с NoSQL базами) может применятся с такими NoSQL, как Cassandra, CouchDB, EhCache, Apache Ignite, и даже Redis. В том случае, если вы используете Spring Framework, он предоставляет возможность использовать Spring Data для NoSQL (Spring Data Redis, Mongo, Cassandra и другие).
Как и следует ожидать, доступ к NoSQL-базам данных осуществляется с помощью все тех же инструментов, что и с реляционными базами — в них входит концепция «репозиторий» и derived query. Однако следует учитывать, что применять JPA-концепции, относящиеся к принципам работы с реляционными СУБД, на концепцию NoSQL — очень ресурсозатратно и не всегда оправдывает такую необходимость.
Часто NoSQL применяют для быстрых и легковесных операций в том числе и распределенных операций, в таких случая важна скорость работы, чего JPA-спецификация не может гарантировать для работы с NoSQL-базами данных ввиду ее специфики (потому как JPA specification написана исключительно для реляционных СУБД — RDBMS). В первую очередь, инструменты подобные Hibernate OGM и Spring Data предназначены для удобства разработки и легкого перехода с одной базы данных на другую, даже если этот переход с SQL на NoSQL. Более подробнее ознакомиться с семейством Spring Data можно на их сайте.
Помимо всего прочего, часто Hibernate используют в связке с Spring Data JPA, понятное дело, если ваш проект — это spring-приложение. Это очень удобная и сильная связка. Spring Data JPA представляет собой слой абстракции базы данных, при этом предоставляет инструменты генерации запросов по имени метода (derived query), применяя концепцию «репозиторий» вместо устаревшей «DAO». Со стороны Spring Data JPA применяются различного рода «репозитории» — интерфейсы, предоставляющие базовые методы работы с БД, derived query, modifying query, JPQL, Criteria Api динамическое построение запросов и их выполнение в рантайме (runtime), в довесок к ним Hibernate предоставляет свой язык написания запросов HQL, а также Entity Graphs, инструменты работы с транзакциями и так далее. Так что их связка — это сила и мощь классической разработки.
Когда не подходит
Как было сказано выше, Hibernate решает проблему рутинного описания, и ORM-отображения. Отбросив весь пафос в сторону, скажем прямо, Hibernate за вас формирует запросы к базе. И так иногда получается, что запросы эти, прямо скажем, не очень. Часто возникают проблема из-за незнания специфик работы фреймворка, появляются просадки в скорости работы приложения, и хуже того, утечки памяти (например, известная проблема Eager fetch).
Так что, если вам важна скорость работы с базой, Hibernate станет не всегда вашим выбором, с учетом его persistence context, L2 cache, transaction assistance и прочими удобствами и излишествами.
Альтернативы
В качестве альтернатив можно привести безусловно MyBatis, EclipseLink, DataNucleus, iBATIS. Так или иначе, выбор инструмента зависит от вас и ваших потребностей. Можете хоть на чистом JDBC писать. Кстати, вот один из примеров, где Hibernate не может использоваться — всем известный и модно-молодежный ClickHouse не может взаимодействовать с Hibernate. Да и в общем-то, ClickHouse только с Native JDBC и работает, а лепят его, куда попало.
Советы в работе
Однозначно современный Hibernate настраивается чуть ли не из коробки: подключил и радуйся. Но, приступая к работе, важно понимать — это огромный монолит, который развивался на протяжении 20-ти лет, и следует отнестись к нему уважительно и почтить его хотя бы чтением спецификации JPA.
Необходимо знать принципы работы транзакций, ACID-принципы, принципы построения запросов внутри Hibernate, каким образом он взаимодействует с БД, что такое persistence context, что такое проблема n+1 и все вот это вот. Да и не мешало бы хоть немного знать сам SQL.
MyBatis / Ангелина
Что это такое
MyBatis — это фреймворк для взаимодействия с базой данных в Java-приложении. В отличие от своих альтернатив (например, Hibernate), он не реализует спецификации JPA. Также отличается сам маппинг объектов: например, Hibernate представляет таблицы в виде сущностей и при запросе к базе данных сам генерирует SQL-запрос, MyBatis же мапится не на таблицы, а на SQL-запросы.
Для начала работы с MyBatis необходимо определить объект интерфейс-маппер и xml-документ с описанием маппинга, где указывается SQL-запрос и типы получаемых и возвращаемых данных. MyBatis преобразует вызов метода интерфейса в запрос к базе, а результат запроса преобразуется в результат метода. Xml-файл маппинга помимо описания запроса может содержать логику используя компоненты: if, when, where, foreach и др. Вместо xml-файла MyBatis также поддерживает специальные аннотации:
- @Insert, @Select, @Update, @Delete (sql-запрос, который должен быть выполнен при вызове метода, обозначенного одной из этих аннотаций);
- @Param (указание параметров запроса);
- @Result и @Results (описывают результат запроса, который должен вернуться после вызова);
- @One и @Many (описание связей между таблицами, сопоставляя объект с коллекцией, объект с объектом).
MyBatis не умеет создавать схемы, а работает с уже существующими таблицами. При правильном подходе MyBatis может значительно увеличить скорость работы проекта в части взаимодействия с БД.
В каком случае использовать
При разработке стоит отдать преимущество MyBatis, в случаях:
- Если в приложении используются сложные запросы и связи;
- Когда в приложении имеется достаточно много различных сложных выборок из нескольких таблиц;
- Если в приложении в основном происходит работа с хранимыми процедурами;
- Если возникает необходимость написать свой ORM;
Также MyBatis более удобен в случае использования ненормализованной базы данных.
Когда не подходит
- Если в проекте много простых сущностей без сложных связей;
- Если использование автоматического генерирования запросов к базе оправдано;
- Если есть необходимость в создании и изменении схемы.
Альтернативы
Основной альтернативой является, конечно же, Hibernate. Также можно назвать:
- Ebean — ORM-фреймворк, который как и MyBatis прост и понятен в изучении. Сущности описываются знакомыми аннотациями JPA, а запросы к БД выполняются посредством вызова методов объекта Ebean.
- EclipseLink — эталонная реализация JPA, во многом схожая с Hibernate. Помимо JPA EclipseLink поддерживает ряд других стандартов и имеет особенности, которых нет в Hibernate (например, аннотации @Struct и @ReadOnly).
- Reladomo — также ORM-фреймворк, ключевая особенность которого состоит в генерации Java-классов из xml-описания структуры БД. Взаимодействие с БД происходит посредством вызова методов сгенерированных классов.
- Apache Cayenne — среда, которая позволяет генерировать ORM-объекты с помощью программы-конструктора. Создание, удаление, сохранение объектов в БД и более сложные запросы, происходят с помощью вызова методов класса DataContext.
Советы в работе
JSF / Павел
Что это такое
JSF (Java Server Faces) — это Java-фреймворк, который предназначен для разработки веб-интерфейсов пользователя. JSF позволяет проектировать пользовательский интерфейс на основе повторно используемых компонентов. Разработчик пишет xhtml-шаблоны, в которых при помощи специального языка скриптов может обращаться к атрибутам java-объектов. Шаблоны состоят из переиспользуемых компонентов, JSF из коробки предоставляет богатую библиотеку таких компонентов, но также есть возможность разрабатывать свои.
Основные преимущества JSF:
- упрощает формирование пользовательского интерфейса за счет набора повторно используемых компонентов пользовательского интерфейса;
- упрощает передачу данных приложения в пользовательский интерфейс и из него;
- помогает управлять состоянием пользовательского интерфейса при запросах к серверу;
- предоставляет простую модель установления связи между созданными клиентом событиями и кодом приложения на стороне сервера;
- упрощает повторное использование компонентов пользовательского интерфейса.
В каком случае использовать
JSF и фреймворки, базирующиеся на JSF (например, PrimeFaces и MyFaces), обычно используются в ситуации, когда необходимо реализовать монолит на Java, где и бизнес-логика, и представление пользователя будет в одном приложении. Такие ситуации возникают, когда невозможно или нет необходимости разрабатывать отдельное frontend-приложение для взаимодействия с пользователем. Обычно это корпоративные порталы, где красота интерфейса не так важна, а важнее функциональность, надежность и сопровождаемость.
Использование JSF предполагает использование готовых компонентов, поэтому позволяет относительно просто реализовывать сложные интерфейсные вещи — диаграммы, графики, интерактивные таблицы. Реализовать подобное при помощи JS-фреймворков будет сложнее как с точки зрения взаимодействия с бэкендом, так и с точки зрения реализации на фронтенде. Бэкенд и фронтенд в данном случае пишется одной командой разработчиков, приложение работает на одном сервере приложений и изменения в бизнес-логике и в пользовательском интерфейсе реализуются и выпускаются согласовано.
В основном, JSF используется при разработке корпоративных и государственных порталов. Также JSF используется в рамках стека технологий Oracle ADF, следовательно, JSF используется на портале поддержки Oracle и web-интерфейсы Oracle BPM Suite.
Когда не подходит
Фреймворк не подходит в тех случаях, когда бэкенд разрабатывается по принципу микросервисной архитектуры, и на стороне фронтенда нет необходимости реализовывать сложные статистические или табличные представления. Также в случае, когда пользовательское представление не перегружено элементами и не предполагает повторное использование множества компонентов.
Альтернативы
Основная альтернатива — не использовать JSF вовсе, а писать полноценное фронтенд-приложение.
Если речь идет о монолите на Java, то в качестве альтернатив будут выступать фреймворки PrimeFaces и MyFaces, которые сами основываются на JSF и предоставляют дополнительные библиотеки JSF-компонентов. Также есть вариант разработки приложения при помощи фреймворка Play.
Советы в работе
Самое главное, разобраться с жизненным циклом запроса, без этого использование JSF будет неэффективным. Может так получиться, что какие-то сложные вычисления или запросы в БД будут выполняться после обновления каждого элемента страницы.
Необходимо строго придерживаться паттерна MVC и четко разделять бизнес-логику и логику отображения — в противном случае код будет очень тяжело сопровождать в дальнейшем. Также необходимо определиться с одной библиотекой тегов и использовать только ее.
PrimeFaces / Ярослав
Что это такое
PrimeFaces — это OpenSource-библиотека компонентов для приложения на основе JSF, о котором уже говорили выше. PrimeFaces дополняет стандартный набор компонентов JSF, из которых можно собрать простой фронт для приложения.
В каком случае использовать
PrimeFaces хорошо подходит для создания простого UI с выводом информации в графики, таблицы и так далее. Например, для создания админок, новостных сайтов или страничек статистики.
У библиотеки есть отличный сайт — на нем можно посмотреть примеры того, что может получится с использованием этого фреймворка. А на этой странице можно узнать, какие компании используют PrimeFaces.
Когда не подходит
PrimeFaces не поможет, когда требуется создать сложный специфический фронт с множеством разнообразных интерактивных элементов. С наращиванием функционала код будет становится очень сложным для поддерживания. В таком случае вместо JSF лучше использовать JS-фреймворки (Angular или React). Тогда разработка UI уходит на фронт, а бэк превращается в RESTful-приложение.
Если требуется генератор простого html (например, для генерации email сообщения), то лучше посмотреть в сторону генераторов шаблонов. Они легче, проще и лучше подойдут для такой задачи.
Альтернативы
У PrimeFaces есть несколько альтернатив. Одни из них — набор похожих по смыслу библиотек (IceFaces, RichFaces и так далее). Они все также являются дополнениями к JSF и содержат набор готовых компонентов. Тем не менее, я бы рекомендовал использовать PrimeFaces, потому что он единственный, кто все еще получает обновления.
Есть еще альтернативы среди генераторов шаблонов (Thymeleaf, Mustache), но это скорее альтернатива JSF, а не PrimeFaces.
Советы в работе
Использовать тот фреймворк, который подходит под конкретно вашу задачу. И если выбор встает из того, выбрать ли PrimeFaces или RichFaces, Thymeleaf или Mustache, а четкого перевеса в сторону одного из решений нет, то можно выбрать то, что лучше знаешь и больше нравится 🙂
В итоге
Итак, вот основные фреймворки и библиотеки, которые пригодятся в работе программиста на Java — Spring Framework, Hibernate, MyBatis, JSF и PrimeFaces.
Spring Framework — это фреймворк, на основе которого разрабатываются приложения. Лучше всего подходит для небольших приложений или приложений на микросервисной архитектуре. Основной альтернативой является JakartaEE (WildFly, WebSphere, GlassFish).
Hibernate — библиотека для Java, предназначенная для связывания ООП и реляционной базы данных. Hibernate прекрасно подходит для использования в бизнес-слое приложения, избавляя от необходимости использования хранимых процедур или написания нативного SQL. В качестве альтернатив можно привести MyBatis, EclipseLink, DataNucleus, iBATIS.
MyBatis — это фреймворк для взаимодействия с базой данных в Java-приложении. С ним работают в тех случаях, когда в приложении используются сложные запросы и связи, имеется достаточно много различных сложных выборок из нескольких таблиц, происходит работа с хранимыми процедурами или возникает необходимость написать свой ORM. Основной альтернативой является Hibernate.
JSF — это Java-фреймворк, который предназначен для разработки веб-интерфейсов пользователя. JSF позволяет проектировать пользовательский интерфейс на основе повторно используемых компонентов. Основная альтернатива — не использовать JSF вовсе, а писать полноценное фронтенд-приложение.
PrimeFaces — это OpenSource-библиотека компонентов для приложения на основе JSF. PrimeFaces хорошо подходит для создания простого UI с выводом информации в графики, таблицы и тому подобное. Альтернатива — набор похожих по смыслу библиотек (IceFaces, RichFaces и тому подобное).
Этот список был составлен на основе опыта наших программистов. Так что кроме перечисленных здесь инструментов, существует еще много других фреймворков и библиотек, которые могут пригодиться в вашей работе.
Изучайте, программируйте и ищите новые 🙂
Object Relational Mapping (ORM) Data Access
This section covers data access when you use Object Relational Mapping (ORM).
Section Summary
- Introduction to ORM with Spring
- General ORM Integration Considerations
- Hibernate
- JPA
- Spring Framework
- 6.1.0-SNAPSHOT
- 6.1.0-RC2
- 6.0.14-SNAPSHOT
- 6.0.13 current
- Related Spring Documentation
- Spring Framework
- Spring Cloud
- Spring Cloud Commons
- Spring Data Commons
- Spring Data JPA
- Spring Data LDAP
- Spring Data Redis
- Spring Data MongoDB
- Spring Data JDBC & R2DBC
- Spring Data REST
- Spring Data Cassandra
- Spring Data Couchbase
- Spring Data Elasticsearch
- Spring LDAP
- Spring Session
- Spring Authorization Server
- Spring Vault
- Spring Security Kerberos
Apache®, Apache Tomcat®, Apache Kafka®, Apache Cassandra™, and Apache Geode™ are trademarks or registered trademarks of the Apache Software Foundation in the United States and/or other countries. Java™, Java™ SE, Java™ EE, and OpenJDK™ are trademarks of Oracle and/or its affiliates. Kubernetes® is a registered trademark of the Linux Foundation in the United States and other countries. Linux® is the registered trademark of Linus Torvalds in the United States and other countries. Windows® and Microsoft® Azure are registered trademarks of Microsoft Corporation. “AWS” and “Amazon Web Services” are trademarks or registered trademarks of Amazon.com Inc. or its affiliates. All other trademarks and copyrights are property of their respective owners and are only mentioned for informative purposes. Other names may be trademarks of their respective owners.