Member Menu
 
 Monthly JBoss newsletter:
 
Hibernate Books
CaveatEmptor

Using JasperReports with Hibernate

This section will demonstrate a couple of ways to use the results of a Hibernate query as the datasource of a report in JasperReports.

When the Hibernate query returns a simple collection of objects, The JRBeanCollectionDataSource class in the JasperReports library will work fine.

List cats = session.find("from eg.Cat");

Map parameters = new HashMap();
parameters.put("Title", "The Cat Report");

InputStream reportStream = this.class.getResourceAsStream("/the-cat-report.xml");
JasperDesign jasperDesign = JasperManager.loadXmlDesign(reportStream);
JasperReport jasperReport = JasperManager.compileReport(jasperDesign);

JRBeanCollectionDataSource ds = new JRBeanCollectionDataSource(cats);
JasperPrint jasperPrint = JasperManager.fillReport(jasperReport, parameters, ds);

JasperManager.printReportToPdfFile(jasperPrint, "the-cat-report.pdf");

However, when the Hibernate query returns tuples of objects (each tuple as an array, each array as an element in the returned List), things get a little tricky. Jasper needs a way to reference each object in the array by a field name. This class is a good solution, but at this time you are required to pass an array of field names matching the results of the query.

public class HibernateQueryResultDataSource implements JRDataSource { 

  private String[] fields; 
  private Iterator iterator; 
  private Object currentValue; 

  public HibernateQueryResultDataSource(List list, String[] fields) { 
    this.fields = fields; 
    this.iterator = list.iterator(); 
  } 

  public Object getFieldValue(JRField field) throws JRException { 
    Object value = null; 
    int index = getFieldIndex(field.getName()); 
    if (index > -1) { 
      Object[] values = (Object[])currentValue; 
      value = values[index]; 
    } 
    return value; 
  } 

  public boolean next() throws JRException { 
    currentValue = iterator.hasNext() ? iterator.next() : null; 
    return (currentValue != null); 
  } 

  private int getFieldIndex(String field) { 
    int index = -1; 
    for (int i = 0; i < fields.length; i++) { 
      if (fields[i].equals(field)) { 
        index = i; 
        break; 
      } 
    } 
    return index; 
  } 

}

Now running your report would look something like this:

List cats = session.find("select cat.type, cat.birthdate, cat.name from eg.DomesticCat cat");

Map parameters = new HashMap();
parameters.put("Title", "The Cat Report");

InputStream reportStream = this.class.getResourceAsStream("/the-cat-report.xml");
JasperDesign jasperDesign = JasperManager.loadXmlDesign(reportStream);
JasperReport jasperReport = JasperManager.compileReport(jasperDesign);

String[] fields = new String[] { "type", "birthdate", "name"};
HibernateQueryResultDataSource ds = new HibernateQueryResultDataSource(cats, fields);
JasperPrint jasperPrint = JasperManager.fillReport(jasperReport, parameters, ds);

JasperManager.printReportToPdfFile(jasperPrint, "the-cat-report.pdf");

Another, alternate implementation, from Erik Romson:

public class HibernateQueryResultDataSource implements JRDataSource
{
    private static final transient Logger logger = 
        Logger.Factory.getInstance(HibernateQueryResultDataSource.class);
    protected HashMap fieldsToIdxMap=new HashMap();
    protected Iterator iterator;
    protected Object currentValue;
    List values;

    public HibernateQueryResultDataSource(List list, String query)
    {
        int start =query.indexOf("select ");
        int stop =query.indexOf(" from ");
        Assertion.assertTrue(
           (start!=-1) && 
           (stop!=-1),
           "The query "+
           query+
           " must be of the form select x,x from ..."
        );
        start+="select".length();
        String parameters=query.substring(start,stop);
        parameters=parameters.trim();
        Assertion.assertTrue(
            parameters.length()>0,
            "The query "+
            query+
            " seems to be weird"
        );
        StringTokenizer tokenizer=new StringTokenizer(parameters,",");
        int idx=0;
        while( tokenizer.hasMoreTokens() )
        {
            String parameter=tokenizer.nextToken();
            fieldsToIdxMap.put( parameter.trim(), new Integer(idx) );
            idx++;
        }
        values=list;
        this.iterator = list.iterator();
    }

    public Object getFieldValue(JRField field) throws JRException
    {
        String fieldName=field.getName().replace('_','.'); 
        //JasperReports does not allow '.' in field names, so we 
        //use '_' for nested properties instead and must convert here 
        //(comment added by kbrown)
        Integer idxInt=(Integer) fieldsToIdxMap.get(fieldName);
        if (idxInt==null)
        {
            Assertion.fail(
                "The field \""+
                fieldName+
                 "\" did not have an index mapping. Should be one off "+
                fieldsToIdxMap.keySet()
            );
        }
        Object[] values = (Object[]) currentValue;
        Assertion.assertTrue(
            idxInt.intValue()<values.length,
            "The index from field "+
            fieldName+
            " was illegal"
        );
        Object value= values[ idxInt.intValue() ];
        if ( logger.isDebugEnabled() )
        {
            logger.debug("fetched value "+value+" from field "+fieldName);
        }
        return value;
    }

    public boolean next() throws JRException
    {
        currentValue = iterator.hasNext() ? iterator.next() : null;
        return currentValue != null;
    }

    public List getValues()
    {
        return values;
    }
}

Using the information from this page I've made a patch to JasperReports 0.6.5. You can find the patch at: http://www.waltermourao.com.br/JasperReports/jr65_hql.zip or you can download the full version from: http://www.waltermourao.com.br/JasperReports/jasperreports-hql-0.6.5.zip


  NEW COMMENT

can you give us the jasper xml template for this example? 12 Feb 2004, 13:59 afelipe
Hi,
I'm trying to use jasper with hibernate.

Can you give us the jasper xml template for this example?

I'm having trouble in defining the attribute's names. When working 
with normal datasets, the fieldnames are capitalized. 
What's the correct way to define them when the fieldnames relate to 
class attributes ?

Thanks in advance,
André Felipe
afconsult (at) yahoo (dot) com
 
jasper xml template for this example ... 30 Jul 2004, 03:45 sylvain_2020
Hi,
I would do interested in having this XML template as well.

Would it be possible to get it ?

Thanks in advance

sylvain_2020
sylvain_2020(at)msn(dot)com
 
sharing 26 Sep 2004, 18:38 vido
I rewrote the getFieldValue to use reflection. Hope this helps.

-V

public class HibernateQueryResultDataSource implements JRDataSource { 

	  private Iterator iterator; 
	  private Object currentValue; 

	  public HibernateQueryResultDataSource(List list) { 
	    this.iterator = list.iterator(); 
	  } 

	  public Object getFieldValue(JRField field) throws JRException { 
	    Object value = null; 
		try {
		    Method fld = currentValue.getClass().getMethod("get" +
field.getName(),null);
		    System.out.println(fld.getName());
			value = fld.invoke(currentValue,null);
			System.out.println(value);
		}catch (IllegalAccessException iae) {
			iae.printStackTrace();
		} catch(InvocationTargetException ite) {
			ite.printStackTrace();
		}catch(NoSuchMethodException nsme) {
			nsme.printStackTrace();
		}
	    return value; 
	  } 

	  public boolean next() throws JRException { 
	    currentValue = iterator.hasNext() ? iterator.next() : null; 
	    return (currentValue != null); 
	  } 
	}
 
Getter by BeanUtils 02 Feb 2005, 13:31 gombasa
Here is a slightly reworked version of the previous post, which 
generates the getter's name with BeanUtils:

  public Object getFieldValue(JRField field) throws JRException {
        Object value = null;
        try {
            Method getter = PropertyUtils.getReadMethod(PropertyUtils.
getPropertyDescriptor(currentValue, field.getName()));
            value = getter.invoke(currentValue, null);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return value;
    }
    
(It might be a bit more standard.)
 
Other for reflection 09 Mar 2005, 04:33 rengar
-Class for use relations many-to-one inside hibernate class.

public class HibernateQueryResultDataSource implements JRDataSource {
    
    private Iterator iterator;
    private Object currentValue;
    
    public HibernateQueryResultDataSource(List list) {
        this.iterator = list.iterator();
    }
    
    public Object getFieldValue(JRField field) throws JRException {
        Object value = null;
        String methodName = null;
        

           StringTokenizer st = new StringTokenizer(field.getName
(),"_");
            value = currentValue;  
            
            while(st.hasMoreTokens()){
                methodName = st.nextToken();
                methodName = "get"+methodName.replaceFirst
(methodName.substring(0,1), methodName.substring(0,1).toUpperCase
());                
                try{
                   
                    Method metodoFinal = value.getClass
().getDeclaredMethod(methodName);
                    value = metodoFinal.invoke(value);
                    
                }catch(Exception e){
                    throw new JRException("Error trying to instanciate 
hibernate class.");
                }
            }
        return value;
    }    
    
    public boolean next() throws JRException {
        currentValue = iterator.hasNext() ? iterator.next() : null;
        return (currentValue != null);
    }
    
}
 
Another JRDataSource for hibernate 13 May 2005, 07:06 sgallet
Allows access to compenents (use __ instead of .).
Allows use of collection,map in subreports.

import java.lang.reflect.*;
import java.util.*;

import org.apache.commons.beanutils.*;
import org.apache.commons.logging.*;

import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.data.*;
import net.sf.jasperreports.engine.export.*;
import net.sf.jasperreports.engine.util.*;
import net.sf.jasperreports.view.*;


public class JRHibernateDataSource implements JRDataSource {

    protected HashMap fieldsToIdxMap=new HashMap();

    protected Iterator iterator;

    protected Object currentValue;


    public JRHibernateDataSource(Collection list) {
        this.iterator = list.iterator();
    }

    public JRHibernateDataSource(Map list) {
      this.iterator = list.values().iterator();
  }

    private Object nestedFieldValue(Object object, String field) {
        Object value = null;
        if (field.indexOf("__")>-1) {
            try {
                Method nestedGetter =
PropertyUtils.getReadMethod(PropertyUtils.getPropertyDescriptor(object,
field.substring(0,field.indexOf("__"))));
                Object nestedObject = nestedGetter.invoke(object, null);
               
value=nestedFieldValue(nestedObject,field.substring(field.indexOf("__")+2,field.length()));
            } catch (Exception ex) {
                ex.printStackTrace();
            }           
        } else {
            try {
                Method getter =
PropertyUtils.getReadMethod(PropertyUtils.getPropertyDescriptor(object,
field));
                value = getter.invoke(object, null);
               
if(Collection.class.isAssignableFrom(getter.getReturnType())) {
                    return new JRHibernateDataSource((Collection)value);
                }
                if(Map.class.isAssignableFrom(getter.getReturnType())) {
                    return new JRHibernateDataSource((Map)value);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }           
        }
        return value;
    }
   
    public Object getCurrentValue() throws JRException {
        return currentValue;
    }

    public Object getFieldValue(JRField field) throws JRException {
        return nestedFieldValue(currentValue,field.getName());
    }
   
    public boolean next() throws JRException {
        currentValue = iterator.hasNext() ? iterator.next() : null;
        return currentValue != null;
    } 
}
 
What about subReports ?? 16 May 2005, 19:30 j2ee_satya
Hello, 

How do we handle the case where we need subreports for a master 
report. 

I mean if we have cats and for each cat we want to list its kittens 
(one to many) how can we do that ??

Thank you.
 
Using HibernateQueryResultDataSource with subreports 17 May 2005, 09:19 sgallet
You can use the code in my previous code and update your master report
like this :


...
   <field name="kittens" class="java.lang.Object">
   </field>
... 
   <subreport>
      <reportElement positionType="Float" isPrintRepeatedValues="false"
mode="Opaque" x="64" y="29" width="450" height="30"
isRemoveLineWhenBlank="true"/>
      <dataSourceExpression>
         <![CDATA[$F{kittens}]]>
      </dataSourceExpression>
      <subreportExpression class="java.lang.String">
         <![CDATA["kittens.jasper"]]>
      </subreportExpression>
   </subreport>
 
We Should still use the HQL 18 May 2005, 14:45 j2ee_satya
Hello, 

    With the above code, i think we should still use the HQL to do 
a "select cats.name, cats.kittens From cats".
One more doubt here is do we give the above HQL as part of the 
<queryString> in the report design ??

But with Hibernate i think in most of the cases we get the list of 
cats directly and so, if there is a way to just send in the List<cats> 
as a datasource and the JRHibernateDataSource be able to detect the 
Sublists(the lists to which it is related as one-to-many) 
automatically would be a great idea.

Thank you,
 
How about selecting kittens based on a criteria. 18 May 2005, 15:02 j2ee_satya
I need kittens as a subreport but only its age, not its name or 
anything. 

Well the point is when using sql queries, i simply used to give a 
query string in the subreport and some parameters which got its values 
from fields of the master report., Now with HQL, i cant insert the HQL 
query in querystring, so how can i generate such dynamic reports ?? 

Like depending on a particular field on the master report the 
subreport should be generated. 

For example if there is a field in the CAT table called "disabled"

I can have a parameter in subreport for this particular field of 
master report and a query in the subreport says 

Select  * from kitten,cat where kitten.catId=Cat.Id and Cat.disabled=$P
{disabled}(This value is obtained from the master reports field).

Thank you.
 
XML Template 18 Jul 2007, 16:47 alexreyflores
POST QUESTIONS ON THE FORUM! COMMENTS HERE SHOULD ADD VALUE TO THE PAGE!

Hi! I would be interested in seeing the XML Template used for this 
exmple too, as I am having problems understanding how all this works 
within the XML Template. The rest is all clearly understandable for me, 
but I need to know the structure of this jrxml, because I will need to 
handle it in order to build dynamic reports and I won't use iReport as 
it would be used for standard reports building. Thanks in advance!!!
 
Just use return aliases 13 Sep 2007, 16:48 aluchko
I've actually found a real easy way to handle tuples without any
reflection or arrays of field names using aliases. 

It turns out there's a handy Query.getReturnAliases() method that will
return the names of all the aliases in a query so you can have the
following datasource.

(Note this class originally had some wrappers around the query and stuff
so there might be a trivial error since I didn't bother to write
something just to test it)
public class JRDataSourceHibQuery implements JRDataSource {

	private Query query;
	private Iterator<Object[]> results;
	private Object[] current;

	public JRDataSourceHibQuery(Query query) throws BasicException{
		this.query = query;
		results = query.list().iterator();
	}
	
	public Object getFieldValue(JRField fieldName) throws JRException {
		String[] aliases = query.getReturnAliases();
		for (int i = 0; i < aliases.length; i++) {
			if (aliases[i].equals(fieldName.getName()))
				return current[i];
		}
		return null;
	}

	public boolean next() throws JRException {
		current = (results.hasNext()? results.next(): null);
		return (current != null);
	}

}


The only quirk is instead of

"select cat.type, cat.birthdate, cat.name from eg.DomesticCat cat"

your query how has to be 

"select cat.type as type, cat.birthdate as birthdate, cat.name as name
from eg.DomesticCat cat"

Need to have those return aliases!
 
I can't get this to work 05 Dec 2007, 06:13 ssnukala
I am trying this code out, but it is not working for me. Here is my java
class

	 public void findItemDetails()
{
List Item=new ArrayList();
List outputRec = new ArrayList();
Criteria itemCrit = null;		
try 
{

Session session = HibernateUtil.getSessionFactory().getCurrentSession(); 
Transaction tx = (Transaction)session.beginTransaction(); 
itemCrit = session.createCriteria(Item.class);
System.out.println("the item crit is "+itemCrit);
QueryUtil101.createTypeCriteria("","",itemCrit,"");								
outputRec = itemCrit.list();
System.out.println("the outputrec is"+outputRec);

 Map parameters = new HashMap();
// parameters.put("Title", "The silaom Report");
 InputStream reportStream = this.class.getResourceAsStream("item.jrxml");
 InputStream reportStream =
this.getClass().getClassLoader().getResourceAsStream("item.jrxml");
 System.out.println("the reportstream is "+reportStream);
 JasperDesign jasperDesign = JRXmlLoader.load(reportStream);
 System.out.println("the jasperdesign is "+jasperDesign);
 JasperReport jasperReport =
JasperCompileManager.compileReport(jasperDesign);

	......
and other code here.

I also added a class path item called RESOURCES and added the folder
path where I have the item.jrxml. So the classpath has the report folder
in its path.

But i am not able to get past the getResourceAsStream command, it keeps
returning null. 
When i just copied the code above
this.class.getResourceAsStream("item.jrxml") i have a RED underline
under Class ("Syntax error on token class, "identifier" required). Not
sure why i am having this issue. 

Can you please elaborate on the directory structure you used and class
path components and also how this.class.getResourceAsStream works for
you and not for me. 

I am relatively new to java and jasper reports. So any insight and
detailed explanation would be greatly appreciated.
 
© Copyright 2006, Red Hat Middleware, LLC. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc. [Privacy Policy]