JasperReports DB support
Рубрика: JasperReports | 15 March 2007, 09:58 | juriy
В прошлой заметке я привел пример простого отчета, сгенерированного с помошью библиотеки JasperReports. В этой раз я продолжу тему автоматизированных отчетов и более детально расскажу про структуру шаблона и про то, как заставить Jasper генерировать отчеты на основе данных из БД.
Структура шаблона.
Шаблон Jasper отчета – это xml документ, составленный по определенным правилам. Правил немного и большинство из них понятны интуитивно.
Каждый отчет должен иметь корневой элемент jasperReport с одним обязательным атрибутом: name – название отчета. Авторы библиотеки рекомендуют создавать отчеты таким образом, чтобы атрибут name и имя файла шаблона были одинаковыми. К примеру, если файл называется min_report.xml, то атрибут name должен иметь значение “min_report”.
У корневого элемента jasperReport нет обязательных подэлементов, поэтому минимальный шаблон отчета выглядит так:
<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE jasperReport
PUBLIC "-//JasperReports//DTD Report Design//EN"
"http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">
<jasperReport name="min_report">
</jasperReport>
Содержимое тега jasperReport полностью определяет внешний вид отчета.
Отчет состоит из нескольких блоков. В шаблоне может содержаться максимум по одному блоку каждого типа (это правило касается только блоков “разметки”, которые описаны ниже). С другой стороны, ни один из блоков обязательным не является.
<title> – название отчета. Название отображается один раз в начале отчета.
<pageHeader> – заголовок страницы. Отображается вначале каждой новой страницы отчета.
<columnHeader> – заголовок колонки. Имеет смысл только в случае, если отчет разбит на несколько колонок. По умолчанию в отчете есть только одна колонка. columnHeader отображается вначале каждой новой колонки.
<detail> – ключевая и самая важная часть отчета. Можно назвать этот блок “телом” отчета. В блоке detail содержится основная информация.
<columnFooter> – “подножье” колонки. Отображается после колонки. Аналогично с columnHeader этот блок имеет смысл вставлять только в том случае, если колонок в шаблоне как минимум две.
<pageFooter> – “подножье” страницы. Печатается в конце каждой страницы отчета.
<lastPageFooter> – “подножье” последней страницы. Печатается в конце последней страницы отчета. Если lastPageFooter определен, то на последней странице не будет напечатан pageFooter, а только lastPageFooter. Есть один интересный нюанс: если в отчете всего одна страница и определен lastPageFooter, то pageFooter не будет напечатан вообще: ведь единственная страница является и первой и последней.
<summary> – выводы или итоги. Печатается один раз в конце отчета.
То есть отчет “выглядит” так:
<title>
<pageHeader>
<columnHeader>
<detail>
<columnFooter>
<summary>
<pageFooter>|<lastPageFooter>
Каждый из этих элементов содержит один обязательный подэлемент – band. band – это часть площади отчета, которая отводится в распоряжение соответствующего блока.
Внутри band содержатся элементы, которые отвечают за отображение данных.
Посмотрим еще раз на отчет из прошлой заметки:
<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE jasperReport
PUBLIC "-//JasperReports//DTD Report Design//EN"
"http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">
<jasperReport name="simple">
<style
name="Normal"
isDefault="true"
pdfFontName="c:\tahoma.ttf"
pdfEncoding="Cp1251"
/>
<detail>
<band height="20">
<staticText>
<reportElement x="180" y="0" width="200" height="20" />
<text><![CDATA[Тест !!]]></text>
</staticText>
</band>
</detail>
</jasperReport>
Как видно, мы пренебрегли всеми блоками кроме двух – detail и style. Внутри блока detail содержится band – “полоса”. Этот элемент описывает свойства той части отчета, которая будет отведена под блок detail. В этом примере мы указали, что высота полосы будет 20 пикселей (height=”20″). Внутри band, в свою очередь содержится статический текст (“Тест !!”).
Блок style нужен для определения стиля документа. В нашем примере он нужен для корректной локализации (без него русские буквы ведут себя непредсказуемо). Я посвящу стилям отдельную заметку. Сейчас я предлагаю принять этот блок таким, какой он есть.
Элемент reportElement, в этом примере, определяет положение текста внутри band’а – текст будет смещен на 180 пикселей вправо.
Внутри одного band’а может быть несколько элементов с данными: к примеру, такой шаблон тоже будет вполне допустимым:
<jasperReport name="simple">
<style name="Normal" isDefault="true" pdfFontName="c:\tahoma.ttf" pdfEncoding="Cp1251" />
<detail>
<band height="20">
<staticText>
<reportElement x="100" y="0" width="50" height="20"/>
<text><![CDATA[Текст 1]]></text>
</staticText>
<staticText>
<reportElement x="150" y="0" width="50" height="20"/>
<text><![CDATA[Текст 2]]></text>
</staticText>
</band>
</detail>
</jasperReport>
Использование данных БД.
Заставить Jasper работать с базой данных совсем не сложно. Для примера я использовал MySQL. Совершенно не важно, какую СУБД используете вы. Для того, чтобы успешно сформировать отчет по данным из базы вам достаточно знать, как соединиться с БД через JDBC.
Для примера я создал одну простую таблицу – customers:
DROP TABLE IF EXISTS `jasper`.`customers`;
CREATE TABLE `customers` (
`id` int(10) unsigned NOT NULL auto_increment,
`firstname` varchar(45) NOT NULL default '',
`lastname` varchar(45) NOT NULL default '',
`segment` varchar(45) NOT NULL default '',
`city` varchar(45) NOT NULL default '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB ;
Начнем с java кода.
Если записать последовательность действий, которые нужно выполнить для передачи данных из базы в отчет, то получится такой список:
1. Соединиться с БД, выполнить запрос и получить ResultSet.
2. Передать полученный ResultSet в JasperFillManager.fillReport(…)
3. Корректно закрыть соединение с БД.
Чтобы передать в отчет ResultSet его нужно “обернуть” в JRResultSetDataSource – класс, специально для этого созданный и наследующий JRDataSource.
Давайте вспомним предыдущую заметку. “Заполняя” отчет мы передавали в него эксземпляр JREmptyDataSource – пустой источник данных. В этот раз мы передаем экземпляр JRResultSetDataSource, поскольку источник данных это ResultSet.
Код изменится совсем немного:
JasperPrint jasperPrint = JasperFillManager.fillReport(
jasperReport,
new HashMap(),
new JRResultSetDataSource(resultSet)
);
Если собрать воедино все сказанное, то выйдет такой кусочек кода:
public class JasperDBDemo {
public static void main(String[] args) throws SQLException {
Connection conn = null;
ResultSet resultSet = null;
PrepearedStatement statement = null;
System.out.println("Starting");
try {
// Открываем соединение с базой
Class.forName("org.gjt.mm.mysql.Driver");
String query = "select * from customers";
conn = DriverManager.getConnection("jdbc:mysql://localhost/jasper", "jasper", "jasper");
statement = conn.prepareStatement(query);
// Исполняем запрос и получаем ResultSet
resultSet = statement.executeQuery();
JasperReport jasperReport = JasperCompileManager.compileReport("reports/db_report.xml");
// Передаем resultSet в отчет
JasperPrint jasperPrint = JasperFillManager.fillReport( jasperReport, new HashMap(),
new JRResultSetDataSource(resultSet) );
JasperExportManager.exportReportToHtmlFile(jasperPrint, "reports/db_report.html");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (JRException e) {
e.printStackTrace();
} finally {
// Корректно закрываем соединение с базой
try {
resultSet.close();
statement.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("Done.");
}
}
Теперь займемся шаблоном отчета.
Чтобы использовать поля выборки в отчете их необходимо описать. Описание поля – это его имя и java класс, совместимый с типом поля.
<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE jasperReport
PUBLIC "-//JasperReports//DTD Report Design//EN"
"http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">
<jasperReport name="JasperDBDemo">
<style name="Arial_Normal" isDefault="true" fontName="Arial"
fontSize="12" pdfFontName="c:\tahoma.ttf" pdfEncoding="Cp1251"
isPdfEmbedded="false" />
<field name="id" class="java.lang.String" />
<field name="firstname" class="java.lang.String" />
<field name="lastname" class="java.lang.String" />
<field name="city" class="java.lang.String" />
<field name="segment" class="java.lang.String" />
</jasperReport>
После того, как поля описаны к ним можно обращаться из отчета, используя синтаксис $F{имя_поля}. К примеру, чтобы обратиться к полю firstname необходимо написать $F{firstname}.
Теперь становится довольно очевидным, как надо создать отчет, используя поля выборки:
<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE jasperReport
PUBLIC "-//JasperReports//DTD Report Design//EN"
"http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">
<jasperReport name="JasperDBDemo">
<style name="Arial_Normal" isDefault="true" fontName="Arial"
fontSize="12" pdfFontName="c:\tahoma.ttf" pdfEncoding="Cp1251"
isPdfEmbedded="false" />
<field name="id" class="java.lang.String" />
<field name="firstname" class="java.lang.String" />
<field name="lastname" class="java.lang.String" />
<field name="city" class="java.lang.String" />
<field name="segment" class="java.lang.String" />
<detail>
<band height="20">
<textField>
<reportElement x="0" y="0" width="50" height="20" />
<textFieldExpression class="java.lang.String">
<![CDATA[$F{id}]]>
</textFieldExpression>
</textField>
<textField>
<reportElement x="51" y="0" width="200" height="20" />
<textFieldExpression class="java.lang.String">
<![CDATA[$F{firstname}]]>
</textFieldExpression>
</textField>
<textField>
<reportElement x="252" y="0" width="200" height="20" />
<textFieldExpression class="java.lang.String">
<![CDATA[$F{lastname}]]>
</textFieldExpression>
</textField>
</band>
</detail>
</jasperReport>
Как видно, для каждой записи из базы мы напечатали 3 элемента, – id, firstname и lastname. Нет ничего проще, верно?.
Напоследок, еще один небольшой “трюк”. Запрос можно хранить прямо в шаблоне отчета. Для этого текст запроса достаточно поместить в блок <queryString>, а в java коде передавать не JRDataSource, а java.sql.Connection.
Идея довольно простая, поэтому ниже я просто приведу код реализации.
Шаблон будет выглядеть так:
<jasperReport name="JasperDBDemo">
<style name="Arial_Normal" isDefault="true" fontName="Arial"
fontSize="12" pdfFontName="c:\tahoma.ttf" pdfEncoding="Cp1251"
isPdfEmbedded="false" />
<queryString>
<![CDATA[ select * from customers ]]>
</queryString>
<field name="id" class="java.lang.String" />
<field name="firstname" class="java.lang.String" />
<field name="lastname" class="java.lang.String" />
<field name="city" class="java.lang.String" />
<field name="segment" class="java.lang.String" />
<detail>
<band height="20">
<textField>
<reportElement x="0" y="0" width="50" height="20" />
<textFieldExpression class="java.lang.String">
<![CDATA[$F{id}]]>
</textFieldExpression>
</textField>
<textField>
<reportElement x="51" y="0" width="200" height="20" />
<textFieldExpression class="java.lang.String">
<![CDATA[$F{firstname}]]>
</textFieldExpression>
</textField>
<textField>
<reportElement x="252" y="0" width="200" height="20" />
<textFieldExpression class="java.lang.String">
<![CDATA[$F{lastname}]]>
</textFieldExpression>
</textField>
</band>
</detail>
</jasperReport>
А java код – так:
public class JasperDBDemo {
public static void main(String[] args) throws SQLException {
Connection conn = null;
System.out.println("Starting");
try {
// Открываем соединение с базой
Class.forName("org.gjt.mm.mysql.Driver");
String query = "select * from customers";
conn = DriverManager.getConnection("jdbc:mysql://localhost/jasper",
"jasper", "jasper");
JasperReport jasperReport = JasperCompileManager
.compileReport("reports/db_report.xml");
// Передаем resultSet в отчет
JasperPrint jasperPrint = JasperFillManager.fillReport( jasperReport,
new HashMap(), conn );
JasperExportManager.exportReportToHtmlFile(jasperPrint,
"reports/db_report.html");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (JRException e) {
e.printStackTrace();
} finally {
// Корректно закрываем соединение с базой
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("Done.");
}
}
Порядок следования элементов в шаблоне имеет значение. Нельзя описывать поля перед описанием запроса. Нельзя ставить title после footer’а и.т.д.
Требование вполне естественное, но чтобы не возникало лишних ошибок, я рекомендую использовать редактор xml который умеет “подсвечивать” отклонения от dtd и нормально описывать причину ошибки.
На этом все. Пишите комменты.
Предыдущая тема: Report in
Следующая тема: Жизненный цикл отчета

А какой редактор умеющий подсвечивать” отклонения от dtd посоветуешь?
Eclipse xml editor, конечно :-)
А на что ты расчитывал? :-)
Если там неправильно структуру пишешь, то ошибочный тег подсвечивается красной волнистой линией и при наведении мышки отображается “вырезка” из dtd: что там должно быть.
Мне кажется – очень полезное свойство. Конечно, чтобы все работало, eclipse должен быть в состоянии скачать dtd: то есть надо настроить прокси, и быть подключенным к инету.
Скачивает один раз, потом кеширует.
Какой из
http://www.google.com/search?q=eclipse+xml+editor
?
Тот, который я юзаю шел в комплекте с WebToolKit (WTK). Сейчас немного лениво искать точное название плугина, но если надо – говори.
Я так понимаю – ты решил на практике туториал применить. Пиши, как получилось, какие места неочевидные, буду совершенствовать.
Я только начинаю разбираться с JasperReport и у меня возник вопрос:
Во всех тэгах мы явно указуем размер поля. А если я заранее не знаю какой длинны у меня будит текст, что делать ?
Использую ireport для netbeans для рисования отчетов с определенным скл-запросом. А если сначала пользователь должен выбрать один из критериев скл-запроса (например, имя преподавателя,по которому надо сформировать отчет). Как это обработать, подскажите пож-та…
Заранее благодарю…
Elmira,
при использовании в запросе параметра WHERE передавай
данные JRResultSetDataSource’ом.
А можно Jasper как-нить заставить работать с несколькими базами данных?
К примеру, в одном отчете должны отображаться результаты запросов к нескольким бд
>> jjjcoder
Можно, используя subreports.
что прописать в шаблоне, чтобы использовать
MapDataSource?
т.е. я хочу в отчете показать данные, к примеру из HashMap’a заданного.
А можно ли параметры передавать в свой JavaBean как-нибудь?
Вызов статического метода не предполагает передачу параметров
Доброго времени суток!
Есть сайт на PhP c CMS joomla. Можно ли совмместить отчеты от Jasper на сайт?
Хочу график статистики записей показывать.
Доброго времени суток! С товарищем спорим вот по какому вопросу: в каком месте лучше выполнять запрос. в xml’ке или в коде? может что подскажите?
Лучше в xml. А параметры передавать HashMap’ом.
Но может кому удобнее и в коде генерить sql запрос. На вкус и цвет все фломастеры разные :)
Спасибо за статью.
Предложение – прикреплять архив исходных кодов, описываемых в статье.
Спасибо! Отличный материал. Есть вопрос – мне в отчете нужно использовать две таблицы формирующиеся по двум разным запросам. Как это реализовать?