JPA данные, превышающие память микросервиса (минимальный фетч сайз + запись в файл). Мне кажется это очень плохо для практики, есть другие мнения? Сама статья: https://medium.com/predictly-on-tech/spring-data-jpa-batching-using-streams-af456ea611fc
При выполнении запроса к БД, Postgres по умолчанию выполняет запрос целиком и кеширует результат в виде набора всех доступных строк (https://github.com/pgjdbc/pgjdbc/blob/master/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java#L117). Далее, Hibernate обходит этот набор строк для формирования списка (https://github.com/hibernate/hibernate-orm/blob/5.5/hibernate-core/src/main/java/org/hibernate/loader/Loader.java#L1043) и последующей трансформации в список объектов, получаемых из репозитория. Полезные ссылки: https://jdbc.postgresql.org/documentation/query/#getting-results-based-on-a-cursor Для того, чтобы изменить это поведение нужно выполнить несколько условий: 1. Метод репозитория должен возвращать не массив/список, а Stream; 2. Чтобы при формировании запроса ответ целиком не выгружался в память, стоит указать размер буфера, используемого для хранения результатов из БД. 3. Так как запрос у нас теперь ленивый (Stream будет получать данные из БД по мере необходимости, например для формирования результирующего json), то на время всего запроса нужно держать открытой транзакцию; Для ознакомления можно обратиться к https://github.com/hibernate/hibernate-orm/blob/5.5/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java ; Таким образом запрос становится вида: interface CurrentEntityRepository extends JpaRepository<UUID, CurrentEntity> { @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.FETCH_SIZE, value = "1000")) @Query("select c from CurrentEntity c") Stream<CurrentEntity> findAllEntities(); } Так как для работы с открытым стримом из БД нужно держать открытой транзакцию, возникает вопрос как правильно сформировать ответ. Мы не сможем повесить на метод контроллера @Transactional поскольку маршалинг происходит после завершения метода контроллера и закрытия транзакции (для json'a - тут https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java#L440). На данный момент можно выбрать минимум 2 подхода - явный и неявный. Явный подход заключается в явном открытии транзакции для получения стрима и явном закрытии после окончания формирования ответа сервиса. В таком случае контроллер принимает вид: @GetMapping("/api/v1/entities") @Transactional(readOnly = true) void findAllEntities(HttpServletResponse httpServletResponse) throws IOException { httpServletResponse.setStatus(HttpServletResponse.SC_OK); try (var data = currentEntityService.findAllEntities()) { var writer = new BufferedWriter(new OutputStreamWriter(httpServletResponse.getOutputStream())); objectMapper.writerFor(Iterator.class).writeValue(writer, data.iterator()); } } Неявный подход - в делегировании логики работы с транзакциями фильтру виду : class StreamApiTransactionalFilter extends OncePerRequestFilter { private final PlatformTransactionManager platformTransactionManager; public StreamApiTransactionalFilter(PlatformTransactionManager platformTransactionManager) { this.platformTransactionManager = platformTransactionManager; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // Проверка, что API нужно нужно открывать транзакцию заранее TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager); transactionTemplate.setReadOnly(true); transactionTemplate.executeWithoutResult(x -> { try { filterChain.doFilter(request, response); } catch (IOException | ServletException e) { throw new RuntimeException(e); } }); } }
а что сделвать хотят?
мы делали свой маппер качающий 2гб из базы в память тк вся логика шла на торговле ассетами из памяти (биржа) так что бывает все что угодно если требует задача ) но надо хорошо подумать 1 есть ли у микросервиса диск 2 нужен ли он ему (или лучше memcache/redis) 3 не создаст ли это больше проблем чем решает
Обсуждают сегодня