posts - 310, comments - 6939, trackbacks - 0, articles - 3
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

使用JasperReport与iBATIS开发Web报表

Posted on 2008-01-25 09:32 诗特林 阅读(6997) 评论(10)  编辑  收藏 所属分类: BI
应该IT168写的专稿:http://publish.itpub.net/j/2008-01-24/200801241020641.shtml
 

使用JasperReportiBATIS开发Web报表

 

JasperReport是一种采用纯Java实现的快速且非常流行的生成报表的类库。而对于任何的报表方案,取得数据并传递给报表引擎是其中最重要且最值得关心的方面。但遗憾的是,在这方面JasperReport本身似乎有一定的不足。而如今的很多Java应用程序,采用数据获取框架来进行数据的匹配与动态生成SQL。例如iBATIS数据映射框架。当然,如果只是使用JasperReport获取数据及管理数据的默认机制的话,不足以与现成的数据框架进行很好的平衡。但可喜的是,可以通过使用传递给JasperReport一个数据库的连接进行代替,当然这种连接可以通过使用XML进行非常方便的管理与配置。

源代码下载:http://cid-7b377ace522ff6c7.skydrive.live.com/self.aspx/iBatisJasper/iBatisJasper.rar

一、准备工作

Hibernate类似,iBATIS也是一个ORM解决方案,不同的是两者各有侧重。Hibernate提供了Java对象到数据库表之间的直接映射,开发者无需直接涉及数据库操作的实现细节,实现了一站式的ORM解决方案。而iBATIS则采取了另一种方式,即提供Java对象到SQL(面向参数和结果集)的映射实现,实际的数据库操作需要通过手动编写SQL实现。

iBATIS是又一个O/R Mapping解决方案,j2eeO/R方案真是多,和Hibernate相比,iBATIS最大的特点就是小巧,上手很快。如果你不需要太多复杂的功能,iBATIS是能满足你的要求又足够灵活的最简单的解决方案。在本文的示例中,采用Spring+JSF+iBATIS的模式进行示例的开发。所使用的lib如下图所示:


1.所使用的jar

二、在iReport中可视化定制模板

定制报表格式有二种方式,一种就是写jrxml文件,其实就是xml文件,只不过是后缀名不一样罢了。另一种方式更直接,就是生成一个JasperDesign类的实例,在japsperDesign中自己定义模板。jrxml文件也是通过一个JRXmlLoad加载过来,转成JasperDesign类的实例。也就是说写jrxml文件还需要进行解析,加载。现实中我们使用的报表一般格式比较固定,因而可以通过先使用iReport工具生成模板,再加载解析的方式。这种方式简单,而且可见性强。

iReport做为一个优秀的报表设计器,有着功能非常强大的特性。作为开源的Java程序,不但有适合于Windows安装的应用程序,同时,还提供完全开放的源代码,可供参考及原理分析。在本文中,主要通过图形界面中的模板设计,以及与数据库的连接等一系列的操作,来介绍如何定制一定要求的报表模板。

通过iReport可初见化的图形界面,可以设计出各种各样的简单或复杂的报表。通过iReport的这种可视化界面设计,可以为JasperReport提供优秀的报表模板,而无须去理解或是掌握那些复杂的XML语法。如此则可以Web报表开发节省大量的开发时间。

在进行iReport模板设计之前,需要编写JavaBean类:MonthlySalesBean.java,代码如下:

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;


public class MonthlySalesBean {

    
private int employeeID;;
    
private String last = null;
    
private String first = null;
    
private BigDecimal total = null;
    
private List sales = null;
    
private LatestSale latestSale = null;
    
    
public static List createBeanCollection () {
        List list 
= new ArrayList ();
        
        MonthlySalesBean msb 
= new MonthlySalesBean ();
        msb.setEmployeeID(
1);
        msb.setFirst(
"John");
        msb.setLast(
"Doe");
        msb.setTotal(
new BigDecimal ("1600.50"));
        
        LatestSale ls 
= new LatestSale ();
        ls.setAmount(
new BigDecimal ("32.21"));
        msb.setLatestSale(ls);
        
        list.add(msb);
        
        
return list;
    }

    
public int getEmployeeID() {
        
return employeeID;
    }

    
public void setEmployeeID(int employeeID) {
        
this.employeeID = employeeID;
    }

    
public String getFirst() {
        
return first;
    }

    
public void setFirst(String first) {
        
this.first = first;
    }

    
public String getLast() {
        
return last;
    }

    
public void setLast(String last) {
        
this.last = last;
    }

    
public BigDecimal getTotal() {
        
return total;
    }

    
public void setTotal(BigDecimal total) {
        
this.total = total;
    }

    
public List getSales() {
        
return sales;
    }

    
public void setSales(List sales) {
        
this.sales = sales;
    }

    
public LatestSale getLatestSale() {
        
return latestSale;
    }

    
public void setLatestSale(LatestSale latestSale) {
        
this.latestSale = latestSale;
    }
    
}


 

将上面的类打成一个jar包,并置于classpath目录下,则iReport可以进行访问。使用JavaBean做为数据源,为了在设计报表时能够看到数据,在程序中要为iReport提供一个静态方法,该方法返回上面定义JavaBean的一个结果集,这个静态方法可能在程序运行中并不是必须的,但是在iReport中它确实必须的,换句话说,这个静态方法是专门为iReport量身定做的,为了iReport在设计报表时能够调用这个静态方法返回相应的JavaBean结果集,以便设计的报表放在Java项目中之前就能像使用SQL数据库数据源一样可以浏览。在iReport中先进行数据源的连接配置,此处采用是JavaBeans set data source连接方式:

2.iReport进行数据源的连接

 

三、处理iBati返回数据

 

如果iBATIS没有采用JavaBean作为返回对象,则可以采用java.util.map作为数据的返回对象。采用java.util.Map对象,需要额外的一些步骤。下面的代码则说明了iBATISselect语句返回的java.util.Map对象。Src/ iBATIS.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 1.0//EN"
    "http://iBATIS.apache.org/dtd/sql-map-2.dtd"
>
<sqlMap>

    
<select id="salesByListOfMapsSQL" resultClass="java.util.HashMap">
        SELECT
            E.EMPLOYEE_ID "ID",
            E.FIRST_NAME "FIRST",
            E.LAST_NAME "LAST",
            MS.TOTAL_SALES "TOTAL",
            MS.LATEST_SALE
        FROM
            EMPLOYEE E,
            MONTHLY_SALES MS
        WHERE
            E.EMPLOYEE_ID = MS.EMPLOYEE_ID
            AND MS.MONTH = #value#
    
</select>
    
    
<resultMap id="searchResultList" class="MonthlySalesBean">
            
<result property="employeeID" column="ID"/>
            
<result property="first" column="FIRST"/>
            
<result property="last" column="LAST"/>
            
<result property="total" column="TOTAL"/>
            
<result property="latestSale.amount" column="LATEST_SALE"/>
    
</resultMap>    
    
    
<select id="salesByJavaBeansSQL" resultMap="searchResultList">
        SELECT
            E.EMPLOYEE_ID "ID",
            E.FIRST_NAME "FIRST",
            E.LAST_NAME "LAST",
            MS.TOTAL_SALES "TOTAL",
            MS.LATEST_SALE
        FROM
            EMPLOYEE E,
            MONTHLY_SALES MS
        WHERE
            E.EMPLOYEE_ID = MS.EMPLOYEE_ID
            AND MS.MONTH = #value#
    
</select>    
</sqlMap>

 

上面的代码返回的对象即为map对象。请注意,map对象中的Key值直接来自于select语句,因此,像TO_CHARMS.TOTAL_SALES)这样的表达式在报表中不提倡使用。因此,比较人性化的为字段命名,是一件很值得的事情。因为mapkey值是作为java.lang.Object类型来进行存储的,因此有必要对字段返回类型进行一下整理。

真正的数据填充类应该是ServiceLocatorBean.java类,其代码如下所示:

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

import javax.servlet.ServletContext;

import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

public class ServiceLocatorBean implements ServiceLocatorIF {
    
    
private static final long serialVersionUID = -7166271873610635886L;

    
//the Spring application context
    private ApplicationContext appContext;
        
    DAO dao 
= null;
    
    
public ServiceLocatorBean() {
        
        
try {
        
            
// get the spring context
            ServletContext context = FacesUtils.getServletContext();
            
this.appContext = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
            
            
// create instance of the business object
            this.dao = (DAO) this.lookupService("dao");
            
            Connection conn 
= this.dao.getSqlMapClient().getDataSource().getConnection();
            
            conn.setAutoCommit(
false);
    
            
/*
               Creating a statement lets us issue commands against
               the connection.
             
*/

            Statement s 
= conn.createStatement();
            
            
// just in case old tables from prior run (after first run which
            
// will create the USER1 schema)
            try {
                s.execute(
"drop table employee");
                s.execute(
"drop table monthly_sales");
            }
 catch (Exception ex) {
                
// not to be concerned (at least in this example
            }

    
            
/*
               We create a table, add a few rows, and update one.
             
*/

            s.execute(
"create table employee (employee_id int, first_name varchar(40), last_name varchar(40))");
            
            s.execute(
"insert into employee values (1,'sterning', 'chen')");
            s.execute(
"insert into employee values (2,'yuxuan', 'Wand')");
            s.execute(
"insert into employee values (3,'Mickey', 'Li')"); 
            
            s.execute(
"create table monthly_sales (employee_id int, total_sales numeric(16, 2), latest_sale numeric(8, 2), month int)");
            
            s.execute(
"insert into monthly_sales values (1, 1600.50, 32.50, 1)");
            s.execute(
"insert into monthly_sales values (2, 1544.20, 12.50, 1)");
            s.execute(
"insert into monthly_sales values (3, 18814.80, 78.65, 1)");
            
            s.execute(
"insert into monthly_sales values (1, 1450.50, 10.65, 2)");
            s.execute(
"insert into monthly_sales values (2, 2004.25, 52.10, 2)");
            s.execute(
"insert into monthly_sales values (3, 9819.00, 40.65, 2)"); 
            
            s.close();
            conn.commit();            
        
        }
 catch (SQLException sqle) {
            
// just means the tables already exist
            sqle.printStackTrace();
        }
 catch (Exception ex) {
            ex.printStackTrace();
        }

        
    }
    
    
    
public DAO getDao() {
        
return this.dao;
    }

    
    
public Object lookupService(String serviceBeanName) {
        
return appContext.getBean(serviceBeanName);
    }


}


 

四、将iBATIS数据填入JasperReport

就通常而言,采用Java Bean作为iBATIS的返回对象,相比起java.util.Map对象来说,更加的方便与可行。很多的开发人员采用iBATIS的这种方式来进行数据的映射,同时,此方法还可以无缝的将iBATISJapserReport集成起来。

JasperReport中,提供了一个JRDataSource的实现,从而开发人员可以通过此类来传递iBATISlist对象给JasperReport模板。而JRBeanCollectionDataSource类使用JavaBean来构造,从而可以通过循环查找collection并获得相应的bean属性。如下的代码示例说明了如何在调用JasperReport引擎时实例化JRBeanCollectionDataSource对象。

import java.io.File;
import java.util.HashMap;
import java.util.List;

import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.util.JRLoader;


public class SearchBean {
    
    
private final static String JAVA_BEAN_REPORT = "monthly_sales_java_beans.jasper";
    
private final static String LIST_OF_MAP_REPORT = "monthly_sales_list_of_maps.jasper"

    
public String generateFromJavaBeans () {
        
        
try {
            ServiceLocatorIF sl 
= (ServiceLocatorIF) FacesUtils
                .getManagedBean(
"serviceLocatorBean");
            
            List list 
= sl.getDao().getSqlMapClient().queryForList("salesByJavaBeansSQL", month);
            
            FacesUtils.setSessionAttribute(
"JASPER_PRINT", generateReport (list, JAVA_BEAN_REPORT));
            
            viewReport 
= "true";
        }
 catch (Exception ex) {
            ex.printStackTrace();
        }

        
        
return null;
    }

    
    
public String generateFromListOfMaps () {
        
        
try {
            ServiceLocatorIF sl 
= (ServiceLocatorIF) FacesUtils
                .getManagedBean(
"serviceLocatorBean");    
            
            List list 
= sl.getDao().getSqlMapClient().queryForList("salesByListOfMapsSQL", month);
            
            FacesUtils.setSessionAttribute(
"JASPER_PRINT", generateReport (list, LIST_OF_MAP_REPORT));
            
            viewReport 
= "true";
        }
 catch (Exception ex) {
            ex.printStackTrace();
        }

        
        
return null;
    }


    
private JasperPrint generateReport (List dataList, String reportName) {
        JasperPrint jasperPrint 
= null;
        
        
try {
            
            String localPath 
= FacesUtils.getServletContext().getRealPath("/");
            
            File reportFile 
= new File(localPath + "WEB-INF" + File.separator + reportName);
            
            
if (!reportFile.exists())
                
throw new JRRuntimeException(".jasper file not found. The report design must be compiled first.");
                        
            JasperReport jasperReport 
= (JasperReport)JRLoader.loadObject(reportFile.getPath());
            
            
if (reportName.equals(JAVA_BEAN_REPORT)) {
                
                jasperPrint 
= JasperFillManager.fillReport(
                        jasperReport,
                        
new HashMap(), 
                        
new JRBeanCollectionDataSource (dataList));        
                
            }
 else {
                
                jasperPrint 
= JasperFillManager.fillReport(
                        jasperReport,
                        
new HashMap(), 
                        
new CustomJRDS (dataList));
                
            }

        
        }
 catch (Exception ex) {
            ex.printStackTrace();
        }

        
        
return jasperPrint;
    }
    

    
public String getMonth() {
        
return month;
    }


    
public void setMonth(String month) {
        
this.month = month;
    }


    
public String getViewReport() {
        
return viewReport;
    }


    
public void setViewReport(String viewReport) {
        
this.viewReport = viewReport;
    }

    
    
private String month = null;
    
private String viewReport = null;    
}


 

在上面的代码中,定义的参数map,是在运行时传递相关的参数值给JasperReport。例如,可以在报表模板中定义一个名为REPORT_TITLE的参数,然后在运行时传递这一参数的值给它,传递的方式一般是健/值对的形式。例如Key=REPORT_TITLEValue=Sale Report。当然,参数是传递给fillReport方法。然后,JasperReport会加载已经编译好的Jasper模板文件(.jasper)。最后调用静态的fillReport方法。

JasperPrint对象是在数据展示或显示时需要用到的。而在本例中,采用了JRPdfExporter来作为输出的格式,即输出为PDF格式文件,请参考PdfServlet.java文件,代码如下所示:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.export.JRPdfExporter;


public class PdfServlet extends HttpServlet {

    
public void service(HttpServletRequest request, HttpServletResponse response)
            
throws IOException, ServletException {

        JasperPrint jasperPrint 
= (JasperPrint) request.getSession()
                .getAttribute(
"JASPER_PRINT");

        List jasperPrintList 
= new ArrayList();

        jasperPrintList.add(jasperPrint);

        JRPdfExporter exporter 
= new JRPdfExporter();
        exporter.setParameter(JRExporterParameter.JASPER_PRINT_LIST,
                jasperPrintList);

        ByteArrayOutputStream baos 
= new ByteArrayOutputStream();
        exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);

        
try {
            exporter.exportReport();
        }
 catch (JRException e) {
            
throw new ServletException(e);
        }


        
byte[] bytes = baos.toByteArray();

        
if (bytes != null && bytes.length > 0{
            response.setContentType(
"application/pdf");
            response.setContentLength(bytes.length);
            ServletOutputStream ouputStream 
= response.getOutputStream();

            
try {
                ouputStream.write(bytes, 
0, bytes.length);
                ouputStream.flush();
            }
 finally {
                
if (ouputStream != null{
                    
try {
                        ouputStream.close();
                    }
 catch (IOException ex) {
                    }

                }

            }

        }

    }

}


 

尽管上面的JasperReport机制可以将iBATIS连接起来,但应该根据项目报表的需要对JavaBean进行修改与调整。而JasperReport字段对象可以很好的与普通的JDBC字段进行匹配。例如,JasperReportOraclenumeric字段类型对应的转成java.math.BigDecimal对象类型。而在iBATISBean属性应该与JasperReport中定义的字段类型进行很好的匹配。需要对字段的类型进行认真仔细的选择,因为不同类型或是不同表达式对数据的展示有不同的效果。例如,BigDecimal类型比String类型更加适合货币格式。

五、代码运行效果

1.系统主界面

 


3.报表运行主界面

2.采用JavaBean生成报表


4.采用JavaBean生成报表

六、小结

在本文中,笔者展示了如何使用比较成熟的iBATIS数据框架来对JasperReport进行数据填充。iBATIS最大的特点是简单,而iBATIS所拥有的易维护及易配置特性,在JasperReport中充分的体现出来了。这种简单与灵活性,正好弥补了JasperReport在这方面的不足,从而达到灵活开发Web报表的目的。



评论

# re: 使用JasperReport与iBATIS开发Web报表  回复  更多评论   

2008-01-25 11:26 by ci
不错....

# re: 使用JasperReport与iBATIS开发Web报表  回复  更多评论   

2008-01-25 14:55 by cnfox
rar文件被破坏?

# re: 使用JasperReport与iBATIS开发Web报表  回复  更多评论   

2008-01-26 19:48 by julycoolboy
大概看了一下,博主做的不错,我来谈谈我使用JR的心得,JR本身分为推和拉的方式来填充模板,使用BEAN做为数据源,就是先取出数据后推到JR中去,我这里返回的是一个自定义Iterator,里面使用的ResultSet,这个好处就是不会一次性把数据取出来,生成报表的时候会一条条取,这样就能突破推方式的数据量极限了(正常方式下,一个万条数据的LIST,我们的开发PC都容易内存溢出)

# re: 使用JasperReport与iBATIS开发Web报表  回复  更多评论   

2008-04-30 16:38 by regale
不错....

# re: 使用JasperReport与iBATIS开发Web报表  回复  更多评论   

2008-07-02 17:14 by wanxinge
发一份给我可以吗?万分感谢啊,下载rar被破坏。我的邮箱:wangxinge_5689@163.com

# re: 使用JasperReport与iBATIS开发Web报表  回复  更多评论   

2008-12-05 11:52 by ireport
发份我,不胜感激!
lujuju@qq.com

# re: 使用JasperReport与iBATIS开发Web报表  回复  更多评论   

2008-12-26 04:09 by renbao
我也需要,谢谢22624223@qq.com

# re: 使用JasperReport与iBATIS开发Web报表  回复  更多评论   

2009-10-07 03:00 by Wonner
千万不要用iBATIS.

# re: 使用JasperReport与iBATIS开发Web报表  回复  更多评论   

2014-11-26 16:10 by 阿浪
正在学习这个,想参考一下,能发一份给我吗?
1572669095@qq.com

# re: 使用JasperReport与iBATIS开发Web报表  回复  更多评论   

2015-01-16 16:42 by hanck
学习中,望前辈们多多指导啊,可以发一份给我吗?不胜感激!
389610110@qq.com

只有注册用户登录后才能发表评论。


网站导航: