最近一直在用extjs来开发网站的后台管理程序,经常要对数据进行保存,有时要同时对多张表保存,如果每次都一点点开发将会非常麻烦,于是自己对前台和后台进行了封装,可以实现前台数据的自动搜集,后台数据的自动接收,数据的自动保存。
第一步:前台和后台数据传递要有一个规范:
1.model类要有一个origin字段来保存数据的原始记录,后台操作有时需要对比原始记录,增强程序的可拓展性, model类的示例代码如下:
package com.sesan.pub.model;
import com.sesan.core.annotations.ID;
public class App {
@ID
private String appCode;
private String appName;
private String shortname;
private String imageUrl;
private String url;
private String des;
private Short status;
private App origin;
public String getAppCode() {
return appCode;
}
public void setAppCode(String appCode) {
this.appCode = appCode;
}
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public String getShortname() {
return shortname;
}
public void setShortname(String shortname) {
this.shortname = shortname;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public Short getStatus() {
return status;
}
public void setStatus(Short status) {
this.status = status;
}
public App getOrigin() {
return origin;
}
public void setOrigin(App origin) {
this.origin = origin;
}
}
2. ModelOperator<T>对象,T为model对象,该对象包含新增的数据集,修改的数据集和删除的数据集,代码如下:
package com.sesan.core.model;
import java.util.List;
/**
* 单表保存数据收集对象,Grid和Form都可以使用, grid和form都可以理解为datawindow的一种
*
* @author Shilian Peng
*
* @param <T> 模型对象,如User
*/
public class ModelOperator<T> {
List<T> addRecords;
List<T> updateRecords;
List<T> deleteRecords;
public List<T> getAddRecords() {
return addRecords;
}
public void setAddRecords(List<T> addRecords) {
this.addRecords = addRecords;
}
public List<T> getUpdateRecords() {
return updateRecords;
}
public void setUpdateRecords(List<T> updateRecords) {
this.updateRecords = updateRecords;
}
public List<T> getDeleteRecords() {
return deleteRecords;
}
public void setDeleteRecords(List<T> deleteRecords) {
this.deleteRecords = deleteRecords;
}
}
第二步:前台在做CRUD的时候需要做相关的操作:
1.在数据的读取,新增,修改和删除操作都要使用拓展的方法, 这样在数据保存的时候就可以统一的收集数据,封装的代码如下:
/**
* Grid 增删改查操作类
*/
Ext.define('Ext.grid.ModelOperator', {
/**
* 收集grid保存是需要的数据
*/
getUpdateInfos: function( ){
var grid = this;
var store = grid.getStore();
var j = store.getCount();
var i;
var record;
var data;
var m=[],n=[],d=[];
var deleteRerords ;
//获取新增或修改的记录
for(i=j-1;i>=0;i--){
record=store.getAt(i);
if(record.dirty){
data = Ext.apply({},record.data );
//将boolean类型值转化成0 1
Ext.Object.each(data, function(key, value, myself) {
if (value === false) {
data[key]=0;
}
if (value === true) {
data[key]=1;
}
});
if(record.__isNew){
n.push(data);
}else{
data.origin=record.__origin;
//将boolean类型值转化成0 1
Ext.Object.each(data.origin, function(key, value, myself) {
if (value === false) {
data.origin[key]=0;
}
if (value === true) {
data.origin[key]=1;
}
});
m.push(data);
}
}else if( record.__isNew ){
store.remove(record);
}
}
//获取删除的记录
deleteRerords = store.getRemovedRecords( );
for(i=0 ;i<deleteRerords.length;i++){
if( !deleteRerords[i].__isNew ){
d.push(deleteRerords[i].__origin);
}
}
var Operator = {
addRecords:n,
deleteRecords:d,
updateRecords:m
};
return Operator;
},
retrieve:function( parms){
var grid = this;
var store = grid.getStore();
if (parms){
store.proxy.extraParams=parms;
}
store.load({
scope: store,
callback: function(records, operation, success) {
if (success==true){
this.each(function(b) {
b.__origin = Ext.apply({}, b.data);
});
}
}
});
} ,
insertDatas:function( record){
var grid=this;
var store = grid.getStore();
var row ;
var gridsel = grid.getSelectionModel().getSelection( ) ;
if (gridsel.length>0){
row =store.indexOf( gridsel[0] );
}else{
row = store.count();
}
var records= store.insert(row,record ? record : {});
records.forEach(function(record){
record.__isNew=true;
record.dirty = true;
}) ;
if( grid.plugins){
var edit = grid.plugins[0] ;
edit.startEditByPosition({row:row,column:1});
}
},
deleteSelectRecords:function( ){
var grid = this;
var store = grid.getStore( );
var sels= grid.getSelectionModel().getSelection( ) ;
var row;
var i;
if (sels.length<=0){
return ;
}
store.remove(sels);
},
/**
* 获取选择的记录集中的data值集合
*/
getSelectDatas: function( ){
var me= this;
var datas=[];
var records = me.getSelectionModel( ).getSelection( ) ;
len = records.length;
if (len) {
for (i = 0; i < len; i++) {
record = records[i];
datas.push(record.data );
}
}
return datas;
} ,
selectAll :function(){
var me= this;
me.getSelectionModel().selectAll();
}
});
Ext.grid.Panel.mixin('ModelOperator', Ext.grid.ModelOperator);
2.示例代码如下:
Ext.define("app.controller.AppController", {
extend: "Ext.app.Controller",
config: {
refs: [{
ref: 'window',
selector: 'appView'
}]
},
init: function() {
this.control({
'appView': {
afterrender: this.initWindow
} ,
'appView button[action=addAction]' : {
click : this.add
},
'appView button[action=delAction]' : {
click : this.del
},
'appView button[action=saveAction]' : {
click : this.save
},
'appView button[action=refreshAction]' : {
click : this.refresh
}
});
},
constructor: function(){
Ext.applyIf(this, this.config);
this.callSuper(arguments);
},
initWindow : function(){
this.refresh();
},
add:function(){
var window = this.getWindow();
var grid = window.down('gridpanel[itemId=grd1]');
grid.insertDatas();
},
del:function(){
var grid = this.getWindow().down('gridpanel[itemId=grd1]');
grid.deleteSelectRecords();
},
save:function(){
var window = this.getWindow();
var grid1 = window.down('gridpanel[itemId=grd1]')
var appOperator =grid1.getUpdateInfos();
//ajax提交数据
Ext.Ajax.request({
scope: this,
url: 'app/saveModels.do',
params: {
appOperator: Ext.JSON.encode( appOperator)
},
success : function(response, options){
this.refresh( );
}
});
},
refresh : function(){
var window = this.getWindow();
var grid= window.down('grid');
grid.retrieve();
this.getWindow().down('gridpanel[itemId=grd1]').retrieve();
}
})
第三步:struts2接收数据的时候可以使用struts2的全局转换器:
1.转换器代码如下:
package com.sesan.core.struts2.convert;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter;
import com.sesan.core.model.ModelOperator;
import com.sesan.core.util.StringUtils;
import net.sf.json.JSONObject;
/**
* ModelOperator<Model> 对象转换器
*
*
* @author Shilian Peng
*
*/
public class ModelConvert extends DefaultTypeConverter {
@Override
public Object convertValue(Map<String, Object> context, Object target, Member member, String propertyName, Object value, @SuppressWarnings("rawtypes") Class toType) {
Class<?> actionClass = member.getDeclaringClass() ;
String fileName = member.getName();
fileName = StringUtils.toLowerCaseFirstOne(fileName.substring(3 ) ) ;
Field field = null;
try {
field = actionClass.getDeclaredField(fileName);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
field.setAccessible(true);
ParameterizedType pType = (ParameterizedType)field.getGenericType();
Type[] types = pType.getActualTypeArguments();
Class<?> modelClass = (Class<?>)types[0];
JSONObject jsonObject = JSONObject.fromObject(((String[])value)[0]);
Map<String, Object> config = new HashMap<String, Object>();
config.put("addRecords", modelClass );
config.put("updateRecords", modelClass );
config.put("deleteRecords", modelClass );
return JSONObject.toBean(jsonObject, ModelOperator.class, config);
}
}
2.action 代码如下
package com.sesan.pub.action;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import com.sesan.core.model.ModelOperator;
import com.sesan.pub.model.App;
import com.sesan.pub.service.IAppService;
public class AppAction {
@Autowired
private IAppService appService;
Map<String, Object> result;
private ModelOperator<App> appOperator;
public String getAllApp() {
result = new HashMap<String, Object>();
try {
List<App> rows = appService.selectAll();
result.put("rows", rows);
result.put("success", true);
} catch (Exception e) {
result.put("success", false);
result.put("msg", e.getMessage());
e.printStackTrace();
}
return "json";
}
public String saveModels() {
result = new HashMap<String, Object>();
try {
appService.saveModels(appOperator);
result.put("success", true);
} catch (Exception e) {
result.put("success", false);
result.put("msg", e.getMessage());
e.printStackTrace();
}
return "json";
}
public Map<String, Object> getResult() {
return result;
}
public void setResult(Map<String, Object> result) {
this.result = result;
}
public void setAppOperator(ModelOperator<App> appOperator) {
this.appOperator = appOperator;
}
}
第四步:service层实现数据的自动保存
1.通过传递Mapper对象 ModelOperator<T>就可以实现数据的自动保存,前提是所有的Mapper对象有相同的方法,我的程序中是使用mybatis的generator插件自动生成的代码,可以保证方法是相同的,详细代码如下:
package com.sesan.core.model;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.sesan.core.annotations.ID;
import com.sesan.core.util.StringUtils;
/**
* 单表保存逻辑封装类,模型对象必须有ID注解,否则认为是组合主键
*
* @author Shilian Peng
*
* @param <T> Mapper对象
* @param <M> Model对象
*/
public class ModelSave<T, M> {
/**
* 单表保存,当发生数据库异常时返回运行时异常,事务会自动回滚
* @param mapper
* @param modelOperator
*/
public void saveModels(T mapper, ModelOperator<M> modelOperator) {
for (M record : modelOperator.getDeleteRecords()) {
Field pkField = null;
Method getKeyMethod = null;
boolean KeyFlag = false;// 如果有ID注解则为true
// 获取主键,如果有ID注解,则主键为单个字段,如果没有,则视为符合主键,可把record当做主键,因为model对象是集成主键的
for (Field field : record.getClass().getDeclaredFields()) {
if (field.getAnnotation(ID.class) != null) {
pkField = field;
}
}
// 获取主键值
if (pkField != null) {
for (Method method : record.getClass().getMethods()) {
if (method.getName().equals(
"get"
+ StringUtils.toUpperCaseFirstOne(pkField
.getName()))) {
getKeyMethod = method;
KeyFlag = true;
break;
}
}
}
for (Method method : mapper.getClass().getMethods()) {
if ("deleteByPrimaryKey".equals(method.getName())) {
try {
if (KeyFlag) {
method.invoke(mapper, getKeyMethod.invoke(record));
} else {
method.invoke(mapper, record);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException();
} catch (IllegalArgumentException e) {
e.printStackTrace();
throw new RuntimeException();
} catch (InvocationTargetException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
}
}
for (M record : modelOperator.getUpdateRecords()) {
for (Method method : mapper.getClass().getMethods()) {
if ("updateByPrimaryKey".equals(method.getName())) {
try {
method.invoke(mapper, record);
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException();
} catch (IllegalArgumentException e) {
e.printStackTrace();
throw new RuntimeException();
} catch (InvocationTargetException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
}
}
for (M record : modelOperator.getAddRecords()) {
for (Method method : mapper.getClass().getMethods()) {
if (("insertSelective").equals(method.getName())) {
try {
System.out.println(record);
method.invoke(mapper, record);
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException();
} catch (IllegalArgumentException e) {
e.printStackTrace();
throw new RuntimeException();
} catch (InvocationTargetException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
}
}
}
}
2.主键注解类,如果有主键注解认为主键是单个字段,如果没有的话认为是复合主键,由于model如果是复合主键那么model类是继承于复合主键的如:public class Module extends ModuleKey ,所以主键可以认为是整个对象,主键注解类代码如下:
package com.sesan.core.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 模型对象的ID注解
*
* @author Shilian Peng
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ID {
}
3.services类的示例代码:
package com.sesan.pub.service.imp;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.sesan.core.model.ModelOperator;
import com.sesan.core.model.ModelSave;
import com.sesan.pub.dao.AppMapper;
import com.sesan.pub.model.App;
import com.sesan.pub.service.IAppService;
public class AppServiceImp implements IAppService {
@Autowired
private AppMapper appMapper ;
@Override
public List<App> selectAll() {
return appMapper.selectAll();
}
@Transactional(value = "pubTransactionManager", propagation = Propagation.REQUIRED, rollbackForClassName = {
"Exception", "RuntimeException" })
@Override
public void saveModels(ModelOperator<App> AppsOperator) {
new ModelSave<AppMapper, App>().saveModels(appMapper,
AppsOperator);
}
}
这些是我这些天的劳动成果,个人感觉能够加快程序的开发速度,不过肯定也有很多不足,贴出来和大家一起探讨。