接着上一篇文章业务开始围绕原始凭证展开,而展开的基础无疑是围绕着科目展开的。首先我们业务层面以财政部的小企业会计准则的一级科目引入软件中。下面我们来考虑如何将科目切入软件更加灵活,方便业务扩展、维护与升级。
SPI是首先想到的数据服务方式
为什么会想到它呢?首先当然是根据遗忘规律。因为我们是有空的时候才去写一点代码,不像以此为职业的开发人员每天在推进设想,时间久了会将类、方法和设计都记到脑子里;而做为业余爱好人员有可能有时每天都写,有时一两个月不会想起此事,当再次打开电脑的时候,有可能想去取个科目数据,却都想不起它是那个类了,如果你没有这种情况,那说明你还好,做事情比较专注。而利用SPI,将所有的基础资料,数据库方面的数据以一种服务的方式集中放在一个模块中,则很容易让我们去回想或提取数据。另一个原因,这种方式对我们使用数据库连接池druid也更加方便和易于管理,至少目前的软件架构我是这样认为的,当然你也可以按照自己的设想来设计。
定义数据服务接口
最初的服务定义方式可以根据实际情况有很多种设计方式,但我想了一下以下三个属性是首要考虑的:数据类型、数据库操作方式、数据提取方式。数据类型像科目、物料、仓库等,数据库操作方式有增改查删除、执行存储过程、函数等、提取方式像一次性全部提取还是条件输入提取等。我们用最简单的方式去开始,必要的时候再重构。以科目为例,在一个新的模块中,定义接口如下:
public interface iKeMu {
/**
* 数据库更改操作
*/
public int updateDB(Connection con, String sql);
/**
* 返回可观察全部列表的科目数据
* @return 科目所有的信息
*/
ObservableList<KeMu> getObservalKeMu();
}
这个服务接口只是部分,后面可以根据需要自己增加,目前我们只根据眼下的需要实现getObservalKeMu()方法,他返回数据库科目表的所有数据让我们来展示。
其完整实现过程如下:
public class KeMu implements iKeMu {
/**
* 数据库主键值
*/
private String strDbId;
/**
* 网上下载的科目表的序号,保留实际意义
*/
private String strNo;
/**
* 科目类型
*/
private String keMuType;
/**
* 科目层级
*/
private String keMuLevel;
/**
* 科目代码
*/
private String keMuNum;
/**
* 科目名称
*/
private String keMuName;
/**
* 科目余额方向
*/
private String yuEdriect;
/**
* 科目的状态
*/
private String status;
/**
* 科目创建日期
*/
private String createDate;
/**
* 科目审核日期
*/
private String checkDate;
/**
* 审核人
*/
private String CheckEmp;
public KeMu() {
}
/**
* 修改数据库
*
* @param con 数据库的连接
* @param sql 修改SQL语句
* @return 返回修改影响的行数,为0表示一行数据都没有被修改 为Statement.EXECUTE_FAILED表示执行失败。
*/
@Override
public int updateDB(Connection con, String sql) {
Statement sm = null;
int affectRows = 0;
try {
// 首先获得表的所有数据
sm = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
affectRows = sm.executeUpdate(sql);
} catch (SQLException e) {
e.printStackTrace();
// 如果出现异常,则表示执行失败。
} finally {
// 关闭Statement
JDBCUtils.close(sm,con);
}
return affectRows;
}
public String getStrDbId() {
return strDbId;
}
public void setStrDbId(String strDbId) {
this.strDbId = strDbId;
}
public String getStrNo() {
return strNo;
}
public void setStrNo(String strNo) {
this.strNo = strNo;
}
public String getKeMuNum() {
return keMuNum;
}
public void setKeMuNum(String keMuNum) {
this.keMuNum = keMuNum;
}
public String getKeMuName() {
return keMuName;
}
public static void main(String[] args) {
try {
new KeMu().getDataList();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void setKeMuName(String keMuName) {
this.keMuName = keMuName;
}
/**
* 从数据库中查询科目信息
* @return 保存信息的HashMap
* @throws SQLException 数据库异常
*/
public static HashMap<String, HashMap<String, Object>> getDataList() throws SQLException {
HashMap<String, HashMap<String, Object>> hashMapKemu = null;
String strQueryGroup ="SELECT [id] "+
" ,[serNo] ,[numberCode] ,[keType] ,[keMuName] ,[keMuLevel] ,[amountDirect]"+
" ,[status] ,[createDate] ,[createEmp] ,[checkDate] FROM [FarmSystem].[dbo].[Kemu] ";
Connection con = null;
Statement sm = null;
try {
//用测试类直接取得连接,后面要修改成连接池生产环境形式
con = JDBCUtils.getConnection();
sm = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
// 查询
ResultSet rs = JDBCUtils.queryDB(sm, strQueryGroup);
hashMapKemu=JDBCUtils.mapResultSet(rs);
// 关闭结果集�
rs.close();
} catch (SQLException e2) {
throw e2;
} finally {
// 关闭数据库连接
JDBCUtils.close(sm,con);
}
return hashMapKemu;
}
public String getYuEdriect() {
return yuEdriect;
}
public void setYuEdriect(String yuEdriect) {
this.yuEdriect = yuEdriect;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCreateDate() {
return createDate;
}
public void setCreateDate(String createDate) {
this.createDate = createDate;
}
public String getCheckDate() {
return checkDate;
}
public void setCheckDate(String checkDate) {
this.checkDate = checkDate;
}
public KeMu(String strDbId, String strNo, String keMuNum, String keMuName, String yuEdriect, String status, String createDate, String checkDate) {
this.strDbId = strDbId;
this.strNo = strNo;
this.keMuNum = keMuNum;
this.keMuName = keMuName;
this.yuEdriect = yuEdriect;
this.status = status;
this.createDate = createDate;
this.checkDate = checkDate;
}
public String getKeMuType() {
return keMuType;
}
public void setKeMuType(String keMuType) {
this.keMuType = keMuType;
}
public String getKeMuLevel() {
return keMuLevel;
}
public void setKeMuLevel(String keMuLevel) {
this.keMuLevel = keMuLevel;
}
public String getCheckEmp() {
return CheckEmp;
}
public void setCheckEmp(String checkEmp) {
CheckEmp = checkEmp;
}
/**
* 取得科目数据字典
* @return 科目可观察数据列表
*/
@Override
public ObservableList<KeMu> getObservalKeMu() {
/**
* 科目数据源
*/
ObservableList<KeMu> data = null;
try {
//将HashMap转换为对象数据,以便适配可观察对象列表
HashMap<String, HashMap<String, Object>> tempData = KeMu.getDataList();
Object[] tempDataValues = tempData.values().toArray();
KeMu[] destData = new KeMu[tempDataValues.length];
for (int i = 0; i < tempDataValues.length; i++) {
KeMu tempModelKeMu = new KeMu();
HashMap<String, String> oneHashMap = (HashMap<String, String>) tempDataValues[i];
tempModelKeMu.setStrDbId(oneHashMap.get("id"));
tempModelKeMu.setStrNo((String) oneHashMap.get("serNo"));
tempModelKeMu.setKeMuNum((String) oneHashMap.get("numberCode"));
tempModelKeMu.setKeMuType((String) oneHashMap.get("keType"));
tempModelKeMu.setKeMuName((String) oneHashMap.get("keMuName"));
tempModelKeMu.setKeMuLevel((String) oneHashMap.get("keMuLevel"));
tempModelKeMu.setYuEdriect((String) oneHashMap.get("amountDirect"));
tempModelKeMu.setStatus((String) oneHashMap.get("status"));
tempModelKeMu.setCreateDate((String) oneHashMap.get("createDate"));
tempModelKeMu.setCheckDate((String) oneHashMap.get("createEmp"));
tempModelKeMu.setCheckEmp((String) oneHashMap.get("checkDate"));
destData[i] = tempModelKeMu;
}
data = FXCollections.observableArrayList(destData);
} catch (SQLException ex) {
Logger.getLogger(KeMu.class.getName()).log(Level.SEVERE, null, ex);
}
return data;
}
增加数据模块的访问控制
接口和实现完成后,我们要在数据模块的模块信息文件中进行相应的描述说明,眼下至少增加如下两行:
exports baseDataModel; provides iKeMu with KeMu;
具体的意思可参考SPI文档说明。
注意:数据模块做了如上的工作,同样的,在业务模块必须在模块描述文件中做相应的描述。
我在业务模块中做了如下的说明:
requires dbSqlServer;
uses iKeMu;
这样在业务模块中就可以进行服务调用了。
优雅的使用数据模块提供的服务
以上工作完成后,在业务模块使用的时候,只要导入相应的包就可以了,部分代码如下:
import baseDataModel.KeMu;
import baseDataModel.iKeMu;
....
import java.util.Arrays;
import java.util.ServiceLoader;
ServiceLoader<iKeMu> is=ServiceLoader.load(iKeMu.class);
for(iKeMu keMu:is){
obsKeMu=keMu.getObservalKeMu();
}
if(obsKeMu!=null){
Globle.debugPrint("在财税模块输出数据服务提供的科目数据。");
}
至此,我们看到已经成功的提取了数据,如果如下图:
剩下的工作就是根据业务做灵活的自由发挥了。