
Илья Сазонов
Всегда.Да
Нам хотелось бы обсудить один вопрос разработки на Spring в связке c JPA.
Одна из самых распространенных проблем производительности здесь — это исчерпание коннекшен пула.
Но как это ни странно, единственный способ управлять жизненным циклом соединения в Spring — это аннотация @Transactional. Ее применение автоматически приводит сразу к трем действиям.
Единственный способ средствами фреймворка сделать разделяемую сессию (то есть EntityManager) без создания транзакции — это OSIV, что признано антипаттерном большинством специалистов.
Получается, что проблема исчерпания соединений в связке Spring с JPA есть, а инструментов для тонкого контроля коннекшенов нет.
Поэтому код, в котором мы сначала получаем @Entity из базы данных, потом делаем HTTP-запрос в сторонний сервис, а потом получаем из ранее загруженной сущности ленивую коллекцию, писать нельзя.
Но многие разработчики об этом не знают, поэтому такой код можно найти практически в любом сервисе любой компании. Когда нагрузка растет, эти проблемы выплывают наружу и для их исправления требуются экспертные знания и героические усилия.
Если бы у нас была аннотация наподобие @Transactional, которая в начале работы метода открывает EntityManager и закрывает его, когда работа метода закончена, перечисленных выше проблем просто не было бы. Можно было бы в одной транзакции загрузить @Entity, а в другой — загрузить связанную коллекцию. А в промежутке сделать HTTP-запрос.
Помните OSIV — Open Session In View, признанный антипаттерном? Нам нужна аннотация, которая превратит антипаттерн в полезный инструмент. Аннотация, которая откроет сессию на время работы метода Open Session In Method. А дальше внутри этого метода можно будет делать разные транзакции, которые будут работать с одной сессией. И такую аннотацию может сделать каждый — нужно просто применить Spring AOP.
Конечно, остается вопрос открытого соединения. Ведь при стандартных настройках Spring не будет закрывать соединения, если EntityManager еще открыт. Тут мы сделаем еще одну аннотацию, которую можно ставить на методы, чей вызов приводит к выполнению HTTP-запросов. Эта аннотация будет закрывать соединение перед запуском такого метода, тем самым явным образом устраняя главный источник проблем — выполнение HTTP-запросов, пока есть открытое соединение с БД.
Если пользоваться этими двумя аннотациями, можно даже убрать лишние запросы в БД, которые получаются из-за передачи detached entity в EntityManager#merge.
Можно предложить новое решение Главного Вопроса Маппинга — где надо маппить Entity в DTO: в сервисе или в контроллере?
И еще это дает проектам, в которых есть код, делающий HTTP-запросы во время выполнения транзакции в БД, возможность быстро поправить проблемы с производительностью, просто проставив в нужных местах новые аннотации.
Если есть такие абстракции, как сессия, соединения и транзакция, странно, что ими нельзя управлять по отдельности. Мы расскажем, как это исправить.
Всегда.Да
Сбер