Если вы достаточно долго программировали на Java, скорее всего, вам нужно создавать отчеты для бизнес-пользователей. В моем случае я видел несколько проектов, использующих библиотеку JasperReports® для создания отчетов в PDF и других форматах файлов. Недавно я имел честь наблюдать, как Майк и его команда используют упомянутую библиотеку отчетов и проблемы, с которыми они столкнулись.
JasperРепорты в двух словах
В двух словах, генерация отчетов с использованием JasperReports (JR) включает три этапа:
- Загрузить скомпилированный отчет (т.е. загрузить объект
JasperReport
) - Запустите отчет, заполнив его данными (результаты в объект
JasperPrint
) - Экспортируйте заполненный отчет в файл (например, используйте
JRPdfExporter
для экспорта в PDF)
В коде Java это выглядит примерно так.
01
02
03
04
05
06
07
08
09
10
11
12
|
JasperReport compiledReport = JasperCompileManager.compileReport( "sample.jrxml" ); Map<String, Object> parameters = ...; java.sql.Connection connection = dataSource.getConnection(); try { JasperPrint filledReport = JasperFillManager.fillReport( compiledReport, parameters, connection); JasperExportManager.exportReportToPdf( filledReport, "report.pdf" ); } finally { connection.close(); } |
Благодаря классам фасадов это выглядит достаточно просто. Но внешность может быть обманчива!
Учитывая приведенный фрагмент кода (и три этапа, обрисованные в общих чертах), какие части, по вашему мнению, занимают больше всего времени и памяти? (Звучит как вопрос интервью).
Если вы ответили (# 2), заполнив данные, вы правы! Если вы ответили № 3, вы также правы, так как № 3 пропорционален № 2.
ИМХО , большинство онлайн-уроков показывают только простые части. В случае с JR , кажется, нет обсуждения более сложных и хитрых частей. Здесь, с командой Майка, мы столкнулись с двумя трудностями: ошибки нехватки памяти и длительные отчеты. То, что сделало эти трудности особенно запоминающимися, было то, что они обнаружились только во время производства (не во время разработки). Я надеюсь, что, поделившись ими, их можно избежать в будущем.
Недостаточно памяти
Первой проблемой были сообщения, исчерпавшие память. Во время разработки тестовые данные, которые мы используем для запуска отчета, будут слишком малы по сравнению с реальными рабочими данными. Итак, дизайн для этого .
В нашем случае все отчеты запускались с помощью JRVirtualizer
. Таким образом, он будет сбрасываться на диск / файл, когда будет достигнуто максимальное количество страниц / объектов в памяти.
В ходе процесса мы также узнали, что виртуализатор необходимо очистить. В противном случае будет несколько временных файлов. И мы можем очистить эти временные файлы только после экспорта отчета в файл.
01
02
03
04
05
06
07
08
09
10
11
12
|
Map<String, Object> parameters = ...; JRVirtualizer virtualizer = new JRFileVirtualizer( 100 ); try { parameters.put(JRParameter.REPORT_VIRTUALIZER, virtualizer); ... ... filledReport = JasperFillManager.fillReport( compiledReport, parameters, ...); // cannot cleanup virtualizer at this point JasperExportManager.exportReportToPdf(filledReport, ...); } finally { virtualizer.cleanup(); } |
Для получения дополнительной информации см. Пример виртуализатора — JasperReports .
Обратите внимание, что JR не всегда является виновником, когда мы сталкивались с ошибками нехватки памяти при выполнении отчетов. Иногда мы сталкивались с ошибкой нехватки памяти даже перед использованием JR. Мы увидели, как можно использовать JPA для загрузки всего набора данных для отчета ( Query.getResultList()
и TypedQuery.getResultList()
). Опять же, ошибка не появляется во время разработки, так как набор данных все еще мал. Но когда набор данных слишком велик для размещения в памяти, мы получаем ошибки нехватки памяти. Мы решили не использовать JPA для создания отчетов. Я думаю, нам просто нужно подождать, пока в JPA 2.2 станет доступен Query.getResultStream()
. Я бы хотел, чтобы JPA Query.getResultList()
возвратил Iterable
вместо этого. Таким образом, возможно, что один объект отображается одновременно, а не весь набор результатов.
Пока что не загружайте весь набор данных. Загружать одну запись за раз. В процессе мы вернулись к старому доброму JDBC. Хорошо, что JR хорошо использует ResultSet
.
Долгосрочные отчеты
Второй проблемой были длительные отчеты. Опять же, это, вероятно, не происходит во время разработки. В лучшем случае отчет, который длится около 10 секунд, считается длинным. Но с реальными рабочими данными он может работать в течение 5-10 минут. Это особенно болезненно, когда отчет генерируется по HTTP-запросу. Если отчет может начать запись в выходной поток ответа в течение времени ожидания (обычно 60 секунд или до 5 минут), то у него есть хорошие шансы быть полученным запрашивающим пользователем (обычно через браузер). Но если для заполнения отчета требуется более 5 минут, а для экспорта в файл — еще 8 минут, то пользователь просто увидит истекший HTTP-запрос и зарегистрирует его как ошибку. Звучит знакомо?
Имейте в виду, что отчеты могут работать в течение нескольких минут. Итак, дизайн для этого .
В нашем случае мы запускаем отчеты в отдельном потоке. Для отчетов, запускаемых с помощью HTTP-запроса, мы отвечаем страницей, содержащей ссылку на созданный отчет. Это позволяет избежать проблемы тайм-аута. Когда пользователь нажимает на эту ссылку и отчет еще не завершен, он / она увидит, что отчет все еще генерируется. Но когда отчет будет завершен, он / она сможет увидеть сгенерированный файл отчета.
01
02
03
04
05
06
07
08
09
10
11
12
|
ExecutorService executorService = ...; ... = executorService.submit(() -> { Map<String, Object> parameters = ...; try { ... ... filledReport = JasperFillManager.fillReport( compiledReport, parameters, ...); JasperExportManager.exportReportToPdf(filledReport, ...); } finally { ... } }); |
Нам также пришлось добавить возможность остановки / отмены текущего отчета. Хорошо, что в JR есть код, который проверяет Thread.interrupted()
. Таким образом, простое прерывание потока остановит его. Конечно, вам нужно написать несколько тестов для проверки (ожидайте JRFillInterruptedException
и ExportInterruptedException
).
И пока мы занимались этим, мы заново открыли способы добавления «слушателей» к генерации отчетов (например, FillListener
и JRExportProgressMonitor
) и предоставили пользователю некоторую информацию о прогрессе.
Мы также создали служебные тестовые классы для генерации больших объемов данных, повторяя данный фрагмент данных снова и снова. Это полезно, чтобы помочь остальной части команды разрабатывать приложения JR, которые предназначены для обработки длительных запусков и ошибок нехватки памяти.
Дальнейшие соображения дизайна
Еще одна вещь, которую следует учитывать, — это открытие и закрытие ресурса, необходимого при заполнении отчета. Это может быть соединение JDBC, сеанс Hibernate, JPA EntityManager
или поток ввода файла (например, CSV, XML). Ниже показан примерный набросок моих соображений по поводу дизайна.
1
2
3
4
5
6
7
8
|
1 . Compiling - - - - - - - - - - - - - -\ - - - -\ \ 2 . Filling > open-close \ - - - -/ resource > swap to file / 3 . Exporting / - - - - - - - - - - - - - -/ |
Мы хотим изолировать # 2 и определить декораторы, которые будут открывать ресурс, заполнять отчет и закрывать открытый ресурс в блоке finally
. Открытый ресурс может зависеть от элемента <queryString>
(если имеется) внутри отчета. В некоторых случаях, когда нет элемента <queryString>
, вероятно, нет необходимости открывать ресурс.
1
2
3
4
5
6
7
|
< queryString language = "hql" > <![CDATA[ ... ]]> </ queryString > ... < queryString language = "csv" > <![CDATA[ ... ]]> </ queryString > |
Кроме того, мы также хотим объединить # 2 и # 3 как одну абстракцию. Эта единственная абстракция упрощает оформление с помощью улучшений, таких как сброс созданных объектов страницы в файлы и их загрузка во время экспорта. Как уже упоминалось, это то, что делает JRVirtualizer
. Но нам бы хотелось, чтобы дизайн был прозрачным для объекта (-ов) с использованием комбинированной абстракции №2 и №3.
Подтверждения
Это все на данный момент. Еще раз спасибо Майку и его команде за то, что они поделились своим опытом. Да, он тот самый парень, который жертвует доходы своего приложения на благотворительность . Также, спасибо Клэр за идеи по тестированию, повторяя данные снова и снова. Соответствующие фрагменты кода можно найти на GitHub .
Смотреть оригинальную статью здесь: JasperReports: The Tricky Parts
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |