Статьи

Одна вещь, которую знают хорошие весенние разработчики

В ходе моих недавних учебных занятий по (основной) среде Spring Framework меня спросили: «Если есть что-то, что должен знать разработчик (Java) Spring, что это должно быть?» Этот вопрос застал меня врасплох. Да, (базовый) Spring Framework действительно охватывает множество областей (например, компоненты, конфигурация, аспектно-ориентированное программирование, транзакции). И мне было трудно указать только одну вещь. В итоге я упомянул все, что мы освещали в нашем (3-х дневном) учебном курсе .

Если есть что-то, что должен знать разработчик (Java) Spring, что это должно быть?

Когда я больше думал об этом вопросе, я начал думать о самом важном. В итоге я подумал о том, как Spring использует аспекты для добавления поведения к управляемым объектам (обычно называемым bean-компонентами) как наиболее важное. Именно так Spring Framework поддерживает транзакции, безопасность, область действия, конфигурацию на основе Java и другие. И я делюсь своими мыслями здесь в этом посте.

В итоге я подумал о том, как Spring использует аспекты для добавления поведения к управляемым объектам (обычно называемым bean-компонентами) как наиболее важное.

ORM и исключения отложенной загрузки

Большинство разработчиков, использующих ту или иную форму ORM , столкнулись с исключением, которое означает, что дочерние объекты не могут быть загружены (например, LazyInitializationException ).

Некоторые разработчики, которые сталкивались с этим, использовали бы шаблон «открытый сеанс в представлении» ( OSIV ), чтобы оставить сеанс открытым и предотвратить возникновение этого исключения. Но я считаю это излишним. Хуже того, некоторые разработчики считают, что паттерн «открытая сессия» является единственным решением. Возможная причина этого заблуждения может заключаться в том, что разработчик, вероятно, не вооружен знаниями об эффективном использовании Spring Framework для того, чтобы сеанс ORM был открыт дольше.

В случае JPA шаблон «открытый менеджер сущностей в представлении» создаст менеджера сущностей в начале запроса, свяжет его с потоком запроса и закроет его, когда ответ будет завершен.

Итак, если бы не шаблон OSIV , что было бы лучшим решением?

Короткий ответ — использовать Spring Framework, чтобы сеанс @Transactional открытым в течение необходимого вам времени (например, @Transactional ). Продолжайте читать, поскольку я предоставлю более длинный ответ.

Услуги и хранилища

В многоуровневой архитектуре типичным шаблоном проектирования является определение домена или службы приложения (обычно определяемой как интерфейс) для обеспечения бизнес-функций (например, начать использовать корзину для покупок, добавлять товары в эту корзину, искать товары). Реализации доменных и прикладных служб обычно делегируют извлечение / сохранение сущностей домена в репозитории.

One_Thing_Good_Spring_Developers_Know

Репозитории (или объекты доступа к данным) также определяются как интерфейсы для извлечения / сохранения объектов домена (т. Е. Обеспечения доступа ORM и CRUD ). Естественно, реализации репозитория используют библиотеки ORM (например, JPA / Hibernate, myBATIS) для извлечения и сохранения сущностей домена. При этом он использует классы платформы ORM для подключения к постоянному хранилищу, извлечения / сохранения объекта и закрытия соединения (называемого сеансом в Hibernate). На данный момент проблем с отложенной загрузкой нет.

Проблема сбоев при отложенной загрузке возникает, когда служба извлекает сущность домена с использованием репозитория и хочет загрузить дочерние сущности ( после возврата метода репозитория). К тому времени, когда репозиторий возвращает объект домена, сеанс ORM закрывается. Из-за этого попытки доступа / загрузки дочерних объектов в доменной службе вызывают исключение.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Entity
public class Order {
    @OneToMany // defaults to FetchType.LAZY
    private List<OrderItem> items;
    
    public List<OrderItem> getItems() {…}
}
 
public class SomeApplicationServiceImpl implements SomeApplicationService {
    private OrderRepository orderRepository;
    
    @Override
    public void method1(…) {
        
        order = orderRepository.findById(...);
        order.getItems(); // <-- Lazy loading exception occurs!
        
    }
    
}
 
public class OrderRepositoryImpl implements OrderRepository {
    @PersistenceContext
    private EntityManager em;
    
    @Override
    public Order findById(...) {...}
    
}

Реализация репозитория явно использует JPA для своего ORM (как показано с использованием EntityManager ).

На этом этапе некоторые разработчики могут использовать выборку eager для предотвращения исключения отложенной инициализации. Сказать ORM, чтобы охотно выбрать дочерние элементы объекта заказа, будет работать. Но иногда нам не нужно загружать дочерние элементы. И нетерпеливая загрузка этого может быть ненужной накладной. Было бы здорово загружать его только тогда, когда нам это нужно.

Чтобы предотвратить исключение отложенной инициализации (и не вызывать принудительного извлечения), нам нужно держать сеанс ORM открытым, пока не вызовет вызывающий сервисный метод. В Spring это может быть так же просто, как аннотировать сервисный метод, как @Transactional чтобы сохранить сеанс открытым. Я считаю, что этот подход лучше, чем использование шаблона «открыть сеанс в представлении» (или принудительное использование активной выборки), поскольку он сохраняет сеанс открытым только на время, которое мы намереваемся сделать.

01
02
03
04
05
06
07
08
09
10
11
12
13
public class SomeApplicationServiceImpl implements SomeApplicationService {
    private OrderRepository orderRepository;
    
    @Override
    @Transactional // <-- open the session (if it's not yet open)
    public void method1(…) {
        
        order = orderRepository.findById(...);
        order.getItems(); // <-- Lazy loading exception should not happen
        
    }
    
}

Доменные объекты на уровне представления

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

Но почему вы хотите выставить доменные объекты на уровне представления?

Исходя из своего опыта, я работал с командами, которые предпочитают выставлять доменные объекты на уровне представления. Обычно это приводит к анемичной модели предметной области , поскольку структурам уровня представления необходим способ привязки входных значений к объекту. Это заставляет доменные объекты иметь методы получения и установки, а также конструктор с нулевыми аргументами. Наличие методов получения и установки затруднит применение инвариантов. Для простых доменов это работает. Но для более сложных доменов предпочтительна более богатая модель доменов, так как было бы легче применять инварианты.

В более богатой модели предметной области объекты, которые представляют значения ввода / вывода уровня представления, фактически являются объектами передачи данных (DTO). Они представляют входы (или команды), которые выполняются на уровне домена. Имея это в виду, я предпочитаю использовать DTO и поддерживать более богатую модель предметной области. Таким образом, я не сталкиваюсь с ленивыми исключениями инициализации на уровне представления.

Аспекты для добавления поведения к управляемым объектам

Spring перехватывает вызовы этих аннотированных методов @Transactional чтобы гарантировать, что сеанс ORM открыт.

Транзакции (или просто сохранение открытого сеанса ORM) — это не единственное поведение, предоставляемое с использованием аспектов. Есть безопасность, область применения, конфигурация на основе Java и другие. Знание того, что Spring Framework использует аспекты для добавления поведения, является одной из ключевых причин, почему мы позволяем Spring управлять разработанными нами POJO.

Вывод

Вот и ты. Для меня это самая важная вещь, которую разработчик Spring Framework должен знать при использовании ядра. Теперь, когда я высказал свое мнение относительно того, что является самым важным , как у вас дела? Как вы думаете, это самая важная вещь, которую нужно знать при работе с Core Spring. Ура!