开发工作中,我使用过很多框架,最近使用springMvc+sping4.1+Mybaties3.4搭建了SSM快速开发框架XJJ。前端使用ace、bootstrap响应式框架。做了常用的功能组件封装和单表、一对多表的自动代码生成。可以帮助开发者生成重复的代码,节约开发工作量。这些框架的原理、理解、及整合方法,请参考网上其他贴子,这些不是本文的重点。本文重点描述几点整合的一些思想和架构,并给出源码请大家参考。
代码下载: SSM快速开发框架XJJ (https://gitee.com/zhanghejie/xjj)
技术交流: QQ群 174266358
贴图,先睹为快:
一、模块代码设计
一般针对每一张表的增删改查、分页、条件查询、统计数量等功能,逻辑大概都是一致的,所以写了一个包含这些常用方法的接口。并把它的默认实现都写到一个实现该接口的抽象类中。这样继承了这个抽像类的service都默认实现了最常用的功ce
package com.xjj.framework.service;
import java.util.List;
import com.xjj.framework.entity.EntitySupport;
import com.xjj.framework.exception.DataAccessException;
import com.xjj.framework.web.support.Pagination;
import com.xjj.framework.web.support.XJJParameter;
public interface XjjService {
/**
* 保存
* @param obj
* @return
*/
public Long save(E obj);
/**
* 更新
* @param obj
*/
public void update(E obj);
/**
* 删除
* @param id
*/
public void delete(Long id);
/**
* 删除
* @param id
*/
public void delete(E obj);
/**
* 查询条数
* @param param
*/
public int getCount(XJJParameter param);
/**
* 根据ID得到实体类
* @param ID
* @return
*/
public E getById(Long ID);
/**
* 根据参数得到实体类
* @param param
* @return
*/
public E getByParam(XJJParameter param) throws DataAccessException;
/**
* 查询所有
* @return
*/
public List findAll();
/**
* 根据参数查询列表
* @param param
* @return
*/
public List findList(XJJParameter param);
/**
* 根据某属性值数组查询列表
* @param property
* @param objArr
* @return
*/
public List findListByColumnValues(String property,Object[] objArr);
/**
* 分页查询列表
* @param param
* @param page
* @return
*/
public Pagination findPage(XJJParameter param, Pagination page);
/**
* 判断是否唯一
* @param tableName
* @param columnName
* @param columnVal
* @param id
* @return
*/
public boolean checkUniqueVal( String tableName,String columnName,String columnVal,Long id);
}
package com.xjj.framework.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import com.xjj.framework.dao.CommonDao;
import com.xjj.framework.dao.XjjDAO;
import com.xjj.framework.entity.EntitySupport;
import com.xjj.framework.exception.DataAccessException;
import com.xjj.framework.utils.StringUtils;
import com.xjj.framework.web.support.Pagination;
import com.xjj.framework.web.support.XJJParameter;
import com.xjj.sec.dao.RoleDao;
@Transactional
public abstract class XjjServiceSupport implements XjjService {
@Autowired
private CommonDao commonDao;
public XjjServiceSupport(){ }
public abstract XjjDAO getDao();
public Long save(E obj) {
getDao().save(obj);
Long id = obj.getId();
return id;
}
public void update(E obj) {
getDao().update(obj);
}
public E getById(Long id) {
return (E)getDao().getById(id);
}
public void delete(Long id) {
getDao().delete(id);
}
public void delete(E obj) {
if(null!=obj && null!=obj.getId())
{
delete(obj.getId());
}
}
public int getCount(XJJParameter query) {
return getDao().getCount(query.getQueryMap());
}
public List findAll() {
return getDao().findAll();
}
public List findList(XJJParameter query) {
return getDao().findList(query.getQueryMap());
}
public Pagination findPage(XJJParameter query, Pagination page) {
int totalRecord = getDao().getCount(query.getQueryMap());
page.setTotalRecord(totalRecord);
int limit = page.getPageSize();
int currentPage = page.getCurrentPage();
int offset = (currentPage-1)*limit;
page.setItems(getDao().findPage(query.getQueryMap(),offset,limit));
return page;
}
public E getByParam(XJJParameter param) throws DataAccessException
{
List list = getDao().findList(param.getQueryMap());
if(null==list || list.size()==0)
{
return null;
}
if(list.size()>1)
{
throw new DataAccessException("得到一行数据,数据库却返回多条数据");
}
return list.get(0);
}
public List findListByColumnValues(String property, Object[] propValArr)
{
property = StringUtils.toUnderScoreCase(property);
return getDao().findListByColumnValues(property,propValArr);
}
public boolean checkUniqueVal(String tableName,String columnName,String columnVal,Long id)
{
int flag = commonDao.checkUniqueVal(tableName,columnName,columnVal,id);
if(flag>0)
{
return false;
}
return true;
}
}
package com.xjj.framework.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import com.xjj.framework.dao.CommonDao;
import com.xjj.framework.dao.XjjDAO;
import com.xjj.framework.entity.EntitySupport;
import com.xjj.framework.exception.DataAccessException;
import com.xjj.framework.utils.StringUtils;
import com.xjj.framework.web.support.Pagination;
import com.xjj.framework.web.support.XJJParameter;
import com.xjj.sec.dao.RoleDao;
@Transactional
public abstract class XjjServiceSupport implements XjjService {
@Autowired
private CommonDao commonDao;
public XjjServiceSupport(){ }
public abstract XjjDAO getDao();
public Long save(E obj) {
getDao().save(obj);
Long id = obj.getId();
return id;
}
public void update(E obj) {
getDao().update(obj);
}
public E getById(Long id) {
return (E)getDao().getById(id);
}
public void delete(Long id) {
getDao().delete(id);
}
public void delete(E obj) {
if(null!=obj && null!=obj.getId())
{
delete(obj.getId());
}
}
public int getCount(XJJParameter query) {
return getDao().getCount(query.getQueryMap());
}
public List findAll() {
return getDao().findAll();
}
public List findList(XJJParameter query) {
return getDao().findList(query.getQueryMap());
}
public Pagination findPage(XJJParameter query, Pagination page) {
int totalRecord = getDao().getCount(query.getQueryMap());
page.setTotalRecord(totalRecord);
int limit = page.getPageSize();
int currentPage = page.getCurrentPage();
int offset = (currentPage-1)*limit;
page.setItems(getDao().findPage(query.getQueryMap(),offset,limit));
return page;
}
public E getByParam(XJJParameter param) throws DataAccessException
{
List list = getDao().findList(param.getQueryMap());
if(null==list || list.size()==0)
{
return null;
}
if(list.size()>1)
{
throw new DataAccessException("得到一行数据,数据库却返回多条数据");
}
return list.get(0);
}
public List findListByColumnValues(String property, Object[] propValArr)
{
property = StringUtils.toUnderScoreCase(property);
return getDao().findListByColumnValues(property,propValArr);
}
public boolean checkUniqueVal(String tableName,String columnName,String columnVal,Long id)
{
int flag = commonDao.checkUniqueVal(tableName,columnName,columnVal,id);
if(flag>0)
{
return false;
}
return true;
}
}
这样下面这个service只要几行代码就实现了增删改查、统计、查询、分页等常用方法。
package com.xjj.sys.dict.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.xjj.sys.dict.dao.DictDao;
import com.xjj.sys.dict.entity.DictItem;
import com.xjj.sys.dict.service.DictService;
import com.xjj.framework.dao.XjjDAO;
import com.xjj.framework.service.XjjServiceSupport;
@Service
public class DictServiceImpl extends XjjServiceSupport implements DictService{
// 注入Service依赖
@Autowired
private DictDao dictDao;
@Override
public XjjDAO getDao() {
return dictDao;
}
}
二、MyBaties查询
不同表的查询、分页逻辑大概也是一样的,XJJ做了一接收参数的封装,然后使用MyBaties的foreach语句动态拼装sql代码,这样查询条件直接可以从前台就可以简单的注入到mapper中。下面是一个查询条件,查询code等于输入值的.
<@querygroup title='编码'>
接收参数的类如下:
package com.xjj.framework.web.support;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Set;
import java.util.regex.Pattern;
import com.xjj.framework.utils.DateTimeUtils;
import com.xjj.framework.utils.StringUtils;
/**
* 记录页面传递过来的查询信息,其中key的含义:属性名@操作@类型
* 格式:propName@operation@type
*
* 支持的操作:
* eq = EQUAL(=)——默认,如果没有操作则为等于
* lk = LIKE(like '%keyword%')
* kl= LIKE(like '%keyword')
* kr= LIKE(like 'keyword%')
* gt = GREAT(>)
* lt = LESS(<)
* ge = GREAT_EQUAL(>=)
* le = LESS_EQUAL(<=)
* nu = IS_NULL(is null)
* ne = NOT_EQUAL(!=)
* nn = NOT_NULL(is not null)
*
* 支持的类型:
* s = 字符串string:String ——默认,如果没有类型,则为字符串
* b = 逻辑boolean:Boolean
* i = 整数int:Integer
* l = 长整数long:Long
* f = 浮点float:Float
* d = 双浮点double:Double
* D = 日期date:Date(格式:yyyy-MM-dd)
* t = 时间datetime:Date(格式:yyyy-MM-dd hh:mm:ss)
*
* 例如:
* query.user_id@eq@l=1 对应sql is : and user_id = 1
* query.name@eq@s=tom 对应sql is : and name = 'tom'
* query.name@lk@s=tom 对应sql is : and name like '%tom%'
* @author zhanghejie
*/
public class XJJParameter{
private static String PARAM_FIX = "query.";
private static String PARAM_PATTERN = "[a-z_A-Z0-9]+@[a-z]{2}(@[sbilfDdt]{1})?";
/**
* Map存储示例
* {
* user_mame:{like:"%张三%","<=":"张三丰"}
* age:{">":33,"<="50}
* orderBy:{id:asc,age:desc}
* }
*/
private HashMap> paramMap = new HashMap>();
public HashMap> getQueryMap()
{
return paramMap;
}
public void clear() {
paramMap.clear();
}
public boolean containsKey(String key) {
key = StringUtils.toUnderScoreCase(key);
return paramMap.containsKey(key);
}
public HashMap get(String key) {
key = StringUtils.toUnderScoreCase(key);
return paramMap.get(key);
}
/**
* 获得查询
* @param query 例:
* @return
*/
public Object getQuery(String query) {
if(StringUtils.isBlank(query))
{
return null;
}
query=query.replace(PARAM_FIX, "");
String[] propArr = query.split("@");
if(propArr.length<2)
{
return null;
}
String propName = StringUtils.toUnderScoreCase(propArr[0].replace(PARAM_FIX,""));
String propOper = propArr[1];
propName = StringUtils.toUnderScoreCase(propName);
HashMap operMap = paramMap.get(propName);
if(null==operMap)
{
return null;
}
return operMap.get(oper2sql(propOper));
}
/**
* 是否包含
* @param query
* @return
*/
public boolean hasQuery(String query) {
Object obj = getQuery(query);
if(null==obj)
{
return false;
}else
{
return true;
}
}
public boolean isEmpty() {
return paramMap.isEmpty();
}
public Set keySet() {
return paramMap.keySet();
}
public void addQuery(String key, String val) {
if(StringUtils.isBlank(key) || StringUtils.isBlank(val) || !key.startsWith(PARAM_FIX))
{
return;
}
key=key.replace(PARAM_FIX, "");
boolean isMatch = Pattern.matches(PARAM_PATTERN, key);
if(!isMatch)
{
//丢弃or抛异常?
return ;
}
String propType =null;
String[] propArr = key.split("@");
String propName = StringUtils.toUnderScoreCase(propArr[0].replace(PARAM_FIX,""));
String propOper = propArr[1];
if(propArr.length==3)
{
propType = propArr[2];
}
HashMap param = paramMap.get(propName);
if(null==param)
{
param = new HashMap();
}
Object obj = type2obj(propOper,propType,val);
System.out.println(propName+"=="+oper2sql(propOper)+" "+obj);
param.put(oper2sql(propOper),obj);
paramMap.put(propName, param);
}
public void addQuery(String sqlKey, Number val) {
addQuery(sqlKey, String.valueOf(val));
}
public void addQuery(String sqlKey, Date val) {
addQuery(sqlKey, DateTimeUtils.formatDateTime(val));
}
/**
* 添加升序字段
* @param propName
*/
public void addOrderByAsc(String propName) {
propName=StringUtils.toUnderScoreCase(propName);
HashMap map = new HashMap();
map.put(propName, "asc");
paramMap.put("orderBy",map);
}
/**
* 添加降序字段
* @param propName
*/
public void addOrderByDesc(String propName) {
propName=StringUtils.toUnderScoreCase(propName);
HashMap map = new HashMap();
map.put(propName, "desc");
paramMap.put("orderBy",map);
}
public HashMap remove(String arg0) {
return paramMap.remove(arg0);
}
public int size() {
return paramMap.size();
}
public Collection> values() {
return paramMap.values();
}
public String getSqlCondition() {
StringBuilder sqlCondition = new StringBuilder();
Object obj = null;
for (String key : paramMap.keySet()) {
obj = null;
if(!"orderBy".equals(key))
{
for (String oper : paramMap.get(key).keySet()) {
sqlCondition.append(" and ");
sqlCondition.append(key);
sqlCondition.append(" ");
sqlCondition.append(oper);
obj = paramMap.get(key).get(oper);
if(null != obj)
{
sqlCondition.append(" ");
if(obj instanceof String)
{
sqlCondition.append("'"+obj+"'");
}else if(obj instanceof Date)
{
sqlCondition.append("str_to_date('"+DateTimeUtils.formatDateTime((Date)obj)+"','%Y-%m-%d %H:%i:%s')");
}else
{
sqlCondition.append(obj);
}
}
}
}
}
//拼装排序
HashMap orderMap = paramMap.get("orderBy");
if(null!=orderMap && !orderMap.isEmpty())
{
sqlCondition.append(" order by ");
int i = 0;
for (String key : orderMap.keySet()) {
sqlCondition.append(key);
sqlCondition.append(" ");
sqlCondition.append(orderMap.get(key));
i++;
if(i!=orderMap.size())
{
sqlCondition.append(",");
}
}
}
System.out.println("sql=="+sqlCondition.toString());
return sqlCondition.toString();
}
/**
* 将操作字符串转化为sql属性
* @param operator
* @return
*/
private String oper2sql(String operator){
if(operator == null || operator.equals("")){
return SqlOperEnum.EQUAL.getSql();
}else if(operator.equals("lk")){
return SqlOperEnum.LIKE.getSql();
}else if(operator.equals("kl")){
return SqlOperEnum.LIKE_LEFT.getSql();
}else if(operator.equals("kr")){
return SqlOperEnum.LIKE_RIGHT.getSql();
}else if(operator.equals("gt")){
return SqlOperEnum.GREAT_THAN.getSql();
}else if(operator.equals("ge")){
return SqlOperEnum.GREAT_EQUAL.getSql();
}else if(operator.equals("lt")){
return SqlOperEnum.LESS_THAN.getSql();
}else if(operator.equals("le")){
return SqlOperEnum.LESS_EQUAL.getSql();
}else if(operator.equals("nu")){
return SqlOperEnum.IS_NULL.getSql();
}else if(operator.equals("ne")){
return SqlOperEnum.NOT_EQUAL.getSql();
}else if(operator.equals("nn")){
return SqlOperEnum.NOT_NULL.getSql();
}else{
return SqlOperEnum.EQUAL.getSql();
}
}
//数据类型
private static enum Type {s, b, i,l,f,d,D,t} ;
private Object type2obj(String operator, String propType,String val){
if(Type.D.toString().equals(propType))
{
System.out.println(operator+"=="+propType+"--"+val);
}
Object obj = null;
if(Type.D.toString().equals(propType))
{
obj= DateTimeUtils.parseShort(val);
return obj;
}
if(Type.t.toString().equals(propType))
{
obj= DateTimeUtils.parse(val);
return obj;
}
if(operator.equals(SqlOperEnum.LIKE.getOper())){
val= "%"+val+"%";
return val;
}else if(operator.equals(SqlOperEnum.LIKE_LEFT.getOper())){
val= "%"+val;
return val;
}else if(operator.equals(SqlOperEnum.LIKE_RIGHT.getOper())){
val= val+"%";
return val;
}
if(Type.s.toString().equals(propType))
{
return val;
}
if(operator.equals(SqlOperEnum.NOT_NULL.getOper()) || operator.equals(SqlOperEnum.IS_NULL.getOper()))
{
return null;
}
//b, i,l,f,d,
if(Type.b.toString().equals(propType))
{
return Boolean.valueOf(val);
}
if(Type.i.toString().equals(propType))
{
return Integer.valueOf(val);
}
if(Type.l.toString().equals(propType))
{
return Long.valueOf(val);
}
if(Type.f.toString().equals(propType))
{
return Float.valueOf(val);
}
if(Type.d.toString().equals(propType))
{
return Double.valueOf(val);
}
return val;
}
public static void main(String[] args) {
boolean isMatch = Pattern.matches("[a-z_A-Z0-9]+@[a-z]{2}(@[sbilfDdt]{1})?", "name@lk");
System.out.println(isMatch);
}
}
对应mapper
insert into t_sys_dict(name,group_code,code,detail,status,sn)
values(#{name},#{groupCode},#{code},#{detail},#{status},#{sn})
UPDATE t_sys_dict
SET name = #{name},
group_code = #{groupCode},
code = #{code},
detail = #{detail},
status = #{status},
detail = #{detail},
sn = #{sn}
WHERE id = #{id};
DELETE FROM t_sys_dict WHERE id = #{id}
${key} ${oper} #{query.${key}[${oper}]}
${key} ${query["orderBy"][key]}
三、页面设计
后台框架页面一次性加载css和js,另打开的tab页面,只是从后台调取的html片断,不再加截css和js,节约流量,提高访问效率。
四、代码生成
读取数据库、使用freemarker做为模版统一生成增删改查、分页、查询、页面等代码。节约开发工作量。
五、字典管理
统一的字典管理设计,并在系统启动时加载到缓存,在前台使用freemaker可以直接根据key查询value.
六、权限设计
使用注解初始化功能权限、使用拦截器实现权限的控制、使用freemarker的判断把权限精确到按钮。
....越写越粗,请大家还是下载代码看看吧,欢迎批评指点拍砖。