Tuesday, February 8, 2011

Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a DynamicReport

In this tutorial we will build a simple Spring MVC 3 application with reporting capabilities. We will use DynamicJasper to generate the dynamic reports and Hibernate for the ORM framework. We will use DynamicJasper's report concatenation to attach a subreport to a parent report. The data will be retrieved from a MySQL database.

This tutorial is part of the following reporting tutorial series that uses Jasper, DynamicJasper, and Apache POI:

Spring 3 - Apache POI - Hibernate: Creating an Excel Report Tutorial
Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a DynamicReport
Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a Subreport
Spring 3 - DynamicJasper - Hibernate Tutorial: Using Plain List
Spring 3 - DynamicJasper - Hibernate Tutorial: Using JRDataSource
Spring 3 - DynamicJasper - Hibernate Tutorial: Using HQL Query

All of these tutorials produce the same document, and all of them demonstrate different ways of creating the same report.

What is DynamicJasper?
DynamicJasper (DJ) is an open source free library that hides the complexity of Jasper Reports, it helps developers to save time when designing simple/medium complexity reports generating the layout of the report elements automatically.

Source: http://dynamicjasper.com/

What is JasperReports?
JasperReports is the world's most popular open source reporting engine. It is entirely written in Java and it is able to use data coming from any kind of data source and produce pixel-perfect documents that can be viewed, printed or exported in a variety of document formats including HTML, PDF, Excel, OpenOffice and Word.

Source: http://jasperforge.org/projects/jasperreports

Background

Before we start our application, let's preview first the final print document:

Our document is a simple Excel document. It's a Sales Report for a list of power supplies. The data is retrieved from a MySQL database.

Domain

Notice that for each Power Supply entry there's a common set of properties:
id
brand
model
maximum power
price
efficiency

Development

Domain

We'll start our application by declaring the domain object PowerSupply

PowerSupply.java
package org.krams.tutorial.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * A simple POJO containing the common properties of a Power Supply
 * This is an annotated Hibernate entity. 
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Entity
@Table(name = "POWER_SUPPLY")
public class PowerSupply implements Serializable {

 private static final long serialVersionUID = 8634209606034270882L;

 @Id
 @Column(name = "ID")
 @GeneratedValue
 private Long id;
 
 @Column(name = "BRAND")
 private String brand;
 
 @Column(name = "MODEL")
 private String model;
 
 @Column(name = "MAXIMUM_POWER")
 private String maximumPower;
 
 @Column(name = "PRICE")
 private Double price;
 
 @Column(name = "EFFICIENCY")
 private Double efficiency;

 public Long getId() {
  return id;
 }

 public void setId(Long id) {
  this.id = id;
 }

 public String getBrand() {
  return brand;
 }

 public void setBrand(String brand) {
  this.brand = brand;
 }

 public String getModel() {
  return model;
 }

 public void setModel(String model) {
  this.model = model;
 }

 public String getMaximumPower() {
  return maximumPower;
 }

 public void setMaximumPower(String maximumPower) {
  this.maximumPower = maximumPower;
 }

 public Double getPrice() {
  return price;
 }

 public void setPrice(Double price) {
  this.price = price;
 }

 public Double getEfficiency() {
  return efficiency;
 }

 public void setEfficiency(Double efficiency) {
  this.efficiency = efficiency;
 }
 
}
PowerSupply is a simple POJO containing six private fields. Each of these fields have been annotated with @Column and assigned with corresponding database column names.
ID
BRAND
MODEL
MAXIMUM_POWER
PRICE
EFFICIENCY

Service

We'll be declaring a single service named DownloadService. This service is the heart of the application that will process and retrieve the report document.

The service will run the following steps:
1. Build the report layout
 2. Add the datasource to a HashMap parameter
 3. Compile the report layout
 4. Generate the JasperPrint object
 5. Export to a particular format, ie. XLS
 6. Set the HttpServletResponse properties
 7. Write to the output stream

DownloadService.java
package org.krams.tutorial.service;

import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.List;
import javax.servlet.http.HttpServletResponse;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.krams.tutorial.domain.PowerSupply;
import org.krams.tutorial.report.Exporter;
import org.krams.tutorial.report.Layouter;
import org.krams.tutorial.report.Writer;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import ar.com.fdvs.dj.core.DynamicJasperHelper;
import ar.com.fdvs.dj.core.layout.ClassicLayoutManager;
import ar.com.fdvs.dj.domain.DynamicReport;
import ar.com.fdvs.dj.domain.builders.ColumnBuilderException;
import javax.annotation.Resource;

/**
 * Service for processing DynamicJasper reports
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Service("downloadService")
@Transactional
public class DownloadService {

 private static Logger logger = Logger.getLogger("service");
 
 @Resource(name="sessionFactory")
 private SessionFactory sessionFactory;
 
 /**
  * Processes the download for Excel format.
  * It does the following steps:
  * <pre>1. Build the report layout
  * 2. Retrieve the datasource
  * 3. Compile the report layout
  * 4. Generate the JasperPrint object
  * 5. Export to a particular format, ie. XLS
  * 6. Set the HttpServletResponse properties
  * 7. Write to the output stream
  * </pre>
  */
 @SuppressWarnings("unchecked")
 public void downloadXLS(HttpServletResponse response) throws ColumnBuilderException, ClassNotFoundException, JRException {
  logger.debug("Downloading Excel report");

  // 1. Build the report layout
  DynamicReport dr = Layouter.buildParentReportLayout();

  // 2. Add the datasource to a HashMap parameter
  HashMap params = new HashMap(); 
  // Here we're adding a custom datasource named "dynamicReportDs". 
  // It's the name we put in the Layouter
  params.put("dynamicReportDs", getDatasource());
  
  // 3. Compile the report layout
  // This will regardless if you activate the .setWhenNoDataAllSectionNoDetail() property or not
  // in the parentReportBuilder under the Layouter class. However you're FORCED 
  // to provide a dummy datasource for the parent report
  JasperReport jr = DynamicJasperHelper.generateJasperReport(dr, new ClassicLayoutManager(), params);
  
  // 4. Generate the JasperPrint object which also fills the report with data
  // This will regardless if you activate the .setWhenNoDataAllSectionNoDetail() property or not
  // in the parentReportBuilder under the Layouter class. However you're FORCED 
  // to provide a dummy datasource for the parent report
  JasperPrint jp = JasperFillManager.fillReport(jr, params, getJRDummyDatasource() );

  // We can also combine compilation (3) and generation (4) in a single line
  // This will only work if you add the .setWhenNoDataAllSectionNoDetail() property
  // in the parentReportBuilder under the Layouter class
  //JasperPrint jp = DynamicJasperHelper.generateJasperPrint(dr, new ClassicLayoutManager(), params);
  
  // Create the output byte stream where the data will be written
  ByteArrayOutputStream baos = new ByteArrayOutputStream();

  // 5. Export to XLS format
  Exporter.exportToXLS(jp, baos);
  
  // 6. Set the response properties
  String fileName = "SalesReport.xls";
  response.setHeader("Content-Disposition", "inline; filename=" + fileName);
  // Make sure to set the correct content type
  response.setContentType("application/vnd.ms-excel");
  // Assign the byte stream's size
  response.setContentLength(baos.size());

  // 7. Write to response stream
  Writer.write(response, baos);
 }
 
 /**
  * Retrieves a Java List datasource.
  * <p>
  * The data is retrieved from a Hibernate HQL query.
  * @return
  */
 @SuppressWarnings("unchecked")
 private List getDatasource() {
  logger.debug("Retrieving datasource");
  
      // Retrieve session
  Session session = sessionFactory.getCurrentSession();
  // Create query for retrieving products
  Query query = session.createQuery("FROM PowerSupply");
  // Execute query
  List<PowerSupply> result = query.list();

  // Return the datasource
  return result;
 }
 
 /**
  * Retrieves a dummy JRDataSource. 
  * @return
  */
 @SuppressWarnings("unchecked")
 private JRDataSource getJRDummyDatasource() {
  logger.debug("Retrieving JRdatasource");
  
  // Return the datasource
  return null;
 }
}
This service is our download service for generating the report document. It should be clear what each line of code is doing. Notice that in step 2 we have to declare the datasource as a key in the HashMap parameter:
HashMap params = new HashMap(); 
  // Here we're adding a custom datasource named "dynamicReportDs". 
  // It's the name we put in the Layouter
  params.put("dynamicReportDs", getDatasource());

Also, we've declared a getDatasource() method that retrieves a list of PowerSupply.
List<PowerSupply> result = query.list();
return result;

Furthermore, we declared a getJRDummyDatasource() method to generate an empty list. This is required if you want to use steps 3 and 4.

Alternatively, you can combine steps 3 and 4 and avoid this dummy datasource entirely:
// We can also combine compilation (3) and generation (4) in a single line
// This will only work if you add the .setWhenNoDataAllSectionNoDetail() property
// in the parentReportBuilder under the Layouter class
JasperPrint jp = DynamicJasperHelper.generateJasperPrint(dr, new ClassicLayoutManager(), params);

The service has been divided into separate classes to encapsulate specific jobs.

The Layouter

The purpose of the Layouter is to layout the design of the report. Here's where we declare the dynamic columns and special properties of the document. At the end of the class, we appended an HQL query

Layouter.java
package org.krams.tutorial.report;

import java.util.Date;

import org.apache.log4j.Logger;

import ar.com.fdvs.dj.core.DJConstants;
import ar.com.fdvs.dj.core.layout.ClassicLayoutManager;
import ar.com.fdvs.dj.domain.DJDataSource;
import ar.com.fdvs.dj.domain.DynamicReport;
import ar.com.fdvs.dj.domain.builders.ColumnBuilderException;
import ar.com.fdvs.dj.domain.builders.DJBuilderException;
import ar.com.fdvs.dj.domain.builders.FastReportBuilder;
import ar.com.fdvs.dj.domain.builders.SubReportBuilder;
import ar.com.fdvs.dj.domain.entities.Subreport;

/**
 * Everything under the package org.krams.tutorial.dynamicjasper are settings imposed by DynamicJasper (not Jasper)
 *<p>
 * Builds the report layout, the template, the design, the pattern or whatever synonym you may want to call it.
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Layouter {

 private static Logger logger = Logger.getLogger("service");
 
 /**
  * Builds the report layout. This doesn't have any data yet. This is your template.
  * 
  * @return the layout
  */
 public static DynamicReport buildChildReportLayout() throws ColumnBuilderException, ClassNotFoundException {
  
  // Create an instance of FastReportBuilder
  FastReportBuilder drb = new FastReportBuilder();
  
  // Create columns
  // The column fields must match the name and type of the properties in your datasource
  drb.addColumn("Id", "id",  Long.class.getName(), 50)
        .addColumn("Brand", "brand", String.class.getName(), 50)
        .addColumn("Model", "model" , String.class.getName(), 50)
        .addColumn("Max Power", "maximumPower", String.class.getName(), 50)
        .addColumn("Price", "price", Double.class.getName(), 50)
        .addColumn("Efficiency", "efficiency", Double.class.getName(), 50)
        .setPrintColumnNames(true)
        
         // Disables pagination
        .setIgnorePagination(true)
        
        // Experiment with this numbers to see the effect
        .setMargins(0, 0, 0, 0) 
        
        // Set the title shown inside the Excel file
        .setTitle("Sales Report") 
        
        // Set the subtitle shown inside the Excel file
        .setSubtitle("This report was generated at " + new Date()) 
        
  // Set to true if you want to constrain your report within the page boundaries
  // For longer reports, set it to false
        .setUseFullPageWidth(true);

  // Set the name of the file
        //drb.setReportName("Sales Report");
        
        // Build the report layout. It doesn't have any data yet!
        DynamicReport dr = drb.build();
        
        // Return the layout
        return dr;
 }
 
 /**
  * Builds the parent report layout. This doesn't have any data yet. This is your template.
  * 
  * @return the layout
  */
 public static DynamicReport buildParentReportLayout() throws ColumnBuilderException, ClassNotFoundException {
  
  // Create an instance of FastReportBuilder
  FastReportBuilder parentReportBuilder = new FastReportBuilder();
 
         // Disables pagination
  parentReportBuilder.setIgnorePagination(true)
  
        // Experiment with this numbers to see the effect
        .setMargins(0, 0, 0, 0) 
        
        // This is a critical property!!!
        // If the parentReportBuilder doesn't have an associated datasource, you must set this property!
        .setWhenNoDataAllSectionNoDetail()
        
  // Set to true if you want to constrain your report within the page boundaries
  // For longer reports, set it to false
        .setUseFullPageWidth(true);

  // Set the name of the file
        parentReportBuilder.setReportName("Concat Report");
       
        // Create a child report
  try {
   // Add the dynamicreport to the parent report layout
   parentReportBuilder.addConcatenatedReport(buildChildReportLayout(), 
     new ClassicLayoutManager(), "dynamicReportDs",
           DJConstants.DATA_SOURCE_ORIGIN_PARAMETER,
           DJConstants.DATA_SOURCE_TYPE_COLLECTION);
   
  } catch (DJBuilderException e) {
   logger.error("Unable to concat child report");
   throw new RuntimeException("Unable to concat child report");
  }
  
        // Build the parent report layout. It doesn't have any data yet!
        DynamicReport dr = parentReportBuilder.build();
        
        // Return the layout
        return dr;
 }
 
 
}
The Layouter has two methods buildParentReportLayout() and buildChildReportLayout() for building the parent and the child layout respectively.

To attach the child report to the parent report, we just directly add the DynamicReport
// Add the dynamicreport to the parent report layout
parentReportBuilder.addConcatenatedReport(buildChildReportLayout(), 
  new ClassicLayoutManager(), "dynamicReportDs",
  DJConstants.DATA_SOURCE_ORIGIN_PARAMETER,
  DJConstants.DATA_SOURCE_TYPE_COLLECTION);
Take note. This is one of the ways you can add a child report in DynamicJasper. You can also add the child report as a Subreport. See the Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a Subreport tutorial.

The Exporter

The purpose of the Exporter is to export the JasperPrint object into different formats, like Excel, PDF, and CSV. Our current implementation exports the document as an Excel format.

Exporter.java
package org.krams.tutorial.report;

import java.io.ByteArrayOutputStream;

import org.apache.log4j.Logger;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.export.JRXlsAbstractExporterParameter;
import net.sf.jasperreports.engine.export.JRXlsExporter;

/**
 * Everything under the package org.krams.tutorial.jasper are settings imposed by Jasper (not DynamicJasper)
 * <p>
 * An exporter for exporting the report in various formats, i.e Excel, PDF, CSV. Here we declare a PDF exporter
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Exporter {

 private static Logger logger = Logger.getLogger("service");
 
 /**
  * Exports a report to XLS (Excel) format. You can declare another export here, i.e PDF or CSV.
  * You don't really need to create a separate class or method for the exporter. You can call it
  * directly within your Service or Controller.
  * 
  * @param jp the JasperPrint object
  * @param baos the ByteArrayOutputStream where the report will be written
  */
 public static void exportToXLS(JasperPrint jp, ByteArrayOutputStream baos) throws JRException {
  // Create a JRXlsExporter instance
  JRXlsExporter exporter = new JRXlsExporter();
  
  // Here we assign the parameters jp and baos to the exporter
  exporter.setParameter(JRExporterParameter.JASPER_PRINT, jp);
        exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);
  
        // Excel specific parameters
        // Check the Jasper (not DynamicJasper) docs for a description of these settings. Most are 
        // self-documenting
        exporter.setParameter(JRXlsAbstractExporterParameter.IS_ONE_PAGE_PER_SHEET, Boolean.FALSE);
        exporter.setParameter(JRXlsAbstractExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.TRUE);
        exporter.setParameter(JRXlsAbstractExporterParameter.IS_WHITE_PAGE_BACKGROUND, Boolean.FALSE);
        
        // Retrieve the exported report in XLS format
        exporter.exportReport();
 }
}
It's worth mentioning that this class has nothing to do with DynamicJasper. Everything is Jasper-related configuration.

The Writer

The purpose of the Writer is to write the "exported" document to the output stream. Once the document has been written to the stream, the user will receive the document ready to be downloaded.

Writer.java
package org.krams.tutorial.report;

import java.io.ByteArrayOutputStream;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

/**
 * Writes the report to the output stream
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Writer {

 private static Logger logger = Logger.getLogger("service");
 /**
  * Writes the report to the output stream
  */
 public static void write(HttpServletResponse response, ByteArrayOutputStream baos) {
  
  logger.debug("Writing report to the stream");
  try {
   // Retrieve the output stream
   ServletOutputStream outputStream = response.getOutputStream();
   // Write to the output stream
   baos.writeTo(outputStream);
   // Flush the stream
   outputStream.flush();

  } catch (Exception e) {
   logger.error("Unable to write report to the output stream");
  }
 }
}

Controller

We've completed the domain and service layer of the application. Since we're developing a Spring MVC web application, we're required to declare a controller that will handle the user's request.

DownloadController.java
package org.krams.tutorial.controller;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JRException;
import org.apache.log4j.Logger;
import org.krams.tutorial.service.DownloadService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import ar.com.fdvs.dj.domain.builders.ColumnBuilderException;

/**
 * Handles download requests
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Controller
@RequestMapping("/download")
public class DownloadController {

 private static Logger logger = Logger.getLogger("controller");
 
 @Resource(name="downloadService")
 private DownloadService downloadService;

 /**
  * Downloads the report as an Excel format. 
  * 

* Make sure this method doesn't return any model. Otherwise, you'll get * an "IllegalStateException: getOutputStream() has already been called for this response" */ @RequestMapping(value = "/xls", method = RequestMethod.GET) public void getXLS(HttpServletResponse response, Model model) throws ColumnBuilderException, ClassNotFoundException, JRException { logger.debug("Received request to download report as an XLS"); // Delegate to downloadService. Make sure to pass an instance of HttpServletResponse downloadService.downloadXLS(response); } }

DownloadController is a simple controller that handles download requests. It delegates report generation to the DownloadService. Notice we're required to pass the HttpServletResponse to the service.

Database Configuration

We've completed the MVC module of the application. However we haven't created yet the Hibernate configuration and the MySQL database.

Our first task is to create an empty MySQL database.

Here are the steps:
1. Run MySQL
2. Open MySQL admin
3. Create a new database mydb

In this tutorial I've setup a local MySQL database and used phpmyadmin to administer it.

Next, we'll be declaring a hibernate-context.xml configuration file. Its purpose is to contain all of Spring-related configuration for Hibernate.

hibernate-context.xml

This configuration requires two external configurations further:

spring.properties
# database properties
app.jdbc.driverClassName=com.mysql.jdbc.Driver
app.jdbc.url=jdbc:mysql://localhost/mydb
app.jdbc.username=root
app.jdbc.password=

#hibernate properties
hibernate.config=/WEB-INF/hibernate.cfg.xml

hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
  "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
  
<hibernate-configuration>
 <session-factory>
  <!-- We're using a MySQL database so the dialect needs to be MySQL as well -->
  <!-- Also we want to use MySQL's InnoDB engine -->
  <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
  
  <!-- Enable this to see the Hibernate generated SQL statements in the logs -->
  <property name="show_sql">false</property>
  
  <!-- Setting this to 'create' will drop our existing database and re-create a new one.
    This is only good for testing. In production, this is a bad idea! -->
  <property name="hbm2ddl.auto">create</property>
 </session-factory>
</hibernate-configuration>

The Import.SQL

After declaring all the Hibernate-related configuration, let's now declare a SQL script that will populate our database with a sample data automatically.

import.sql
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Corsair', 'CMPSU-750TX', '750W', '109.99', '0.80')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Antec', 'NEO ECO 620C', '620W', '69.99', '0.80')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'OCZ', 'OCZ700MXSP', '700W', '89.99', '0.86')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Thermaltake', 'W0070RUC', '430W', '43.99', '0.65')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'COOLER MASTER', 'RS-460-PSAR-J3', '460W', '29.99', '0.70')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Rosewill', 'RG530-S12', '530W', '54.99', '0.80')
Make sure to place this document under the classpath. Hibernate will automatically import the contents of this document everytime your start the application. This is dictated by the hbm2ddl.auto setting we declared in the hibernate.cfg.xml earlier.

We're not required to create this import.sql file. We could of course create a MySQL SQL script and import it directly to the database, or add the data manually in the database. I just believe this is convenient for development purposes.

Spring MVC Configuration

We've declared all the necessary classes and Hibernate-related configuration of the application. However, we haven't created yet the required Spring MVC configuration.

Let's begin with the web.xml
web.xml
<servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 
 <servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>/krams/*</url-pattern>
 </servlet-mapping>

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
Take note of the URL pattern. When accessing any pages in our MVC application, the host name must be appended with
/krams
In the web.xml we declared a servlet-name spring. By convention, we must declare a spring-servlet.xml.

spring-servlet.xml
<!-- Declare a view resolver for resolving JSPs -->
 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" 
      p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
By convention, we must declare an applicationContext.xml as well.

applicationContext.xml
<!-- Activates various annotations to be detected in bean classes -->
 <context:annotation-config />
 
 <!-- Scans the classpath for annotated components that will be auto-registered as Spring beans.
  For example @Controller and @Service. Make sure to set the correct base-package-->
 <context:component-scan base-package="org.krams.tutorial" />
 
 <!-- Configures the annotation-driven Spring MVC Controller programming model.
 Note that, with Spring 3.0, this tag works in Servlet MVC only!  -->
 <mvc:annotation-driven /> 
 
 <!-- Loads Hibernate related configuration -->
 <import resource="hibernate-context.xml" />

Run the Application

We've completed the application. Our last task is to run the application and download the report.

To run the application, open your browser and enter the following URL:
http://localhost:8080/spring-djasper-hibernate-concatsr/krams/download/xls
This will automatically download the report document. Again, here's the final screenshot of the document:

Conclusion

That's it. We've managed to build a simple Spring MVC 3 application with reporting capabilities. We used DynamicJasper to generate the dynamic reports and Hibernate for the ORM framework. Lastly, we used Jasper's JRDataSource to wrap our Java List and used the wrapper object as the data source. Remember the data is still retrieved from a MySQL database.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-mvc-dynamicjasper-integration-tutorial/

You can download the project as a Maven build. Look for the spring-djasper-hibernate-concatdr.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a DynamicReport ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a Subreport

In this tutorial we will build a simple Spring MVC 3 application with reporting capabilities. We will use DynamicJasper to generate the dynamic reports and Hibernate for the ORM framework. We will use DynamicJasper's report concatenation to attach a subreport to a parent report. The data will be retrieved from a MySQL database.

This tutorial is part of the following reporting tutorial series that uses Jasper, DynamicJasper, and Apache POI:

Spring 3 - Apache POI - Hibernate: Creating an Excel Report Tutorial
Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a DynamicReport
Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a Subreport
Spring 3 - DynamicJasper - Hibernate Tutorial: Using Plain List
Spring 3 - DynamicJasper - Hibernate Tutorial: Using JRDataSource
Spring 3 - DynamicJasper - Hibernate Tutorial: Using HQL Query

All of these tutorials produce the same document, and all of them demonstrate different ways of creating the same report.

What is DynamicJasper?
DynamicJasper (DJ) is an open source free library that hides the complexity of Jasper Reports, it helps developers to save time when designing simple/medium complexity reports generating the layout of the report elements automatically.

Source: http://dynamicjasper.com/

What is JasperReports?
JasperReports is the world's most popular open source reporting engine. It is entirely written in Java and it is able to use data coming from any kind of data source and produce pixel-perfect documents that can be viewed, printed or exported in a variety of document formats including HTML, PDF, Excel, OpenOffice and Word.

Source: http://jasperforge.org/projects/jasperreports

Background

Before we start our application, let's preview first the final print document:

Our document is a simple Excel document. It's a Sales Report for a list of power supplies. The data is retrieved from a MySQL database.

Domain

Notice that for each Power Supply entry there's a common set of properties:
id
brand
model
maximum power
price
efficiency

Development

Domain

We'll start our application by declaring the domain object PowerSupply

PowerSupply.java
package org.krams.tutorial.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * A simple POJO containing the common properties of a Power Supply
 * This is an annotated Hibernate entity. 
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Entity
@Table(name = "POWER_SUPPLY")
public class PowerSupply implements Serializable {

 private static final long serialVersionUID = 8634209606034270882L;

 @Id
 @Column(name = "ID")
 @GeneratedValue
 private Long id;
 
 @Column(name = "BRAND")
 private String brand;
 
 @Column(name = "MODEL")
 private String model;
 
 @Column(name = "MAXIMUM_POWER")
 private String maximumPower;
 
 @Column(name = "PRICE")
 private Double price;
 
 @Column(name = "EFFICIENCY")
 private Double efficiency;

 public Long getId() {
  return id;
 }

 public void setId(Long id) {
  this.id = id;
 }

 public String getBrand() {
  return brand;
 }

 public void setBrand(String brand) {
  this.brand = brand;
 }

 public String getModel() {
  return model;
 }

 public void setModel(String model) {
  this.model = model;
 }

 public String getMaximumPower() {
  return maximumPower;
 }

 public void setMaximumPower(String maximumPower) {
  this.maximumPower = maximumPower;
 }

 public Double getPrice() {
  return price;
 }

 public void setPrice(Double price) {
  this.price = price;
 }

 public Double getEfficiency() {
  return efficiency;
 }

 public void setEfficiency(Double efficiency) {
  this.efficiency = efficiency;
 }
 
}
PowerSupply is a simple POJO containing six private fields. Each of these fields have been annotated with @Column and assigned with corresponding database column names.
ID
BRAND
MODEL
MAXIMUM_POWER
PRICE
EFFICIENCY

Service

We'll be declaring a single service named DownloadService. This service is the heart of the application that will process and retrieve the report document.

The service will run the following steps:
1. Build the report layout
 2. Add the datasource to a HashMap parameter
 3. Compile the report layout
 4. Generate the JasperPrint object
 5. Export to a particular format, ie. XLS
 6. Set the HttpServletResponse properties
 7. Write to the output stream

DownloadService.java
package org.krams.tutorial.service;

import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.List;
import javax.servlet.http.HttpServletResponse;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.krams.tutorial.domain.PowerSupply;
import org.krams.tutorial.report.Exporter;
import org.krams.tutorial.report.Layouter;
import org.krams.tutorial.report.Writer;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import ar.com.fdvs.dj.core.DynamicJasperHelper;
import ar.com.fdvs.dj.core.layout.ClassicLayoutManager;
import ar.com.fdvs.dj.domain.DynamicReport;
import ar.com.fdvs.dj.domain.builders.ColumnBuilderException;
import javax.annotation.Resource;

/**
 * Service for processing DynamicJasper reports
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Service("downloadService")
@Transactional
public class DownloadService {

 private static Logger logger = Logger.getLogger("service");
 
 @Resource(name="sessionFactory")
 private SessionFactory sessionFactory;
 
 /**
  * Processes the download for Excel format.
  * It does the following steps:
  * <pre>1. Build the report layout
  * 2. Retrieve the datasource
  * 3. Compile the report layout
  * 4. Generate the JasperPrint object
  * 5. Export to a particular format, ie. XLS
  * 6. Set the HttpServletResponse properties
  * 7. Write to the output stream
  * </pre>
  */
 @SuppressWarnings("unchecked")
 public void downloadXLS(HttpServletResponse response) throws ColumnBuilderException, ClassNotFoundException, JRException {
  logger.debug("Downloading Excel report");

  // 1. Build the report layout
  DynamicReport dr = Layouter.buildParentReportLayout();

  // 2. Add the datasource to a HashMap parameter
  HashMap params = new HashMap(); 
  // Here we're adding a custom datasource named "subreportDs". 
  // It's the name we put in the Layouter
  params.put("subreportDs", getDatasource());
  
  // 3. Compile the report layout
  // This will regardless if you activate the .setWhenNoDataAllSectionNoDetail() property or not
  // in the parentReportBuilder under the Layouter class. However you're FORCED 
  // to provide a dummy datasource for the parent report
  JasperReport jr = DynamicJasperHelper.generateJasperReport(dr, new ClassicLayoutManager(), params);
  
  // 4. Generate the JasperPrint object which also fills the report with data
  // This will regardless if you activate the .setWhenNoDataAllSectionNoDetail() property or not
  // in the parentReportBuilder under the Layouter class. However you're FORCED 
  // to provide a dummy datasource for the parent report
  JasperPrint jp = JasperFillManager.fillReport(jr, params, getJRDummyDatasource() );

  // We can also combine compilation (3) and generation (4) in a single line
  // This will only work if you add the .setWhenNoDataAllSectionNoDetail() property
  // in the parentReportBuilder under the Layouter class
  //JasperPrint jp = DynamicJasperHelper.generateJasperPrint(dr, new ClassicLayoutManager(), params);
  
  // Create the output byte stream where the data will be written
  ByteArrayOutputStream baos = new ByteArrayOutputStream();

  // 5. Export to XLS format
  Exporter.exportToXLS(jp, baos);
  
  // 6. Set the response properties
  String fileName = "SalesReport.xls";
  response.setHeader("Content-Disposition", "inline; filename=" + fileName);
  // Make sure to set the correct content type
  response.setContentType("application/vnd.ms-excel");
  // Assign the byte stream's size
  response.setContentLength(baos.size());

  // 7. Write to response stream
  Writer.write(response, baos);
 }
 
 /**
  * Retrieves a Java List datasource.
  * <p>
  * The data is retrieved from a Hibernate HQL query.
  * @return
  */
 @SuppressWarnings("unchecked")
 private List getDatasource() {
  logger.debug("Retrieving datasource");
  
      // Retrieve session
  Session session = sessionFactory.getCurrentSession();
  // Create query for retrieving products
  Query query = session.createQuery("FROM PowerSupply");
  // Execute query
  List<PowerSupply> result = query.list();

  // Return the datasource
  return result;
 }
 
 /**
  * Retrieves a dummy JRDataSource. 
  * @return
  */
 @SuppressWarnings("unchecked")
 private JRDataSource getJRDummyDatasource() {
  logger.debug("Retrieving JRdatasource");
  
  // Return the datasource
  return null;
 }
}
This service is our download service for generating the report document. It should be clear what each line of code is doing. Notice that in step 2 we have to declare the datasource as a key in the HashMap parameter:
HashMap params = new HashMap(); 
  // Here we're adding a custom datasource named "subreportDs". 
  // It's the name we put in the Layouter
  params.put("subreportDs", getDatasource());

Also, we've declared a getDatasource() method that retrieves a list of PowerSupply.
List<PowerSupply> result = query.list();
return result;

Furthermore, we declared a getJRDummyDatasource() method to generate an empty list. This is required if you want to use steps 3 and 4.

Alternatively, you can combine steps 3 and 4 and avoid this dummy datasource entirely:
// We can also combine compilation (3) and generation (4) in a single line
// This will only work if you add the .setWhenNoDataAllSectionNoDetail() property
// in the parentReportBuilder under the Layouter class
JasperPrint jp = DynamicJasperHelper.generateJasperPrint(dr, new ClassicLayoutManager(), params);

The service has been divided into separate classes to encapsulate specific jobs.

The Layouter

The purpose of the Layouter is to layout the design of the report. Here's where we declare the dynamic columns and special properties of the document. At the end of the class, we appended an HQL query

Layouter.java
package org.krams.tutorial.report;

import java.util.Date;

import org.apache.log4j.Logger;

import ar.com.fdvs.dj.core.DJConstants;
import ar.com.fdvs.dj.core.layout.ClassicLayoutManager;
import ar.com.fdvs.dj.domain.DJDataSource;
import ar.com.fdvs.dj.domain.DynamicReport;
import ar.com.fdvs.dj.domain.builders.ColumnBuilderException;
import ar.com.fdvs.dj.domain.builders.DJBuilderException;
import ar.com.fdvs.dj.domain.builders.FastReportBuilder;
import ar.com.fdvs.dj.domain.builders.SubReportBuilder;
import ar.com.fdvs.dj.domain.entities.Subreport;

/**
 * Everything under the package org.krams.tutorial.dynamicjasper are settings imposed by DynamicJasper (not Jasper)
 *<p>
 * Builds the report layout, the template, the design, the pattern or whatever synonym you may want to call it.
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Layouter {

 private static Logger logger = Logger.getLogger("service");
 
 /**
  * Builds the report layout. This doesn't have any data yet. This is your template.
  * 
  * @return the layout
  */
 public static DynamicReport buildChildReportLayout() throws ColumnBuilderException, ClassNotFoundException {
  
  // Create an instance of FastReportBuilder
  FastReportBuilder drb = new FastReportBuilder();
  
  // Create columns
  // The column fields must match the name and type of the properties in your datasource
  drb.addColumn("Id", "id",  Long.class.getName(), 50)
        .addColumn("Brand", "brand", String.class.getName(), 50)
        .addColumn("Model", "model" , String.class.getName(), 50)
        .addColumn("Max Power", "maximumPower", String.class.getName(), 50)
        .addColumn("Price", "price", Double.class.getName(), 50)
        .addColumn("Efficiency", "efficiency", Double.class.getName(), 50)
        .setPrintColumnNames(true)
        
         // Disables pagination
        .setIgnorePagination(true)
        
        // Experiment with this numbers to see the effect
        .setMargins(0, 0, 0, 0) 
        
        // Set the title shown inside the Excel file
        .setTitle("Sales Report") 
        
        // Set the subtitle shown inside the Excel file
        .setSubtitle("This report was generated at " + new Date()) 
        
  // Set to true if you want to constrain your report within the page boundaries
  // For longer reports, set it to false
        .setUseFullPageWidth(true);

  // Set the name of the file
        //drb.setReportName("Sales Report");
        
        // Build the report layout. It doesn't have any data yet!
        DynamicReport dr = drb.build();
        
        // Return the layout
        return dr;
 }
 
 public static DynamicReport buildParentReportLayout() throws ColumnBuilderException, ClassNotFoundException {
  
  // Create an instance of FastReportBuilder
  FastReportBuilder parentReportBuilder = new FastReportBuilder();
 
         // Disables pagination
  parentReportBuilder.setIgnorePagination(true)
  
        // Experiment with this numbers to see the effect
        .setMargins(0, 0, 0, 0) 
        
        // This is a critical property!!!
        // If the parentReportBuilder doesn't have an associated datasource, you must set this property!
        .setWhenNoDataAllSectionNoDetail()
        
  // Set to true if you want to constrain your report within the page boundaries
  // For longer reports, set it to false
        .setUseFullPageWidth(true);

  // Set the name of the file
        parentReportBuilder.setReportName("Concat Report");
        
  // Retrieve the childReportLayout and assign to a subreport builder
     SubReportBuilder subReportBuilder = new SubReportBuilder();
     subReportBuilder.setDynamicReport(buildChildReportLayout(), new ClassicLayoutManager());
     
  // Create a subreport
     Subreport subreport = null;
  try {
   subreport = subReportBuilder.build();
   // Assign the datasource parameter to the subreport
   // Take note the name of the datasource: myds
   subreport.setDatasource( new DJDataSource( "subreportDs", 
     DJConstants.DATA_SOURCE_ORIGIN_PARAMETER, 
     DJConstants.DATA_SOURCE_TYPE_COLLECTION));

   // Add the subreport to the parent report layout
   parentReportBuilder.addConcatenatedReport(subreport);
   
  } catch (DJBuilderException e) {
   logger.error("Unable to generate subreport");
   throw new RuntimeException("Unable to generate subreport");
  }
  
        // Build the parent report layout. It doesn't have any data yet!
        DynamicReport dr = parentReportBuilder.build();
        
        // Return the layout
        return dr;
 }
 
 
}
The Layouter has two methods buildParentReportLayout() and buildChildReportLayout() for building the parent and the child layout respectively. To attach the child report as a Subreport, we have to convert the DynamicReport object into a Subreport object:
// Retrieve the childReportLayout and assign to a subreport builder
SubReportBuilder subReportBuilder = new SubReportBuilder();
subReportBuilder.setDynamicReport(buildChildReportLayout(), new ClassicLayoutManager());
...
subreport = subReportBuilder.build();
subreport.setDatasource( new DJDataSource( "subreportDs", 
     DJConstants.DATA_SOURCE_ORIGIN_PARAMETER, 
     DJConstants.DATA_SOURCE_TYPE_COLLECTION));
To attach the subreport to the parent report, we have to declare the following:
// Add the subreport to the parent report layout
parentReportBuilder.addConcatenatedReport(subreport);

Take note. DynamicJasper provides a way to add a DynamicReport object as a child report without converting it to a Subreport object. We'll discuss that on the next tutorial.

The Exporter

The purpose of the Exporter is to export the JasperPrint object into different formats, like Excel, PDF, and CSV. Our current implementation exports the document as an Excel format.

Exporter.java
package org.krams.tutorial.report;

import java.io.ByteArrayOutputStream;

import org.apache.log4j.Logger;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.export.JRXlsAbstractExporterParameter;
import net.sf.jasperreports.engine.export.JRXlsExporter;

/**
 * Everything under the package org.krams.tutorial.jasper are settings imposed by Jasper (not DynamicJasper)
 * <p>
 * An exporter for exporting the report in various formats, i.e Excel, PDF, CSV. Here we declare a PDF exporter
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Exporter {

 private static Logger logger = Logger.getLogger("service");
 
 /**
  * Exports a report to XLS (Excel) format. You can declare another export here, i.e PDF or CSV.
  * You don't really need to create a separate class or method for the exporter. You can call it
  * directly within your Service or Controller.
  * 
  * @param jp the JasperPrint object
  * @param baos the ByteArrayOutputStream where the report will be written
  */
 public static void exportToXLS(JasperPrint jp, ByteArrayOutputStream baos) throws JRException {
  // Create a JRXlsExporter instance
  JRXlsExporter exporter = new JRXlsExporter();
  
  // Here we assign the parameters jp and baos to the exporter
  exporter.setParameter(JRExporterParameter.JASPER_PRINT, jp);
        exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);
  
        // Excel specific parameters
        // Check the Jasper (not DynamicJasper) docs for a description of these settings. Most are 
        // self-documenting
        exporter.setParameter(JRXlsAbstractExporterParameter.IS_ONE_PAGE_PER_SHEET, Boolean.FALSE);
        exporter.setParameter(JRXlsAbstractExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.TRUE);
        exporter.setParameter(JRXlsAbstractExporterParameter.IS_WHITE_PAGE_BACKGROUND, Boolean.FALSE);
        
        // Retrieve the exported report in XLS format
        exporter.exportReport();
 }
}
It's worth mentioning that this class has nothing to do with DynamicJasper. Everything is Jasper-related configuration.

The Writer

The purpose of the Writer is to write the "exported" document to the output stream. Once the document has been written to the stream, the user will receive the document ready to be downloaded.

Writer.java
package org.krams.tutorial.report;

import java.io.ByteArrayOutputStream;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

/**
 * Writes the report to the output stream
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Writer {

 private static Logger logger = Logger.getLogger("service");
 /**
  * Writes the report to the output stream
  */
 public static void write(HttpServletResponse response, ByteArrayOutputStream baos) {
  
  logger.debug("Writing report to the stream");
  try {
   // Retrieve the output stream
   ServletOutputStream outputStream = response.getOutputStream();
   // Write to the output stream
   baos.writeTo(outputStream);
   // Flush the stream
   outputStream.flush();

  } catch (Exception e) {
   logger.error("Unable to write report to the output stream");
  }
 }
}

Controller

We've completed the domain and service layer of the application. Since we're developing a Spring MVC web application, we're required to declare a controller that will handle the user's request.

DownloadController.java
package org.krams.tutorial.controller;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JRException;
import org.apache.log4j.Logger;
import org.krams.tutorial.service.DownloadService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import ar.com.fdvs.dj.domain.builders.ColumnBuilderException;

/**
 * Handles download requests
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Controller
@RequestMapping("/download")
public class DownloadController {

 private static Logger logger = Logger.getLogger("controller");
 
 @Resource(name="downloadService")
 private DownloadService downloadService;

 /**
  * Downloads the report as an Excel format. 
  * 

* Make sure this method doesn't return any model. Otherwise, you'll get * an "IllegalStateException: getOutputStream() has already been called for this response" */ @RequestMapping(value = "/xls", method = RequestMethod.GET) public void getXLS(HttpServletResponse response, Model model) throws ColumnBuilderException, ClassNotFoundException, JRException { logger.debug("Received request to download report as an XLS"); // Delegate to downloadService. Make sure to pass an instance of HttpServletResponse downloadService.downloadXLS(response); } }

DownloadController is a simple controller that handles download requests. It delegates report generation to the DownloadService. Notice we're required to pass the HttpServletResponse to the service.

Database Configuration

We've completed the MVC module of the application. However we haven't created yet the Hibernate configuration and the MySQL database.

Our first task is to create an empty MySQL database.

Here are the steps:
1. Run MySQL
2. Open MySQL admin
3. Create a new database mydb

In this tutorial I've setup a local MySQL database and used phpmyadmin to administer it.

Next, we'll be declaring a hibernate-context.xml configuration file. Its purpose is to contain all of Spring-related configuration for Hibernate.

hibernate-context.xml

This configuration requires two external configurations further:

spring.properties
# database properties
app.jdbc.driverClassName=com.mysql.jdbc.Driver
app.jdbc.url=jdbc:mysql://localhost/mydb
app.jdbc.username=root
app.jdbc.password=

#hibernate properties
hibernate.config=/WEB-INF/hibernate.cfg.xml

hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
  "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
  
<hibernate-configuration>
 <session-factory>
  <!-- We're using a MySQL database so the dialect needs to be MySQL as well -->
  <!-- Also we want to use MySQL's InnoDB engine -->
  <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
  
  <!-- Enable this to see the Hibernate generated SQL statements in the logs -->
  <property name="show_sql">false</property>
  
  <!-- Setting this to 'create' will drop our existing database and re-create a new one.
    This is only good for testing. In production, this is a bad idea! -->
  <property name="hbm2ddl.auto">create</property>
 </session-factory>
</hibernate-configuration>

The Import.SQL

After declaring all the Hibernate-related configuration, let's now declare a SQL script that will populate our database with a sample data automatically.

import.sql
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Corsair', 'CMPSU-750TX', '750W', '109.99', '0.80')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Antec', 'NEO ECO 620C', '620W', '69.99', '0.80')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'OCZ', 'OCZ700MXSP', '700W', '89.99', '0.86')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Thermaltake', 'W0070RUC', '430W', '43.99', '0.65')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'COOLER MASTER', 'RS-460-PSAR-J3', '460W', '29.99', '0.70')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Rosewill', 'RG530-S12', '530W', '54.99', '0.80')
Make sure to place this document under the classpath. Hibernate will automatically import the contents of this document everytime your start the application. This is dictated by the hbm2ddl.auto setting we declared in the hibernate.cfg.xml earlier.

We're not required to create this import.sql file. We could of course create a MySQL SQL script and import it directly to the database, or add the data manually in the database. I just believe this is convenient for development purposes.

Spring MVC Configuration

We've declared all the necessary classes and Hibernate-related configuration of the application. However, we haven't created yet the required Spring MVC configuration.

Let's begin with the web.xml
web.xml
<servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 
 <servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>/krams/*</url-pattern>
 </servlet-mapping>

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
Take note of the URL pattern. When accessing any pages in our MVC application, the host name must be appended with
/krams
In the web.xml we declared a servlet-name spring. By convention, we must declare a spring-servlet.xml.

spring-servlet.xml
<!-- Declare a view resolver for resolving JSPs -->
 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" 
      p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
By convention, we must declare an applicationContext.xml as well.

applicationContext.xml
<!-- Activates various annotations to be detected in bean classes -->
 <context:annotation-config />
 
 <!-- Scans the classpath for annotated components that will be auto-registered as Spring beans.
  For example @Controller and @Service. Make sure to set the correct base-package-->
 <context:component-scan base-package="org.krams.tutorial" />
 
 <!-- Configures the annotation-driven Spring MVC Controller programming model.
 Note that, with Spring 3.0, this tag works in Servlet MVC only!  -->
 <mvc:annotation-driven /> 
 
 <!-- Loads Hibernate related configuration -->
 <import resource="hibernate-context.xml" />

Run the Application

We've completed the application. Our last task is to run the application and download the report.

To run the application, open your browser and enter the following URL:
http://localhost:8080/spring-djasper-hibernate-concatsr/krams/download/xls
This will automatically download the report document. Again, here's the final screenshot of the document:

Conclusion

That's it. We've managed to build a simple Spring MVC 3 application with reporting capabilities. We used DynamicJasper to generate the dynamic reports and Hibernate for the ORM framework. Lastly, we used Jasper's JRDataSource to wrap our Java List and used the wrapper object as the data source. Remember the data is still retrieved from a MySQL database.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-mvc-dynamicjasper-integration-tutorial/

You can download the project as a Maven build. Look for the spring-djasper-hibernate-concatsr.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a Subreport ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring 3 - DynamicJasper - Hibernate Tutorial: Using Plain List

In this tutorial we will build a simple Spring MVC 3 application with reporting capabilities. We will use DynamicJasper to generate the dynamic reports and Hibernate for the ORM framework. We will use a simple Java List as our data source. The data will be retrieved from a MySQL database.

This tutorial is part of the following reporting tutorial series that uses Jasper, DynamicJasper, and Apache POI:

Spring 3 - Apache POI - Hibernate: Creating an Excel Report Tutorial
Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a DynamicReport
Spring 3 - DynamicJasper - Hibernate Tutorial: Concatenating a Subreport
Spring 3 - DynamicJasper - Hibernate Tutorial: Using Plain List
Spring 3 - DynamicJasper - Hibernate Tutorial: Using JRDataSource
Spring 3 - DynamicJasper - Hibernate Tutorial: Using HQL Query

All of these tutorials produce the same document, and all of them demonstrate different ways of creating the same report.

What is DynamicJasper?
DynamicJasper (DJ) is an open source free library that hides the complexity of Jasper Reports, it helps developers to save time when designing simple/medium complexity reports generating the layout of the report elements automatically.

Source: http://dynamicjasper.com/

What is JasperReports?
JasperReports is the world's most popular open source reporting engine. It is entirely written in Java and it is able to use data coming from any kind of data source and produce pixel-perfect documents that can be viewed, printed or exported in a variety of document formats including HTML, PDF, Excel, OpenOffice and Word.

Source: http://jasperforge.org/projects/jasperreports

Background

Before we start our application, let's preview first the final print document:

Our document is a simple Excel document. It's a Sales Report for a list of power supplies. The data is retrieved from a MySQL database.

Domain

Notice that for each Power Supply entry there's a common set of properties:
id
brand
model
maximum power
price
efficiency

Development

Domain

We'll start our application by declaring the domain object PowerSupply

PowerSupply.java
package org.krams.tutorial.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * A simple POJO containing the common properties of a Power Supply
 * This is an annotated Hibernate entity. 
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Entity
@Table(name = "POWER_SUPPLY")
public class PowerSupply implements Serializable {

 private static final long serialVersionUID = 8634209606034270882L;

 @Id
 @Column(name = "ID")
 @GeneratedValue
 private Long id;
 
 @Column(name = "BRAND")
 private String brand;
 
 @Column(name = "MODEL")
 private String model;
 
 @Column(name = "MAXIMUM_POWER")
 private String maximumPower;
 
 @Column(name = "PRICE")
 private Double price;
 
 @Column(name = "EFFICIENCY")
 private Double efficiency;

 public Long getId() {
  return id;
 }

 public void setId(Long id) {
  this.id = id;
 }

 public String getBrand() {
  return brand;
 }

 public void setBrand(String brand) {
  this.brand = brand;
 }

 public String getModel() {
  return model;
 }

 public void setModel(String model) {
  this.model = model;
 }

 public String getMaximumPower() {
  return maximumPower;
 }

 public void setMaximumPower(String maximumPower) {
  this.maximumPower = maximumPower;
 }

 public Double getPrice() {
  return price;
 }

 public void setPrice(Double price) {
  this.price = price;
 }

 public Double getEfficiency() {
  return efficiency;
 }

 public void setEfficiency(Double efficiency) {
  this.efficiency = efficiency;
 }
 
}
PowerSupply is a simple POJO containing six private fields. Each of these fields have been annotated with @Column and assigned with corresponding database column names.
ID
BRAND
MODEL
MAXIMUM_POWER
PRICE
EFFICIENCY

Service

We'll be declaring a single service named DownloadService. This service is the heart of the application that will process and retrieve the report document.

The service will run the following steps:
1. Build the report layout
 2. Add the datasource to a HashMap parameter
 3. Compile the report layout
 4. Generate the JasperPrint object
 5. Export to a particular format, ie. XLS
 6. Set the HttpServletResponse properties
 7. Write to the output stream

DownloadService.java
package org.krams.tutorial.service;

import java.io.ByteArrayOutputStream;
import java.util.List;
import javax.servlet.http.HttpServletResponse;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperPrint;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.krams.tutorial.domain.PowerSupply;
import org.krams.tutorial.report.Exporter;
import org.krams.tutorial.report.Layouter;
import org.krams.tutorial.report.Writer;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import ar.com.fdvs.dj.core.DynamicJasperHelper;
import ar.com.fdvs.dj.core.layout.ClassicLayoutManager;
import ar.com.fdvs.dj.domain.DynamicReport;
import ar.com.fdvs.dj.domain.builders.ColumnBuilderException;
import javax.annotation.Resource;

/**
 * Service for processing DynamicJasper reports
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Service("downloadService")
@Transactional
public class DownloadService {

 private static Logger logger = Logger.getLogger("service");
 
 @Resource(name="sessionFactory")
 private SessionFactory sessionFactory;
 
 /**
  * Processes the download for Excel format.
  * It does the following steps:
  * <pre>1. Build the report layout
  * 2. Retrieve the datasource
  * 3. Compile the report layout
  * 4. Generate the JasperPrint object
  * 5. Export to a particular format, ie. XLS
  * 6. Set the HttpServletResponse properties
  * 7. Write to the output stream
  * </pre>
  */
 @SuppressWarnings("unchecked")
 public void downloadXLS(HttpServletResponse response) throws ColumnBuilderException, ClassNotFoundException, JRException {
  logger.debug("Downloading Excel report");

  // 1. Build the report layout
  DynamicReport dr = Layouter.buildReportLayout();

  // 2. Retrieve the datasource
  List<PowerSupply> ds = getDatasource();
  
  // 3. Compile the report layout
  // This won't work if we have a normal List because Jasper expects us to wrap the list as a JRDatasource
  //JasperReport jr = DynamicJasperHelper.generateJasperReport(dr, new ClassicLayoutManager(), null);
  
  // 4. Generate the JasperPrint object which also fills the report with data
  // This won't work if we have a normal List because Jasper expects us to wrap the list as a JRDatasource
  //JasperPrint jp = JasperFillManager.fillReport(jr, null, ds);

  // We MUST combine compilation (3) and generation (4) in a single line if we want to use a List
  JasperPrint jp = DynamicJasperHelper.generateJasperPrint(dr, new ClassicLayoutManager(), ds);
  
  // Create the output byte stream where the data will be written
  ByteArrayOutputStream baos = new ByteArrayOutputStream();

  // 5. Export to XLS format
  Exporter.exportToXLS(jp, baos);
  
  // 6. Set the response properties
  String fileName = "SalesReport.xls";
  response.setHeader("Content-Disposition", "inline; filename=" + fileName);
  // Make sure to set the correct content type
  response.setContentType("application/vnd.ms-excel");
  // Assign the byte stream's size
  response.setContentLength(baos.size());

  // 7. Write to response stream
  Writer.write(response, baos);
 }
 
 /**
  * Retrieves the datasource as as simple Java List.
  * The datasource is retrieved from a Hibernate HQL query.
  * @return
  */
 @SuppressWarnings("unchecked")
 private List<PowerSupply> getDatasource() {
  
      // Retrieve session
  Session session = sessionFactory.getCurrentSession();
  // Create query for retrieving products
  Query query = session.createQuery("FROM PowerSupply");
  // Execute query
  List<PowerSupply> result = query.list();
  
  // Return the datasource
  return result;
 }
}
This service is our download service for generating the report document. It should be clear what each line of code is doing. Notice that steps 3 and 4 MUST be combined as:
JasperPrint jp = DynamicJasperHelper.generateJasperPrint(dr, new ClassicLayoutManager(), ds);
Remember ds is the datasource as declared in step 2.

Also, we've declared a getDatasource() method that retrieves a list of PowerSupply. The list is a normal Java List.
List<PowerSupply> result = query.list();
return result;

The service has been divided into separate classes to encapsulate specific jobs.

The Layouter

The purpose of the Layouter is to layout the design of the report. Here's where we declare the dynamic columns and special properties of the document. At the end of the class, we appended an HQL query

Layouter.java
package org.krams.tutorial.report;

import java.util.Date;

import org.apache.log4j.Logger;

import ar.com.fdvs.dj.domain.DynamicReport;
import ar.com.fdvs.dj.domain.builders.ColumnBuilderException;
import ar.com.fdvs.dj.domain.builders.FastReportBuilder;

/**
 * Everything under the package org.krams.tutorial.dynamicjasper are settings imposed by DynamicJasper (not Jasper)
 *<p>
 * Builds the report layout, the template, the design, the pattern or whatever synonym you may want to call it.
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Layouter {

 private static Logger logger = Logger.getLogger("service");
 
 /**
  * Builds the report layout. This doesn't have any data yet. This is your template.
  * 
  * @return the layout
  */
 public static DynamicReport buildReportLayout() throws ColumnBuilderException, ClassNotFoundException {
  
  // Create an instance of FastReportBuilder
  FastReportBuilder drb = new FastReportBuilder();
  
  // Create columns
  // The column fields must match the name and type of the properties in your datasource
  drb.addColumn("Id", "id",  Long.class.getName(), 50)
        .addColumn("Brand", "brand", String.class.getName(), 50)
        .addColumn("Model", "model" , String.class.getName(), 50)
        .addColumn("Max Power", "maximumPower", String.class.getName(), 50)
        .addColumn("Price", "price", Double.class.getName(), 50)
        .addColumn("Efficiency", "efficiency", Double.class.getName(), 50)
        .setPrintColumnNames(true)
        
         // Disables pagination
        .setIgnorePagination(true)
        
        // Experiment with this numbers to see the effect
        .setMargins(0, 0, 0, 0) 
        
        // Set the title shown inside the Excel file
        .setTitle("Sales Report") 
        
        // Set the subtitle shown inside the Excel file
        .setSubtitle("This report was generated at " + new Date()) 
        
  // Set to true if you want to constrain your report within the page boundaries
  // For longer reports, set it to false
        .setUseFullPageWidth(true);

  // Set the name of the file
        //drb.setReportName("Sales Report");
        
        // Build the report layout. It doesn't have any data yet!
        DynamicReport dr = drb.build();
        
        // Return the layout
        return dr;
 }
}

The Exporter

The purpose of the Exporter is to export the JasperPrint object into different formats, like Excel, PDF, and CSV. Our current implementation exports the document as an Excel format.

Exporter.java
package org.krams.tutorial.report;

import java.io.ByteArrayOutputStream;

import org.apache.log4j.Logger;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.export.JRXlsAbstractExporterParameter;
import net.sf.jasperreports.engine.export.JRXlsExporter;

/**
 * Everything under the package org.krams.tutorial.jasper are settings imposed by Jasper (not DynamicJasper)
 * <p>
 * An exporter for exporting the report in various formats, i.e Excel, PDF, CSV. Here we declare a PDF exporter
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Exporter {

 private static Logger logger = Logger.getLogger("service");
 
 /**
  * Exports a report to XLS (Excel) format. You can declare another export here, i.e PDF or CSV.
  * You don't really need to create a separate class or method for the exporter. You can call it
  * directly within your Service or Controller.
  * 
  * @param jp the JasperPrint object
  * @param baos the ByteArrayOutputStream where the report will be written
  */
 public static void exportToXLS(JasperPrint jp, ByteArrayOutputStream baos) throws JRException {
  // Create a JRXlsExporter instance
  JRXlsExporter exporter = new JRXlsExporter();
  
  // Here we assign the parameters jp and baos to the exporter
  exporter.setParameter(JRExporterParameter.JASPER_PRINT, jp);
        exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);
  
        // Excel specific parameters
        // Check the Jasper (not DynamicJasper) docs for a description of these settings. Most are 
        // self-documenting
        exporter.setParameter(JRXlsAbstractExporterParameter.IS_ONE_PAGE_PER_SHEET, Boolean.FALSE);
        exporter.setParameter(JRXlsAbstractExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.TRUE);
        exporter.setParameter(JRXlsAbstractExporterParameter.IS_WHITE_PAGE_BACKGROUND, Boolean.FALSE);
        
        // Retrieve the exported report in XLS format
        exporter.exportReport();
 }
}
It's worth mentioning that this class has nothing to do with DynamicJasper. Everything is Jasper-related configuration.

The Writer

The purpose of the Writer is to write the "exported" document to the output stream. Once the document has been written to the stream, the user will receive the document ready to be downloaded.

Writer.java
package org.krams.tutorial.report;

import java.io.ByteArrayOutputStream;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

/**
 * Writes the report to the output stream
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Writer {

 private static Logger logger = Logger.getLogger("service");
 /**
  * Writes the report to the output stream
  */
 public static void write(HttpServletResponse response, ByteArrayOutputStream baos) {
  
  logger.debug("Writing report to the stream");
  try {
   // Retrieve the output stream
   ServletOutputStream outputStream = response.getOutputStream();
   // Write to the output stream
   baos.writeTo(outputStream);
   // Flush the stream
   outputStream.flush();

  } catch (Exception e) {
   logger.error("Unable to write report to the output stream");
  }
 }
}

Controller

We've completed the domain and service layer of the application. Since we're developing a Spring MVC web application, we're required to declare a controller that will handle the user's request.

DownloadController.java
package org.krams.tutorial.controller;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JRException;
import org.apache.log4j.Logger;
import org.krams.tutorial.service.DownloadService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import ar.com.fdvs.dj.domain.builders.ColumnBuilderException;

/**
 * Handles download requests
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Controller
@RequestMapping("/download")
public class DownloadController {

 private static Logger logger = Logger.getLogger("controller");
 
 @Resource(name="downloadService")
 private DownloadService downloadService;

 /**
  * Downloads the report as an Excel format. 
  * 

* Make sure this method doesn't return any model. Otherwise, you'll get * an "IllegalStateException: getOutputStream() has already been called for this response" */ @RequestMapping(value = "/xls", method = RequestMethod.GET) public void getXLS(HttpServletResponse response, Model model) throws ColumnBuilderException, ClassNotFoundException, JRException { logger.debug("Received request to download report as an XLS"); // Delegate to downloadService. Make sure to pass an instance of HttpServletResponse downloadService.downloadXLS(response); } }

DownloadController is a simple controller that handles download requests. It delegates report generation to the DownloadService. Notice we're required to pass the HttpServletResponse to the service.

Database Configuration

We've completed the MVC module of the application. However we haven't created yet the Hibernate configuration and the MySQL database.

Our first task is to create an empty MySQL database.

Here are the steps:
1. Run MySQL
2. Open MySQL admin
3. Create a new database mydb

In this tutorial I've setup a local MySQL database and used phpmyadmin to administer it.

Next, we'll be declaring a hibernate-context.xml configuration file. Its purpose is to contain all of Spring-related configuration for Hibernate.

hibernate-context.xml

This configuration requires two external configurations further:

spring.properties
# database properties
app.jdbc.driverClassName=com.mysql.jdbc.Driver
app.jdbc.url=jdbc:mysql://localhost/mydb
app.jdbc.username=root
app.jdbc.password=

#hibernate properties
hibernate.config=/WEB-INF/hibernate.cfg.xml

hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
  "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
  
<hibernate-configuration>
 <session-factory>
  <!-- We're using a MySQL database so the dialect needs to be MySQL as well -->
  <!-- Also we want to use MySQL's InnoDB engine -->
  <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
  
  <!-- Enable this to see the Hibernate generated SQL statements in the logs -->
  <property name="show_sql">false</property>
  
  <!-- Setting this to 'create' will drop our existing database and re-create a new one.
    This is only good for testing. In production, this is a bad idea! -->
  <property name="hbm2ddl.auto">create</property>
 </session-factory>
</hibernate-configuration>

The Import.SQL

After declaring all the Hibernate-related configuration, let's now declare a SQL script that will populate our database with a sample data automatically.

import.sql
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Corsair', 'CMPSU-750TX', '750W', '109.99', '0.80')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Antec', 'NEO ECO 620C', '620W', '69.99', '0.80')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'OCZ', 'OCZ700MXSP', '700W', '89.99', '0.86')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Thermaltake', 'W0070RUC', '430W', '43.99', '0.65')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'COOLER MASTER', 'RS-460-PSAR-J3', '460W', '29.99', '0.70')
insert into POWER_SUPPLY (ID, BRAND, MODEL, MAXIMUM_POWER, PRICE, EFFICIENCY) values (null, 'Rosewill', 'RG530-S12', '530W', '54.99', '0.80')
Make sure to place this document under the classpath. Hibernate will automatically import the contents of this document everytime your start the application. This is dictated by the hbm2ddl.auto setting we declared in the hibernate.cfg.xml earlier.

We're not required to create this import.sql file. We could of course create a MySQL SQL script and import it directly to the database, or add the data manually in the database. I just believe this is convenient for development purposes.

Spring MVC Configuration

We've declared all the necessary classes and Hibernate-related configuration of the application. However, we haven't created yet the required Spring MVC configuration.

Let's begin with the web.xml
web.xml
<servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 
 <servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>/krams/*</url-pattern>
 </servlet-mapping>

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
Take note of the URL pattern. When accessing any pages in our MVC application, the host name must be appended with
/krams
In the web.xml we declared a servlet-name spring. By convention, we must declare a spring-servlet.xml.

spring-servlet.xml
<!-- Declare a view resolver for resolving JSPs -->
 <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" 
      p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
By convention, we must declare an applicationContext.xml as well.

applicationContext.xml
<!-- Activates various annotations to be detected in bean classes -->
 <context:annotation-config />
 
 <!-- Scans the classpath for annotated components that will be auto-registered as Spring beans.
  For example @Controller and @Service. Make sure to set the correct base-package-->
 <context:component-scan base-package="org.krams.tutorial" />
 
 <!-- Configures the annotation-driven Spring MVC Controller programming model.
 Note that, with Spring 3.0, this tag works in Servlet MVC only!  -->
 <mvc:annotation-driven /> 
 
 <!-- Loads Hibernate related configuration -->
 <import resource="hibernate-context.xml" />

Run the Application

We've completed the application. Our last task is to run the application and download the report.

To run the application, open your browser and enter the following URL:
http://localhost:8080/spring-djasper-hibernate-list/krams/download/xls
This will automatically download the report document. Again, here's the final screenshot of the document:

Conclusion

That's it. We've managed to build a simple Spring MVC 3 application with reporting capabilities. We used DynamicJasper to generate the dynamic reports and Hibernate for the ORM framework. Lastly, we used a plain Java List as the data source where the data is retrieved from a MySQL database.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-mvc-dynamicjasper-integration-tutorial/

You can download the project as a Maven build. Look for the spring-djasper-hibernate-list.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring 3 - DynamicJasper - Hibernate Tutorial: Using Plain List ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share