Академический Документы
Профессиональный Документы
Культура Документы
InputStream reportStream
= this.class.getResourceAsStream("/sales-report.xml");
JasperDesign jasperDesign = JasperManager.loadXmlDesign(reportStream);
JasperReport jasperReport = JasperManager.compileReport(jasperDesign);
This approach will work well for small lists. However, for reports involving tens or hundreds of thousands of lines, it is
inefficiant, memory-consuming, and slow. Indeed, experience shows that, when running on a standard Tomcat
configuration, a list returning as few as 10000 business objects can cause OutOfMemory exceptions. It also wastes time
building a bulky list of objects before processing them, and pollutes the Hibernate session (and possibly second-level
caches with temporary objects.
The Hibernate Data Access Layer : The QueryProvider interface and its implementations
We start with the optimised Hibernate data access. (you may note that this layer is not actually Hibernate-specific, so other
implementations could implement other types of data access without impacting the design).
A CriteriaSet is simply a JavaBean which contains parameters which may be passed to the Hibernate query. It is
simply a generic way of encapsulating a set of parameters. A QueryProvider provides a generic way of returning an
arbitrary subset of the query results set. The essential point is that query results are read in small chunks, not all at once.
This allows more efficient memory handling and better performance.
/**
* A QueryProvidor provides a generic way of fetching a set of objects.
*/
public interface QueryProvider {
/**
* Return a set of objects based on a given criteria set.
* @param firstResult the first result to be returned
* @param maxResults the maximum number of results to be returned
* @return a list of objects
*/
List getObjects(CriteriaSet criteria,
int firstResult,
int maxResults) throws HibernateException;
}
A typical implementation of this class simply builds a Hibernate query using the specified criteria set and returns the
requested subset of results. For example :
query.setParameter("categoryCode",
productCriteria.getCategoryCode();
return query.setCacheable(true)
.setFirstResult(firstResult)
.setMaxResults(maxResults)
.setFetchSize(100)
.list();
}
}
A more sophisticated implementation is helpful for dynamic queries. We define an abstract BaseQueryProvider class
which can be used for dynamic query generation. This is typically useful when the report has to be generated using several
parameters, some of which are optionnal.. Each derived class overrides the buildCriteria() method. This method
builds a Hibernate Criteria object using the specified Criteria set as appropriate :
Criteria queryCriteria
= sess.createCriteria(Sale.class);
if (salesCriteria.getStartDate() != null) {
queryCriteria.add(
Expression.eq("getStartDate",
salesCriteria.getStartDate()));
}
// etc...
return queryCriteria;
}
}
Note that a QueryProvider does not need to return Hibernate-persisted objects. Large-volume queries can sometimes be
more efficiently implemented by returning custom-made JavaBeans containing just the required columns. HQL allows you
to to this quite easily :
return query.setCacheable(true)
.setFirstResult(firstResult)
.setMaxResults(maxResults)
.setFetchSize(100)
.list();
}
}
The standard implementation of this interface reads Hibernate objects using a given QueryProvider and returns them to
JasperReports one by one. Here is the source code of this class (getters, setters, logging code and error-handling code
have been removed for clarty) :
//
// Getters and setters for criteriaSet and queryProvider
//
...
Finally, we have to be able to call the Hibernate data source from JasperReports. To do so, we start by looking at the
JasperManager fillReport() method, which takes a JRDataSource object as its third parameter and uses it to generate the
report :
/**
* Replace the character "_" by a ".".
*
* @param fieldName the name of the field
* @return the value in the cache or make
* the replacement and return this value
*/
private String getFieldName(String fieldName) {
String filteredFieldName
= (String) fieldNameMap.get(fieldName);
if (filteredFieldName == null) {
filteredFieldName = fieldName.replace('_','.');
fieldNameMap.put(fieldName,filteredFieldName);
}
return filteredFieldName;
}
}
This class is basically just a proxy between JasperReports and the Hibernate data source object. The only tricky bit is field
name handling. For some reason, JasperReports does not accept field names containing dots (ex. "product.code").
However, when you retrieve a set of Hibernate-persisted business objects, you often need to access object attributes. To
get around this, we replace the "." by a "_" in the JasperReport template (ex. "product_code" instead of "product.code"), and
convert back to a conventional JavaBean format in the getFieldName() method.
InputStream reportStream
= this.class.getResourceAsStream("/sales-report.xml");
JasperDesign jasperDesign
= JasperManager.loadXmlDesign(reportStream);
JasperReport jasperReport
= JasperManager.compileReport(jasperDesign);
ReportDataSource hibernateDataSource
= new ReportDataSourceImpl();
hibernateDataSource.setQueryProvider(new SalesQueryProvider());
hibernateDataSource.setCriteriaSet(salesCriteria);
ReportSource rs = new ReportSource(hibernateDataSource);
JasperPrint jasperPrint
= JasperManager.fillReport(jasperReport,
parameters,
rs);
JasperExportManager.exportReportTsoPdfFile(jasperPrint,
"sales-report.pdf");
Conclusion
JasperReports is a powerful and flexible reporting tool. To achieve high-performance Hibernate intergration however, some
extra work is needed. One way is by writing optimised data access classes which can be plugged into the JasperReports
architecture. Using this approach, you can take advantage of the full power of Hibernate in your JasperReports reporting
solutions.