Автоматизация создания отчетов Java POI

Как программисты, мы находим множество способов написания кода для создания отчетов Excel. Обычно эти решения требуют тонны шаблонного кода.

В этой статье я представляю процедуру, созданную с помощью Java Reflection для создания отчетов Java POI XLSX, используя только шаблон и источник данных. Хотя существуют и другие реализации, подобные этой (некоторые даже лучше), это с открытым исходным кодом, поэтому вам не нужно платить ни цента за его использование. Просто изучите или дайте конструктивный отзыв.

Без дальнейших церемоний, давайте углубимся в это.

Генератор отчетов Excel

Инструмент Excel Report Generator (ERG), созданный для экономии времени и усилий при создании отчетов, представляет собой простой класс Java с Java Reflection, предназначенные для чтения столбцов и комментариев шаблона Excel и преобразования их в инструкции для синтаксического анализа базы данных и ввода в нее данных, таким образом создавая настоящий отчет.

Моя мотивация для создания этого заключалась в том, чтобы посмотреть на беспорядочные процедуры, использующие низкоуровневые инструкции для построения отчетов, строка за строкой и строка за строкой. Если у нас есть шаблон, мы можем сосредоточиться на том, как получить данные, а не на том, как отобразить окончательный результат для пользователя.

Вот как мы переходим от шаблона к реальному отчету:

  • Шаблон, файл Excel, содержит {placeholders}, описывающие свойства Java POJO, которые ERG пытается доступ и замена реальным содержимым, а также комментарии для описания более продвинутых методов извлечения данных из источника данных (в основном итерации списков).
  • Первая строка и первый столбец шаблона имеют специальный комментарий, который указывает, сколько всего столбцов и строк содержит шаблон. Это помогает ERG правильно перемещаться по всем ячейкам, доступным в шаблоне, и обрабатывать их соответствующим образом.
  • Константу [placeholders] можно заменить системной информацией. На данный момент доступен только один [TODAY] и, как следует из названия, он записывает текущую дату и время.

Комментарии могут описывать различные способы доступа к спискам в Java POJO:

  • За комментарием :page следуют две строки, представляющие количество строк и столбцов соответственно, которые содержит каждый шаблон. Это единственный обязательный комментарий, который должен присутствовать, чтобы ERG правильно обработал шаблон и сгенерировал окончательный отчет.
  • За комментарием :for следуют три строки, первая из которых описывает свойство списка Java POJO для итерации, а вторая и третья указывают, сколько строк и областей он имеет соответственно. , который является областью, которую включает каждая итерация.
  • Комментарий :mergeBy объединяет столбцы на основе длины свойства списка Java POJO, указанного в следующей строке комментария.
  • Комментарий :iterateBy аналогичен комментарию :for one, с той лишь разницей, что он выполняет горизонтальную итерацию в одной строке, а не вертикальную в регионе.

Заполнители также могут содержать каналы {property|pipe}. Они указывают тип информации, которую представляет свойство Java POJO, и типы описываются следующим образом:

  • DATE: представляет свойство Date Java POJO. Записанная ячейка имеет тип String.
  • INTEGER: представляет свойство Integer Java POJO. Записанная ячейка имеет тип Числовой.
  • DECIMAL: представляет свойство Double/Float Java POJO. Записанная ячейка имеет тип Числовой.

Наконец, всякий раз, когда свойство равно null, мы можем предоставить стандартную метку для отображения {property?no data available|pipe}, это дает пользователю подсказку, что ничего было найдено, чтобы заполнить отчет.

Реальный сценарий использования

Чтобы продемонстрировать ERG, мы рассмотрим сценарий, в котором мы хотим создавать отчеты для академического приложения с учащимися, которые посещают занятия, которые читают лекции учителя, как показано на рис.A.

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

Прежде всего, мы начнем с предоставления информации о том, сколько столбцов и строк мы хотим в нашем шаблоне, написав комментарий :page в первой ячейке, как показано на рис.C.

:page
9
5

Позже мы начинаем перебирать список «Студенты», добавляя комментарий :for, как показано на figD, и указывая свойства класса «Студент» в каждой ячейке региона. определенный. Как показано на figE, используются следующие свойства: имя, дата рождения, адрес, мобильный телефони адрес электронной почты.

:for
students
6
4

Мы также хотим отобразить каждый класс, который посещают студенты, поэтому нам нужно выполнить итерацию в каждом списке классов студентов, снова используя комментарий :for, как показано на figF. Как показано на figG, используются следующие свойства: name, mediumGrade, и имя_учителя. Поскольку свойство averageGrade является десятичным, нам нужно использовать DECIMAL вертикальную черту.

:for
classes
2
2

Наконец, мы хотим отобразить информацию о дате [СЕГОДНЯ] и дать имя листу {title}. Полный шаблон показан на рис.H.

Далее следует код для обработки этого шаблона и создания отчета.

package com.tiago;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import com.tiago.datasource.DataSource;
import com.tiago.datasource.Student;
import com.tiago.datasource.Teacher;
import com.tiago.datasource.Class;
import com.tiago.datasource.ClassTypeEnum;
import com.tiago.utils.SpreadSheetWriter;
public class Main {
public static void main(String args[]){
Student studentTiago = new Student(
"Tiago",
LocalDate.of(2002, 1, 10),
"Rua Antonio Francisco, 672",
"(32) 992736726",
"tiago@gmail.com",
new ArrayList<Class>()
);
Student studentAnderson = new Student(
"Anderson",
LocalDate.of(1988, 3, 15),
"Rua Antonio Francisco, 572",
"(32) 992711724",
"anderson@gmail.com",
new ArrayList<Class>()
);
Student studentEduardo = new Student(
"Eduardo",
LocalDate.of(2095, 10, 12),
"Rua Antonio Francisco, 272",
"(32) 992736726",
"eduardo@gmail.com",
new ArrayList<Class>()
);
Student studentRafael = new Student(
"Rafael",
LocalDate.of(2000, 5, 10),
"Rua Antonio Francisco, 272",
"(32) 933336722",
"rafael@gmail.com",
new ArrayList<Class>()
);
Student studentTaynara = new Student(
"Taynara",
LocalDate.of(1998, 10, 10),
"Rua Antonio Francisco, 172",
"(32) 992111116",
"taynara@gmail.com",
new ArrayList<Class>()
);
Teacher teacherDiogo = new Teacher(
"Diogo",
LocalDate.of(2005, 9, 23),
"Rua Antonio Francisco, 672",
"(32) 982736725",
"diogo@gmail.com",
new ArrayList<Class>()
);
Teacher teacherLivia = new Teacher(
"Livia",
LocalDate.of(2000, 11, 11),
"Rua Antonio Francisco, 972",
"(32) 982799925",
"livia@gmail.com",
new ArrayList<Class>()
);
Teacher teacherMaria = new Teacher(
"Maria",
LocalDate.of(2004, 5, 17),
"Rua Fernando Francisco, 272",
"(32) 911136711",
"maria@gmail.com",
new ArrayList<Class>()
);
Class mathClass = new Class(
"Math",
ClassTypeEnum.DAY_CLASS,
4.5,
List.of(
studentTiago,
studentRafael,
studentTaynara
),
teacherDiogo);
teacherLivia.getClasses().add(mathClass);
studentTiago.getClasses().add(mathClass);
studentRafael.getClasses().add(mathClass);
studentTaynara.getClasses().add(mathClass);
Class biologyClass = new Class(
"Biology",
ClassTypeEnum.NIGHT_CLASS,
3.2,
List.of(
studentTiago,
studentAnderson,
studentEduardo
),
teacherLivia);
teacherLivia.getClasses().add(biologyClass);
studentTiago.getClasses().add(biologyClass);
studentAnderson.getClasses().add(biologyClass);
studentEduardo.getClasses().add(biologyClass);
Class chemistryClass = new Class(
"Chemistry",
ClassTypeEnum.DAY_CLASS,
4.0,
List.of(
studentAnderson,
studentEduardo,
studentRafael,
studentTaynara
),
teacherMaria);
teacherLivia.getClasses().add(chemistryClass);
studentAnderson.getClasses().add(chemistryClass);
studentEduardo.getClasses().add(chemistryClass);
studentRafael.getClasses().add(chemistryClass);
studentTaynara.getClasses().add(chemistryClass);
DataSource dataSource = new DataSource(
"Students Records",
List.of(
studentTiago,
studentAnderson,
studentEduardo,
studentRafael,
studentTaynara
)
);
SpreadSheetWriter.write(
Optional.of("There's no data"), "students-template.xlsx", dataSource);
System.out.println("Report generated.");
}
}
view raw Main.java hosted with ❤ by GitHub

Как видно из строки 149, мы предоставляем метку для ERG, которая будет отображаться в отчете всякий раз, когда данный список учащихся пуст. Эта ситуация показана на figI (отчет заполнен данными) и figJ (пустой отчет).

Другой реальный сценарий использования с :mergeBy и :iterateBy

Теперь давайте посмотрим на этот отчет с другой точки зрения. Скажем, из всех доступных занятий мы хотим знать, какие из них посещает каждый ученик, а какие пропускает, как в figK:

Для этого нам нужно использовать два вышеупомянутых специальных комментария: :mergeBy объединит ячейки G2, H2 и I2 в одну с заголовком «Классы» и :iterateBy описывают, какие занятия посещал учащийся (отмечены знаком X), а какие нет.

Для этого нам нужно, чтобы список классов был доступен из класса-источника данных для комментария :mergeBy, как в figL:

Как и в первом сценарии, есть комментарий :for, который перебирает список студентов, как показано на figM:

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

Наконец, чтобы получить список имен классов, помеченных или не отмеченных знаком X, в классе Student есть метод для перекрестной проверки списка доступных классов с посещаемыми:

public List<String> getClassesNamesWithAttendedCheck() {
return Student.classes.stream()
.map(it -> it.getName() + (this.attendedClasses.contains(it) ? " (X)" : ""))
.collect(Collectors.toList());
}
view raw Student.java hosted with ❤ by GitHub

Мы используем этот метод для комментария :iterateBy, чтобы нарисовать список классов, помеченных X, как в figO.

Заключение

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

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

Если вы хотите проверить ERG, вы можете перейти здесь.

Спасибо за прочтение! :)