使用Javabean作为数据源的JasperReport报表(通过WebService/RMI调用数据)

    本文接 http://sarin.iteye.com/blog/658145继续研究。
    之前我们用JasperReport制作报表使用的是JDBC数据源。当使用JDBC作为数据源时,JasperReport在运行时会检测数据库是否可用,也就是说它会自己连接一下数据库。而我们要使用远程方法时,数据库不在本地,那么我们就不能使用JDBC数据源了,因为JasperReport在不可访问数据源时会抛异常。此时,我们就必须使用JavaBean作为数据源了。比如通过WebService/RMI方式获取数据的话,就要走JavaBean数据源的方式了。
    先定义一个功能需求,然后我们展开说明。一个车辆行驶数据分析系统中需要实时获取行驶数据,这需要车载设备来实现。数据上传存储在Web端的数据库中,我们需要从数据库中获取数据来显示内容。
    使用JavaBean作为数据源,首先我们要扩展JasperReport的数据源。实现方法很简单,我们定义一个类去实现net.sf.jasperreports.engine.JRDataSource接口即可,覆盖其中的方法。下面给出一个示例。
Java代码 复制代码  收藏代码
  1. package xxx.xxx.web.bean;   
  2. import java.util.List;   
  3. import net.sf.jasperreports.engine.JRDataSource;   
  4. import net.sf.jasperreports.engine.JRException;   
  5. import net.sf.jasperreports.engine.JRField;   
  6. public class DrivingHistoryDataSource implements JRDataSource {   
  7.     //数据集合,使用到了一个VO值对象   
  8. private List<DrivingHistoryDataVO> list;   
  9.     private int i = -1;   
  10.     //构造方法,初始化list   
  11.     public DrivingHistoryDataSource(List<DrivingHistoryDataVO> list) {   
  12.         super();   
  13.         this.list = list;   
  14.     }   
  15.     //覆盖接口中的方法,给List添加参数   
  16.     public Object getFieldValue(JRField jrField) throws JRException {   
  17.         Object value = null;   
  18.         String fieldName = jrField.getName();   
  19.         if ("id".equals(fieldName)) {   
  20.             value = list.get(i).getId();   
  21.         } else if (){   
  22.             //剩余参数的填充这里就省略了,Java 7中的Switch可以使用字符串了就不用这样else if重复写了。   
  23.         }   
  24.         return value;   
  25.     }   
  26.     //覆盖接口中的next()方法   
  27.     public boolean next() throws JRException {   
  28.         i++;   
  29.         if (list == null) {   
  30.             return false;   
  31.         }   
  32.         return (i < list.size());   
  33.     }   
  34. }  
package xxx.xxx.web.bean;
import java.util.List;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRField;
public class DrivingHistoryDataSource implements JRDataSource {
	//数据集合,使用到了一个VO值对象
private List<DrivingHistoryDataVO> list;
	private int i = -1;
	//构造方法,初始化list
	public DrivingHistoryDataSource(List<DrivingHistoryDataVO> list) {
		super();
		this.list = list;
	}
	//覆盖接口中的方法,给List添加参数
	public Object getFieldValue(JRField jrField) throws JRException {
		Object value = null;
		String fieldName = jrField.getName();
		if ("id".equals(fieldName)) {
			value = list.get(i).getId();
		} else if (){
			//剩余参数的填充这里就省略了,Java 7中的Switch可以使用字符串了就不用这样else if重复写了。
		}
		return value;
	}
	//覆盖接口中的next()方法
	public boolean next() throws JRException {
		i++;
		if (list == null) {
			return false;
		}
		return (i < list.size());
	}
}

    因为扩展的数据源中使用了VO值对象,这里给出这个值对象。使用VO的好处就是从数据库读取的结果集直接封装成自定义的VO类型,放在List中也是泛型的一种实现,非常方便使用。
Java代码 复制代码  收藏代码
  1. package xxx.xxx.web.bean;   
  2. import java.io.Serializable;   
  3. public class DrivingHistoryDataVO implements Serializable{   
  4.     private int id;//主键   
  5. //其余的属性省略了   
  6. //默认的构造方法   
  7.     public DrivingHistoryDataVO() {   
  8.         super();   
  9.     }   
  10. //带参数的构造方法,省略其余参数   
  11.     public DrivingHistoryDataVO(int id) {   
  12.         super();   
  13.         this.id = id;   
  14.     }   
  15.     public int getId() {   
  16.         return id;   
  17.     }   
  18.     public void setId(int id) {   
  19.         this.id = id;   
  20.     }   
  21.     //其余的getter和setter方法省略了   
  22. }  
package xxx.xxx.web.bean;
import java.io.Serializable;
public class DrivingHistoryDataVO implements Serializable{
	private int id;//主键
//其余的属性省略了
//默认的构造方法
	public DrivingHistoryDataVO() {
		super();
	}
//带参数的构造方法,省略其余参数
	public DrivingHistoryDataVO(int id) {
		super();
		this.id = id;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	//其余的getter和setter方法省略了
}

    数据源我们都配置好了,剩下的就是往JasperReport模板文件中填充了,而模板文件的设计是和普通JDBC数据源不同的,这里我们来详细说明一下。iReport工具我们使用3.7.2版本的,同时项目类库中JasperReport的版本也要和iReport工具一致,否则是不能编译的。
    打开iReport,点击工具中的选项,找到classpath选项卡,增加我们的项目的类路径,这样我们就能在iReport中使用我们设定好的VO对象了。如下图所示:

    设置好classpath后,我们开始设计报表模板,首先要设置数据源,我们选择JavaBean数据源,这里的Class Name要输入类的全名(含包名),然后点击Read attributes就可以读取到类中的属性了,我们选择全部添加进来,如下图所示:

    我们得到如下的设计模板。

    这里我们说几个注意的地方。日期字段可能是多条相同的记录,那么相同的日期只显示一次,在模板中我们需要多设置一下,就是把属性中的Print Report Value选项去掉,那么为了显示的好看,我们还需要特殊设置一下Padding and Borders,下边界不能显示。如下图所示:

    而问题还没有全部解决,因为重复不显示的那部分边界就也不显示了,所以还需要特殊处理重复不显示那部分格子的边界。我们添加一个静态文本块static field,设置和日期字段完全重合。如下图所示:

    还要说明的是数据超长问题的处理,正如这个模板中的起始地点和结束地点,因为显示的数据过多而留给他们的空间有限,如果这两个字段的数据过长,那么显示就会出现问题,这是必须要处理的问题。这个处理也很简单,我们选中需要超长处理的字段,然后在属性面板中勾选Stretch With Overflow选项,这时如果数据超长就会自动换行了。
    而问题又出现了,如果这两个字段自动换行了,那么必然把一行上的其他元素都给撑大了,那么还需要对其他元素进行设置。选中其他的所有元素,在属性面板的Stretch Type中选择Relative to Tallest Object就可以自适应到最高换行的元素了,显示效果就正常了。
    若报表在页面显示时,也要考虑日期合并的问题,那么就需要在流程中进行处理了。而页面中的合并涉及到rowspan的数值和td中的参数,这是需要处理的地方。这里很自然想到设计一个VO来匹配页面。那么我们设计ColsVO来实现,代码如下:
Java代码 复制代码  收藏代码
  1. package xxx.xxx.web.bean;   
  2. /**  
  3.  * 统计报表用VO,代表一个单元格  
  4.  */  
  5. public class ColsVO {   
  6.     private String value;//单元格显示的值   
  7.     private int rowspan;//单元格占几行   
  8.     public ColsVO() {   
  9.         super();   
  10.     }   
  11.     public ColsVO(String value, int rowspan) {   
  12.         super();   
  13.         this.value = value;   
  14.         this.rowspan = rowspan;   
  15.     }   
  16.     public String getValue() {   
  17.         return value;   
  18.     }   
  19.     public void setValue(String value) {   
  20.         this.value = value;   
  21.     }   
  22.     public int getRowspan() {   
  23.         return rowspan;   
  24.     }   
  25.     public void setRowspan(int rowspan) {   
  26.         this.rowspan = rowspan;   
  27.     }   
  28. }  
package xxx.xxx.web.bean;
/**
 * 统计报表用VO,代表一个单元格
 */
public class ColsVO {
	private String value;//单元格显示的值
	private int rowspan;//单元格占几行
	public ColsVO() {
		super();
	}
	public ColsVO(String value, int rowspan) {
		super();
		this.value = value;
		this.rowspan = rowspan;
	}
	public String getValue() {
		return value;
	}
	public void setValue(String value) {
		this.value = value;
	}
	public int getRowspan() {
		return rowspan;
	}
	public void setRowspan(int rowspan) {
		this.rowspan = rowspan;
	}
}

    下面就是对取出的数据集合进行处理了,我们先来说明页面显示的代码的处理算法。
Java代码 复制代码  收藏代码
  1. public List<Map> getResultList(List<DrivingHistoryDataVO> dataList) {   
  2.     List<Map> returnList = new ArrayList<Map>();   
  3.     // 保证记录顺序不变,使用TreeMap   
  4.     Map<String, Object> tempMap = null;   
  5.     // 日期合并记录数   
  6.     int dateRowspan = 0;   
  7.     for (int i = 0; i < dataList.size(); i++) {   
  8.         tempMap = new HashMap<String, Object>();   
  9.         DrivingHistoryDataVO rawVO = dataList.get(i);   
  10.         // 先填充不需要合并的字段   
  11.         returnList.add(tempMap);   
  12.         // 需要合并日期,单独处理   
  13.         // 是否是最后一条记录的开关   
  14. boolean last = (i == dataList.size() - 1);    
  15.         // 取出两条记录进行比较   
  16.         DrivingHistoryDataVO hdVo1 = null;   
  17.         DrivingHistoryDataVO hdVo2 = null;   
  18.         if (!last) {   
  19.             hdVo1 = dataList.get(i);   
  20.             hdVo2 = dataList.get(i + 1);   
  21.         } else {   
  22.             // 防止最后一条记录无法加入集合   
  23.             hdVo1 = dataList.get(i);   
  24.             if (dataList.size() != 1)   
  25.                 hdVo2 = dataList.get(i - 1);   
  26.             else  
  27.                 hdVo2 = dataList.get(i);   
  28.         }   
  29.         String date1 = hdVo1.getDate();   
  30.         String date2 = hdVo2.getDate();   
  31.         // 比较date1和date2,如果相同,计数器加1;不同,更新统一日期的第一条记录,计数器清零   
  32.         if (date1.equals(date2)) {   
  33.             if (dataList.size() == 1) {   
  34.                 ColsVO colsVO = new ColsVO(hdVo1.getDate(), 1);   
  35.                 returnList.get(0).put("colsVO", colsVO);   
  36.             } else if (last) {   
  37.                 ColsVO colsVO = new ColsVO(hdVo1.getDate(), dateRowspan + 1);   
  38.                 returnList.get(i - dateRowspan).put("colsVO", colsVO);   
  39.             } else {   
  40.                 dateRowspan++;   
  41.             }   
  42.         } else {   
  43.             ColsVO colsVO = new ColsVO(hdVo1.getDate(), dateRowspan + 1);   
  44.             returnList.get(i - dateRowspan).put("colsVO", colsVO);   
  45.             dateRowspan = 0;   
  46.         }   
  47.     }   
  48.     return returnList;   
  49. }  
public List<Map> getResultList(List<DrivingHistoryDataVO> dataList) {
	List<Map> returnList = new ArrayList<Map>();
	// 保证记录顺序不变,使用TreeMap
	Map<String, Object> tempMap = null;
	// 日期合并记录数
	int dateRowspan = 0;
	for (int i = 0; i < dataList.size(); i++) {
		tempMap = new HashMap<String, Object>();
		DrivingHistoryDataVO rawVO = dataList.get(i);
		// 先填充不需要合并的字段
		returnList.add(tempMap);
		// 需要合并日期,单独处理
		// 是否是最后一条记录的开关
boolean last = (i == dataList.size() - 1); 
		// 取出两条记录进行比较
		DrivingHistoryDataVO hdVo1 = null;
		DrivingHistoryDataVO hdVo2 = null;
		if (!last) {
			hdVo1 = dataList.get(i);
			hdVo2 = dataList.get(i + 1);
		} else {
			// 防止最后一条记录无法加入集合
			hdVo1 = dataList.get(i);
			if (dataList.size() != 1)
				hdVo2 = dataList.get(i - 1);
			else
				hdVo2 = dataList.get(i);
		}
		String date1 = hdVo1.getDate();
		String date2 = hdVo2.getDate();
		// 比较date1和date2,如果相同,计数器加1;不同,更新统一日期的第一条记录,计数器清零
		if (date1.equals(date2)) {
			if (dataList.size() == 1) {
				ColsVO colsVO = new ColsVO(hdVo1.getDate(), 1);
				returnList.get(0).put("colsVO", colsVO);
			} else if (last) {
				ColsVO colsVO = new ColsVO(hdVo1.getDate(), dateRowspan + 1);
				returnList.get(i - dateRowspan).put("colsVO", colsVO);
			} else {
				dateRowspan++;
			}
		} else {
			ColsVO colsVO = new ColsVO(hdVo1.getDate(), dateRowspan + 1);
			returnList.get(i - dateRowspan).put("colsVO", colsVO);
			dateRowspan = 0;
		}
	}
	return returnList;
}

    这样我们对原来的数据集合dataList进行遍历处理,就得到了处理后的集合returnList,在页面用FreeMarker进行遍历显示,代码如下:
Html代码 复制代码  收藏代码
  1. <#list returnList as info>  
  2. <tr>  
  3. <#if info.colsVO?has_content>  
  4.     <td rowspan="${ info.colsVO.rowspan}">${ info.colsVO.value}</td>  
  5. <#else>  
  6. </#if>  
  7.     <td>${info.XXX}</td>  
  8. <td>${info.XXX}</td>  
  9. </tr>  
  10. </#list>  
<#list returnList as info>
<tr>
<#if info.colsVO?has_content>
	<td rowspan="${ info.colsVO.rowspan}">${ info.colsVO.value}</td>
<#else>
</#if>
	<td>${info.XXX}</td>
<td>${info.XXX}</td>
</tr>
</#list>

    这样就可以显示合并的效果了,我们看一下效果图片

    日期字段就已经合并显示了,而直接遍历List是不能做到的,而合并算法可能存在问题,希望和大家交流。
    剩下就是报表的打印了。看一下报表打印的核心代码。
Java代码 复制代码  收藏代码
  1. String jrxmlPath = this.getClass().getClassLoader().getResource("/xxx.xxx.template").getPath()+"/report.jrxml";   
  2. JasperReport report = JasperCompileManager.compileReport(jrxmlPath);   
  3. JasperPrint jasperPrint = JasperFillManager.fillReport(report,   
  4.         nullnew DrivingHistoryDataSource(dataList));  
		String jrxmlPath = this.getClass().getClassLoader().getResource("/xxx.xxx.template").getPath()+"/report.jrxml";
		JasperReport report = JasperCompileManager.compileReport(jrxmlPath);
		JasperPrint jasperPrint = JasperFillManager.fillReport(report,
				null, new DrivingHistoryDataSource(dataList));

    其余部分就和正常是一样的了,只是数据源的设置上使用我们扩展的JRDataSource就行了。我们看看打印出来的PDF效果。

    字段已经合并了,但是合并字段的居中还是个问题,需要继续研究。
前面说过使用JavaBean作为数据源是因为Web应用中数据是远程方法调用过来的,而不是在本地数据库生成的。下面用RMI方法来说明本例中的数据获取设置。当然WebService的实现还有很多。
Java代码 复制代码  收藏代码
  1. package xxx.xxx.service;   
  2. import org.springframework.remoting.rmi.RmiProxyFactoryBean;   
  3. /**  
  4.  * 获取RMI远程访问接口  
  5. */  
  6. public class WebServiceClientService{   
  7.     private String serviceName;//WebService服务名   
  8.     private String registryPort;//WebService注册端口号   
  9.     public void setServiceName(String serviceName) {   
  10.         this.serviceName = serviceName;   
  11.     }   
  12.     public void setRegistryPort(String registryPort) {   
  13.         this.registryPort = registryPort;   
  14.     }   
  15.     /**  
  16.      * 获取RMI接口  
  17.      * @param ip  
  18.      * @return  
  19.      */  
  20.     private RmiProxyFactoryBean getRmiProxyFactoryBean(String ip){   
  21.         RmiProxyFactoryBean rpf=new RmiProxyFactoryBean();   
  22.         StringBuffer url=new StringBuffer();   
  23.         url.append("rmi://");   
  24.         url.append(ip);   
  25.         url.append(":");   
  26.         url.append(registryPort);   
  27.         url.append("/");   
  28.         url.append(serviceName);   
  29.         rpf.setServiceUrl(url.toString());     
  30.         return rpf;   
  31.     }   
  32.     /**  
  33.      * 设置RMI远程访问接口生效  
  34.      */  
  35.     private void setRmiProxyFactoryBean(RmiProxyFactoryBean rpf){   
  36.         rpf.afterPropertiesSet();   
  37.     }   
  38.     /**  
  39.      * 获取RMI车辆数据接口  
  40.      */  
  41.     public DrivingHistoryDataWebService getDrivingHistoryDataWebService(String ip){   
  42.         RmiProxyFactoryBean rpf=this.getRmiProxyFactoryBean(ip);   
  43.         rpf.setServiceInterface(DrivingHistoryDataWebServiceIF.class);   
  44.         this.setRmiProxyFactoryBean(rpf);   
  45.         return (DrivingHistoryDataWebService)rpf.getObject();   
  46.     }   
  47. }  
package xxx.xxx.service;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
/**
 * 获取RMI远程访问接口
*/
public class WebServiceClientService{
	private String serviceName;//WebService服务名
	private String registryPort;//WebService注册端口号
	public void setServiceName(String serviceName) {
		this.serviceName = serviceName;
	}
	public void setRegistryPort(String registryPort) {
		this.registryPort = registryPort;
	}
	/**
	 * 获取RMI接口
	 * @param ip
	 * @return
	 */
	private RmiProxyFactoryBean getRmiProxyFactoryBean(String ip){
		RmiProxyFactoryBean rpf=new RmiProxyFactoryBean();
		StringBuffer url=new StringBuffer();
		url.append("rmi://");
		url.append(ip);
		url.append(":");
		url.append(registryPort);
		url.append("/");
		url.append(serviceName);
		rpf.setServiceUrl(url.toString());	
		return rpf;
	}
	/**
	 * 设置RMI远程访问接口生效
	 */
	private void setRmiProxyFactoryBean(RmiProxyFactoryBean rpf){
		rpf.afterPropertiesSet();
	}
	/**
	 * 获取RMI车辆数据接口
	 */
	public DrivingHistoryDataWebService getDrivingHistoryDataWebService(String ip){
		RmiProxyFactoryBean rpf=this.getRmiProxyFactoryBean(ip);
		rpf.setServiceInterface(DrivingHistoryDataWebServiceIF.class);
		this.setRmiProxyFactoryBean(rpf);
		return (DrivingHistoryDataWebService)rpf.getObject();
	}
}

    在Spring的配置文件中,要注入属性,代码如下:
Xml代码 复制代码  收藏代码
  1. <bean id="drivingHistoryDataClientService" class="xxx.xxx.service.WebServiceClientService">  
  2.     <property name="serviceName" value="DrivingHistoryDataWebService"/>  
  3.     <property name="registryPort" value="1234"/>  
  4. </bean>  

    我们在Action代码中就可以通过RMI的接口获取远端的数据了。代码如下:
Java代码 复制代码  收藏代码
  1. DrivingHistoryDataWebService hdws = getWebServiceClientService().getDrivingHistoryDataWebService(ip);   
  2. List<DrivingHistoryDataVO> dataList = hdws.getDrivingHistoryDataList(new Object[]{xxx,xxx,xxx});  
DrivingHistoryDataWebService hdws = getWebServiceClientService().getDrivingHistoryDataWebService(ip);
List<DrivingHistoryDataVO> dataList = hdws.getDrivingHistoryDataList(new Object[]{xxx,xxx,xxx});

    这样就可以通过RMI获取到远端的实现数据了,远端的实现这里就不列出了。
    一家之言,仅供参考,希望对使用者有用,欢迎交流。
http://sarin.iteye.com/blog/693622
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以帮你回答这个问题。 首先,我们需要设计一个JavaBean来存储用户注册信息,例如: ```java public class User { private String username; private String password; // 其他用户信息 // getter 和 setter 方法 } ``` 接下来,我们需要实现注册功能,将用户填写的信息存储到数据库中。在这里,我使用JDBC连接MySQL数据库来实现。 ```java public class RegisterServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取用户填写的信息 String username = request.getParameter("username"); String password = request.getParameter("password"); // 其他用户信息 // 将用户信息封装到JavaBean中 User user = new User(); user.setUsername(username); user.setPassword(password); // 其他用户信息 // 存储用户信息到数据库中 Connection conn = null; PreparedStatement stmt = null; try { // 获取数据库连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password"); // 插入用户信息 String sql = "INSERT INTO user (username, password) VALUES (?, ?)"; stmt = conn.prepareStatement(sql); stmt.setString(1, user.getUsername()); stmt.setString(2, user.getPassword()); stmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { // 关闭数据库连接 if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } // 注册成功,跳转到登录页面 response.sendRedirect("login.jsp"); } } ``` 最后,我们需要实现登录功能,验证用户输入的用户名和密码是否正确。 ```java public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取用户填写的信息 String username = request.getParameter("username"); String password = request.getParameter("password"); // 查询数据库中是否存在该用户 Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try { // 获取数据库连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password"); // 查询用户信息 String sql = "SELECT * FROM user WHERE username=? AND password=?"; stmt = conn.prepareStatement(sql); stmt.setString(1, username); stmt.setString(2, password); rs = stmt.executeQuery(); if (rs.next()) { // 登录成功,跳转到首页 response.sendRedirect("index.jsp"); } else { // 登录失败,提示用户重新登录 response.sendRedirect("login.jsp?error=1"); } } catch (SQLException e) { e.printStackTrace(); } finally { // 关闭数据库连接 if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } } ``` 以上就是使用JavaBean实现注册数据使用数据登录的示例代码,希望可以帮到你。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值