大多数情况下,FineReport直接在设计器里使用“数据集查询”,直接写SQL就能满足报表要求,但对于一些复杂的报表,有时候SQL处理并不方便,这时可以把查询结果在应用层做一些预处理后,再传递给报表,即所谓的“程序数据集”,FineReport的帮助文档上给了一个示例:

 package com.fr.data;   

 import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.ArrayList;
import com.fr.base.FRContext;
import com.fr.data.AbstractTableData;
import com.fr.base.Parameter; public class ParamTableDataDemo extends AbstractTableData {
// 列名数组,保存程序数据集所有列名
private String[] columnNames = null;
// 定义程序数据集的列数量
private int columnNum = 10;
// 保存查询表的实际列数量
private int colNum = 0;
// 保存查询得到列值
private ArrayList valueList = null; // 构造函数,定义表结构,该表有10个数据列,列名为column#0,column#1,。。。。。。column#9
public ParamTableDataDemo() {
// 定义tableName参数
this.parameters = new Parameter[] { new Parameter("tableName") };
// 定义程序数据集列名
columnNames = new String[columnNum];
for (int i = 0; i < columnNum; i++) {
columnNames[i] = "column#" + String.valueOf(i);
}
} // 实现其他四个方法
public int getColumnCount() {
return columnNum;
} public String getColumnName(int columnIndex) {
return columnNames[columnIndex];
} public int getRowCount() {
init();
return valueList.size();
} public Object getValueAt(int rowIndex, int columnIndex) {
init();
if (columnIndex >= colNum) {
return null;
}
return ((Object[]) valueList.get(rowIndex))[columnIndex];
} // 准备数据
public void init() {
// 确保只被执行一次
if (valueList != null) {
return;
}
// 保存得到的数据库表名
String tableName = parameters[0].getValue().toString();
// 构造SQL语句,并打印出来
String sql = "select * from " + tableName + ";";
FRContext.getLogger().info("Query SQL of ParamTableDataDemo: \n" + sql);
// 保存得到的结果集
valueList = new ArrayList();
// 下面开始建立数据库连接,按照刚才的SQL语句进行查询
Connection conn = this.getConnection();
try {
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// 获得记录的详细信息,然后获得总列数
ResultSetMetaData rsmd = rs.getMetaData();
colNum = rsmd.getColumnCount();
// 用对象保存数据
Object[] objArray = null;
while (rs.next()) {
objArray = new Object[colNum];
for (int i = 0; i < colNum; i++) {
objArray[i] = rs.getObject(i + 1);
}
// 在valueList中加入这一行数据
valueList.add(objArray);
}
// 释放数据库资源
rs.close();
stmt.close();
conn.close();
// 打印一共取到的数据行数量
FRContext.getLogger().info(
"Query SQL of ParamTableDataDemo: \n" + valueList.size()
+ " rows selected");
} catch (Exception e) {
e.printStackTrace();
}
} // 获取数据库连接 driverName和 url 可以换成您需要的
public Connection getConnection() {
String driverName = "sun.jdbc.odbc.JdbcOdbcDriver";
String url = "jdbc:odbc:Driver={Microsoft Access Driver (*.mdb)};DBQ=D:\\FineReport_7.0\\WebReport\\FRDemo.mdb";
String username = "";
String password = "";
Connection con = null;
try {
Class.forName(driverName);
con = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return con;
} // 释放一些资源,因为可能会有重复调用,所以需释放valueList,将上次查询的结果释放掉
public void release() throws Exception {
super.release();
this.valueList = null;
}
}

这个示例我个人觉得有二个地方不太方便:
1、db连接串硬编码写死在代码里,维护起来不太方便,目前大多数b/s应用,对于数据库连接,通常是利用spring在xml里配置datasource bean,运行时动态注入

2、将查询出的结果,填充到数据集时,采用的是数字索引(见82行),代码虽然简洁,但是可读性比较差

折腾一番后,于是便有了下面的改进版本:

 package infosky.ckg.fr.data;

 import infosky.ckg.utils.AppContext;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import javax.sql.DataSource;
import com.fr.base.Parameter;
import com.fr.data.AbstractTableData;
import com.fr.general.data.TableDataException; public class ParameterLinkedHashSetDataDemo extends AbstractTableData { private static final long serialVersionUID = 8818000311745955539L; // 字段名枚举
enum FIELD_NAME {
EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY
} private String[] columNames; private LinkedHashSet<LinkedHashMap<String, Object>> rowData; public ParameterLinkedHashSetDataDemo() {
this.parameters = new Parameter[] { new Parameter("jobId"),
new Parameter("minSalary"), new Parameter("maxSalary") }; // 填充字段名
columNames = new String[FIELD_NAME.values().length];
int i = 0;
for (FIELD_NAME fieldName : FIELD_NAME.values()) {
columNames[i] = fieldName.toString();
i++;
} } @Override
public int getColumnCount() throws TableDataException {
return columNames.length;
} @Override
public String getColumnName(int columnIndex) throws TableDataException {
return columNames[columnIndex];
} @Override
public int getRowCount() throws TableDataException {
queryData();
return rowData.size();
} @Override
public Object getValueAt(int rowIndex, int columnIndex) {
queryData();
int tempRowIndex = 0;
for (LinkedHashMap<String, Object> row : rowData) {
if (tempRowIndex == rowIndex) {
return row.get(columNames[columnIndex]);
}
tempRowIndex += 1;
}
return null;
} // 查询数据
private void queryData() {
// 确保只被执行一次
if (rowData != null) {
return;
} // 传入的参数
String jobId = parameters[0].getValue().toString();
float minSalary = Float.parseFloat(parameters[1].getValue().toString());
float maxSalary = Float.parseFloat(parameters[2].getValue().toString()); // 拼装SQL
String sql = "select * from EMPLOYEES where JOB_ID='" + jobId
+ "' and SALARY between " + minSalary + " and " + maxSalary; rowData = new LinkedHashSet<LinkedHashMap<String, Object>>(); Connection conn = this.getConnection();
try {
Statement stmt = conn.createStatement();
// 执行查询
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
// 填充行数据
// 注:字段赋值的顺序,要跟枚举里的顺序一样
LinkedHashMap<String, Object> row = new LinkedHashMap<String, Object>();
row.put(FIELD_NAME.EMPLOYEE_ID.toString(),
rs.getInt(FIELD_NAME.EMPLOYEE_ID.toString()));
row.put(FIELD_NAME.FIRST_NAME.toString(),
rs.getString(FIELD_NAME.FIRST_NAME.toString()));
row.put(FIELD_NAME.LAST_NAME.toString(),
rs.getString(FIELD_NAME.LAST_NAME.toString()));
row.put(FIELD_NAME.EMAIL.toString(),
rs.getString(FIELD_NAME.EMAIL.toString()));
row.put(FIELD_NAME.PHONE_NUMBER.toString(),
rs.getString("PHONE_NUMBER"));
row.put(FIELD_NAME.HIRE_DATE.toString(),
rs.getDate(FIELD_NAME.HIRE_DATE.toString()));
row.put(FIELD_NAME.JOB_ID.toString(),
rs.getString(FIELD_NAME.JOB_ID.toString()));
row.put(FIELD_NAME.SALARY.toString(),
rs.getFloat(FIELD_NAME.SALARY.toString()));
rowData.add(row);
}
rs.close();
stmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
} } // 获取数据库连接
private Connection getConnection() {
Connection con = null;
try {
DataSource dataSource = AppContext.getInstance().getAppContext()
.getBean("dataSource", DataSource.class);
con = dataSource.getConnection();
} catch (Exception e) {
e.printStackTrace();
return null;
}
return con;
} // 释放资源
public void release() throws Exception {
super.release();
this.rowData = null;
} }

改进的地方:
1、getConnection方法,利用Spring注入datasource,当然为了注入方便,还需要一个辅助类AppContext

 package infosky.ckg.utils;

 import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppContext {
private static AppContext instance; private AbstractApplicationContext appContext; public synchronized static AppContext getInstance() {
if (instance == null) {
instance = new AppContext();
}
return instance;
} private AppContext() {
this.appContext = new ClassPathXmlApplicationContext(
"spring/root-context.xml");
} public AbstractApplicationContext getAppContext() {
return appContext;
} }

classes/spring/root-context.xml 里配置db连接

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" />
<property name="username" value="hr" />
<property name="password" value="hr" />
</bean>
</beans>

2、将原来的数组,换成了LinkedHashSet<LinkedHashMap<String, Object>>,这样db查询结果填充到"数据集"时,处理代码的可读性就多好了(见queryData方法),但也要注意到LinkedHashSet/LinkedHashMap的性能较Array而言,有所下降,正所谓:有所得必有得失。但对于复杂的汇总统计报表,展示的数据通常不会太多,所以这个问题我个人看来并不严重。

最新文章

  1. 分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载
  2. 一致性hash算法详解
  3. 三维网格补洞算法(Poisson Method)
  4. webpy使用笔记(二) session/sessionid的使用
  5. json学习系列(3)-JSONObject的过滤设置
  6. Android jni开发资料--NDK环境搭建
  7. Highlighting Text Item On Entry In Oracle Forms
  8. BSON与JSON的区别
  9. httpmime-session 会话保持
  10. ListBox重绘
  11. AHOI1997彩旗飘飘 VIJOS1097合并果子(noip2007)
  12. Qt对xml文件的读写
  13. mysql 分组和聚合函数
  14. git的一些常见命令
  15. ArcCore重构-目标文件结构化
  16. shell中,我们可以通过简单的一个判断来判断命令是否存在
  17. keepalive配置mysql自动故障转移
  18. Go语言学习笔记(三) [控制结构、内建函数]
  19. 【前端】JS文本比较插件
  20. html标签之Object标签详解

热门文章

  1. 自动kill慢查询
  2. 双十一来了,别让你的mongodb宕机了
  3. SQL Server 使用游标更新数据库中的数据(使用存储过程)
  4. jQuery简单入门(四)
  5. 使用For XML PATH 会影响Cross Apply 返回
  6. (原创)大数据时代:基于微软案例数据库数据挖掘知识点总结(Microsoft 决策树分析算法)
  7. 在eclipse中使用maven创建springMVC项目
  8. Js 关于console 在IE 下的兼容问题
  9. 定时器的应用---查询方式---让8个LED灯,左右各4个来回亮
  10. 报表开发工具中mysql数据库连接编码转化失效解决方案