MongoDB程序数据源+报表设计探路
程序数据源之所以要用到程序数据源,是因为组内的项目使用的数据库是MongoDB,而帆软的mongodb插件支持的查询深度还不够,所以采用程序数据源的方式来处理。
帆软报表控件程序数据源的写法说白了比较简单,就是实现AbstractTableData这个抽象类,然后实现其中的4个抽象方法即可。
但是使用起来不是很顺畅,原因在于:
- 首先要在在IDE中写程序数据源的代码;
- 然后编译对应的代码生成class文件;
- 将class文件拷贝到帆软报表设计器对应的classes目录下;
- 打开报表设计器选择程序数据源(class文件)进行报表设计;
- 设计完成的报表拷贝到项目的reportlets目录下;
可以看到,需要不停地在IDE和报表设计器之间切换,还需要在资源管理器中移动文件。可能这是目前嵌入式部署不得不面对的问题,如果是独立部署的话,可能就仅仅需要将classes文件拷贝到帆软报表设计器对应目录下即可。
测试程序数据源可行性在项目中写了一个数据源,如下:public class TestDemo extends AbstractTableData { private String columnNames; private Object rowData; public TestDemo() { String columnNames = {"Name", "Score"}; Object datas = {{"Alex", new Integer(15)}, {"Helly", new Integer(22)}, {"Bobby", new Integer(99)}}; this.columnNames = columnNames; this.rowData = datas; } @Override public int getColumnCount() throws TableDataException { return columnNames.length; } @Override public String getColumnName(int columnIndex) throws TableDataException { return columnNames; } @Override public int getRowCount() throws TableDataException { return rowData.length; } @Override public Object getValueAt(int rowIndex, int columnIndex) { return rowData; }}
不涉及业务,不涉及mongodb数据库,仅仅测试一下可行性。
按照上面的步骤设计完报表之后,将报表拷贝回项目reportlets目录,运行:
http://img.blog.csdn.net/20170807094355603?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWNoYW5nMDc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
可以看到,程序运行OK,报表也能正常显示。
MongoDB程序数据源由于写了程序数据源之后,是要放到报表设计器目录下进行报表设计才能看到结果的,这样给调试带来了一些难度。
解决办法就是:
- main方法进行跟踪调试;
- 单元测试; // 数据源一共有多少列(多少个字段) public abstract int getColumnCount() throws TableDataException; // 对应index的那一列的列名(字段名)是什么 public abstract String getColumnName(int index) throws TableDataException; // 数据源一共有多少行(多少条记录) public abstract int getRowCount() throws TableDataException; // 对应rowIndex的那一行的第columnIndex列的数值 public abstract Object getValueAt(int rowIndex, int columnIndex);
从抽象类接口可以看出,四个抽象方法意义很明确,使用main方法或者单元测试来进行验证的话,跟踪调试查看的数据没有问题,那么报表中的数据应该就没有问题。关于数据库连接的问题按照从前的思维,希望能够使用数据库连接池,复用数据库连接。
然后发现MongoDB的MongoClient和关系型数据库的连接还是有一些区别的,可能开销没有那么大。因此在数据源中直接new新的MongoClient来使用。
再后来仔细想想,我们的程序数据源不是运行在我们的Web容器中,不是使用spring进行管理,而是编译成为class文件之后拷贝到帆软的设计器目录下使用的,那个时候,怎么使用数据库连接池中的连接资源呢?
所以,目前还是在每个数据源中直接new连接的方式来操作,有没有更好的办法抽空问问帆软的技术支持。数据源Demo按照业务,从我们自己的mongo数据库中获取数据,写了如下一个数据源:public class BQItemDataSource extends AbstractTableData { private enum Field_Name { CODE("code"), DESCRIPTION("description"), SPEC("spec"), UNIT("unit"), JLGZ("jlgz"), TYPE("type"), QUANTITY("quantity"); private String fieldName; private Field_Name(String fieldName) { this.fieldName = fieldName; } @Override public String toString() { return fieldName; } } private String columnNames; private ArrayList> rowData; public BQItemDataSource() {// setDefaultParameters(new Parameter {new Parameter("ObjectId")}); columnNames = new String; int i = 0; for (Field_Name fieldName : Field_Name.values()) { columnNames = fieldName.toString(); } } @Override public int getColumnCount() throws TableDataException { return columnNames.length; } @Override public String getColumnName(int columnIndex) throws TableDataException { return columnNames; } @Override public int getRowCount() throws TableDataException { init(); return rowData.size(); } @Override public Object getValueAt(int rowIndex, int columnIndex) { init(); if (columnIndex >= columnNames.length) { return null; } if (rowIndex >= rowData.size()) { return null; } return rowData.get(rowIndex).get(columnNames); } public void init() { if (rowData != null) { return; } rowData = new ArrayList>();// String objectId = parameters.getValue().toString(); String objectId = "5959b01b9107541bbc5cb98b"; JSONArray dataArray = getMongoTableData(); for (int i = 0; i row = new LinkedHashMap(); if (dataObj.containsKey(Field_Name.CODE.toString())) { row.put(Field_Name.CODE.toString(), dataObj.getString(Field_Name.CODE.toString())); } else { row.put(Field_Name.CODE.toString(), null); } if (dataObj.containsKey(Field_Name.DESCRIPTION.toString())) { row.put(Field_Name.DESCRIPTION.toString(), dataObj.getString(Field_Name.DESCRIPTION.toString())); } else { row.put(Field_Name.DESCRIPTION.toString(), null); } if (dataObj.containsKey(Field_Name.SPEC.toString())) { row.put(Field_Name.SPEC.toString(), dataObj.getString(Field_Name.SPEC.toString())); } else { row.put(Field_Name.SPEC.toString(), null); } if (dataObj.containsKey(Field_Name.UNIT.toString())) { row.put(Field_Name.UNIT.toString(), dataObj.getString(Field_Name.UNIT.toString())); } else { row.put(Field_Name.UNIT.toString(), null); } if (dataObj.containsKey(Field_Name.JLGZ.toString())) { row.put(Field_Name.JLGZ.toString(), dataObj.getString(Field_Name.JLGZ.toString())); } else { row.put(Field_Name.JLGZ.toString(), null); } if (dataObj.containsKey(Field_Name.TYPE.toString())) { row.put(Field_Name.TYPE.toString(), dataObj.getString(Field_Name.TYPE.toString())); } else { row.put(Field_Name.TYPE.toString(), null); } if (dataObj.containsKey(Field_Name.QUANTITY.toString())) { row.put(Field_Name.QUANTITY.toString(), dataObj.getDouble(Field_Name.QUANTITY.toString())); } else { row.put(Field_Name.QUANTITY.toString(), null); } rowData.add(row); } } private JSONArray getMongoTableData() { MongoClient mongoClient=new MongoClient( "localhost" , 27017 ); MongoDatabase db = mongoClient.getDatabase("gbq"); MongoCollection collection = db.getCollection("project"); Document doc= collection.find(eq("_id", new ObjectId("5959b01b9107541bbc5cb98b"))).first(); if(doc!=null){ JSONObject data = JSONObject.fromObject(doc.get("data")); return data.getJSONArray("bqItem"); }else{ return new JSONArray(); } } public void release() throws Exception { super.release(); this.rowData = null; }// public static void main(String args) {// BQItemDataSource dataSource = new BQItemDataSource();// dataSource.init();// try {// System.out.println(dataSource.getColumnCount());// for (int i = 0; i < dataSource.getColumnCount(); i++) {// System.out.println(dataSource.getColumnName(i));// }// for (int i = 0; i < dataSource.getRowCount(); i++) {// for (int j = 0; j < dataSource.getColumnCount(); j++) {// System.out.print(dataSource.getValueAt(i, j) + "@");// }// System.out.println();// }// } catch (Exception e) {// e.printStackTrace();// }// }}
如上的数据源中,没有使用传参方式,而是直接在代码中把参数写死了。实际上可以采用传参的方式,在帆软报表设计器中可以设定参数。
但是,上面的数据源class拷贝到帆软设计器目录下的时候,字段是可以获取到的,但是数据是获取不到的。
http://img.blog.csdn.net/20170807100310671?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWNoYW5nMDc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
预览报表的时候页面是空的。依赖的问题想了很久,没有得到答案。周末的时候突然灵光一闪,想到一个可能的原因:
- 我们的程序数据源中使用了JSON和MongoClient,这两个东西是依赖于某些jar包的;
- 程序数据源的class文件拷贝到帆软目录下,在帆软的内置服务器中运行,而这个内置服务器未必引用了对应的jar包;
- 那为什么这个class数据源还是可以引用,并且能看到字段呢?因为帆软中引用的是class文件,而不是源代码,用到字段的时候调用getColumnCount和getColumnName这两个方法,是不受影响的;而显示数据的时候,调用getRowCount和getValueAt两个方法,内部使用了JSON和MongoClient相关的接口,可能内部已经报异常了。
解决办法:
- 查看程序数据源的jar包依赖(使用IDEA的Maven插件查看依赖关系图):
http://img.blog.csdn.net/20170807101154005?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWNoYW5nMDc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
- 将所依赖的jar包拷贝到帆软的lib目录下:
从上图可以看出,直接依赖的包有json-lib和mongodb-driver;
因mongdodb而间接依赖的包有mongodb-driver-core和bson;
因json而间接依赖的包有commons-beanutils、commons-logging、commons-lang、commons-collections、ezmorph。
完成以上步骤之后,在帆软报表设计器中设计完报表点击预览,可以看到报表的内容:
http://img.blog.csdn.net/20170807101449650?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWNoYW5nMDc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
至此,程序数据源的路已经走通,可以尽情发挥业务编程能力,编写数据源了!