使用JasperReport與iBATIS開發Web報表

   JasperReport是一種採用純Java實現的快速且非常流行的生成報表的類庫。而對於任何的報表方案,取得數據並傳遞給報表引擎是其中最重要且最值得關心的方面。但遺憾的是,在這方面JasperReport本身似乎有一定的不足。而如今的很多Java應用程序,採用數據獲取框架來進行數據的匹配與動態生成SQL。例如iBATIS數據映射框架。當然,如果只是使用JasperReport獲取數據及管理數據的默認機制的話,不足以與現成的數據框架進行很好的平衡。但可喜的是,可以通過使用傳遞給JasperReport一個數據庫的連接進行代替,當然這種連接可以通過使用XML進行非常方便的管理與配置。

     一、        準備工作

    與Hibernate類似,iBATIS也是一個ORM解決方案,不同的是兩者各有側重。Hibernate提供了Java對象到數據庫表之間的直接映射,開發者無需直接涉及數據庫操作的實現細節,實現了一站式的ORM解決方案。而iBATIS則採取了另一種方式,即提供Java對象到SQL(面向參數和結果集)的映射實現,實際的數據庫操作需要通過手動編寫SQL實現。

    iBATIS是又一個O/R Mapping解決方案,j2ee的O/R方案真是多,和Hibernate相比,iBATIS最大的特點就是小巧,上手很快。如果你不需要太多複雜的功能,iBATIS是能滿足你的要求又足夠靈活的最簡單的解決方案。在本文的示例中,採用Spring+JSF+iBATIS的模式進行示例的開發。所使用的lib如下圖所示:

   

  

 

二、        在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對象,需要額外的一些步驟。下面的代碼則說明了iBATIS的select語句返回的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_CHAR(MS.TOTAL_SALES)這樣的表達式在報表中不提倡使用。因此,比較人性化的爲字段命名,是一件很值得的事情。因爲map的key值是作爲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的這種方式來進行數據的映射,同時,此方法還可以無縫的將iBATIS與JapserReport集成起來。
  
  在JasperReport中,提供了一個JRDataSource的實現,從而開發人員可以通過此類來傳遞iBATIS的list對象給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_TITLE,Value=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字段進行匹配。例如,JasperReport將Oracle的numeric字段類型對應的轉成java.math.BigDecimal對象類型。而在iBATIS的Bean屬性應該與JasperReport中定義的字段類型進行很好的匹配。需要對字段的類型進行認真仔細的選擇,因爲不同類型或是不同表達式對數據的展示有不同的效果。例如,BigDecimal類型比String類型更加適合貨幣格式

 

 

五、        代碼運行效果

    1.系統主界面

 

   

圖3.報表運行主界面
 

   
    2.採用JavaBean生成報表


圖4.採用JavaBean生成報表

    六、        小結

    在本文中,筆者展示瞭如何使用比較成熟的iBATIS數據框架來對JasperReport進行數據填充。iBATIS最大的特點是簡單,而iBATIS所擁有的易維護及易配置特性,在JasperReport中充分的體現出來了。這種簡單與靈活性,正好彌補了JasperReport在這方面的不足,從而達到靈活開發Web報表的目的。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章