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
Следующая тема: Жизненный цикл отчета

Комментариев: 20

20 Responses to “JasperReports DB support”

Комментарии:

  1. vadim

    А какой редактор умеющий подсвечивать” отклонения от dtd посоветуешь?

  2. juriy

    Eclipse xml editor, конечно :-)
    А на что ты расчитывал? :-)

    Если там неправильно структуру пишешь, то ошибочный тег подсвечивается красной волнистой линией и при наведении мышки отображается “вырезка” из dtd: что там должно быть.

    Мне кажется – очень полезное свойство. Конечно, чтобы все работало, eclipse должен быть в состоянии скачать dtd: то есть надо настроить прокси, и быть подключенным к инету.

    Скачивает один раз, потом кеширует.

  3. vadim

  4. juriy

    Тот, который я юзаю шел в комплекте с WebToolKit (WTK). Сейчас немного лениво искать точное название плугина, но если надо – говори.

    Я так понимаю – ты решил на практике туториал применить. Пиши, как получилось, какие места неочевидные, буду совершенствовать.

  5. John

    Я только начинаю разбираться с JasperReport и у меня возник вопрос:
    Во всех тэгах мы явно указуем размер поля. А если я заранее не знаю какой длинны у меня будит текст, что делать ?

  6. Elmira

    Использую ireport для netbeans для рисования отчетов с определенным скл-запросом. А если сначала пользователь должен выбрать один из критериев скл-запроса (например, имя преподавателя,по которому надо сформировать отчет). Как это обработать, подскажите пож-та…
    Заранее благодарю…

  7. Almagnit

    Elmira,

    при использовании в запросе параметра WHERE передавай

    данные JRResultSetDataSource’ом.

  8. jjjcoder

    А можно Jasper как-нить заставить работать с несколькими базами данных?
    К примеру, в одном отчете должны отображаться результаты запросов к нескольким бд

  9. brainman

    >> jjjcoder
    Можно, используя subreports.

  10. Max

    что прописать в шаблоне, чтобы использовать
    MapDataSource?
    т.е. я хочу в отчете показать данные, к примеру из HashMap’a заданного.

  11. adf

    А можно ли параметры передавать в свой JavaBean как-нибудь?
    Вызов статического метода не предполагает передачу параметров

  12. Alex

    Доброго времени суток!
    Есть сайт на PhP c CMS joomla. Можно ли совмместить отчеты от Jasper на сайт?
    Хочу график статистики записей показывать.

  13. Bezkedov

    Доброго времени суток! С товарищем спорим вот по какому вопросу: в каком месте лучше выполнять запрос. в xml’ке или в коде? может что подскажите?

  14. Tolstokot

    Лучше в xml. А параметры передавать HashMap’ом.
    Но может кому удобнее и в коде генерить sql запрос. На вкус и цвет все фломастеры разные :)

  15. 1.5

    Спасибо за статью.
    Предложение – прикреплять архив исходных кодов, описываемых в статье.

  16. Hapcom

    Спасибо! Отличный материал. Есть вопрос – мне в отчете нужно использовать две таблицы формирующиеся по двум разным запросам. Как это реализовать?

  17. Num

    Спасибо за статью, но как сделать допустим вывод не на 1 стр, а на 2, у меня будет 4 поля на 1й стр, и 4 поля на 2й….

    и да, интересно как сделать если у меня не 1 запрос, а вот 2-n шт…

Leave a Reply