开发工作中,我使用过很多框架,最近使用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<E extends EntitySupport> { /** * 保存 * @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<E> findAll(); /** * 根据参数查询列表 * @param param * @return */ public List<E> findList(XJJParameter param); /** * 根据某属性值数组查询列表 * @param property * @param objArr * @return */ public List<E> 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<E extends EntitySupport> implements XjjService<E> { @Autowired private CommonDao commonDao; public XjjServiceSupport(){ } public abstract XjjDAO<E> 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<E> findAll() { return getDao().findAll(); } public List<E> 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<E> 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<E> 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<E extends EntitySupport> implements XjjService<E> { @Autowired private CommonDao commonDao; public XjjServiceSupport(){ } public abstract XjjDAO<E> 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<E> findAll() { return getDao().findAll(); } public List<E> 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<E> 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<E> 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<DictItem> implements DictService{ // 注入Service依赖 @Autowired private DictDao dictDao; @Override public XjjDAO<DictItem> getDao() { return dictDao; } }
二、MyBaties查询
不同表的查询、分页逻辑大概也是一样的,XJJ做了一接收参数的封装,然后使用MyBaties的foreach语句动态拼装sql代码,这样查询条件直接可以从前台就可以简单的注入到mapper中。下面是一个查询条件,查询code等于输入值的.
<@querygroup title='编码'> <input type="search" name="query.code@eq@s" class="form-control input-sm" placeholder="请输入编码" aria-controls="dynamic-table"> </@querygroup>
接收参数的类如下:
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<String,HashMap<String,Object>> paramMap = new HashMap<String,HashMap<String,Object>>(); public HashMap<String,HashMap<String,Object>> getQueryMap() { return paramMap; } public void clear() { paramMap.clear(); } public boolean containsKey(String key) { key = StringUtils.toUnderScoreCase(key); return paramMap.containsKey(key); } public HashMap<String,Object> 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<String,Object> 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<String> 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<String,Object> param = paramMap.get(propName); if(null==param) { param = new HashMap<String,Object>(); } 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<String,Object> map = new HashMap<String,Object>(); map.put(propName, "asc"); paramMap.put("orderBy",map); } /** * 添加降序字段 * @param propName */ public void addOrderByDesc(String propName) { propName=StringUtils.toUnderScoreCase(propName); HashMap<String,Object> map = new HashMap<String,Object>(); map.put(propName, "desc"); paramMap.put("orderBy",map); } public HashMap<String,Object> remove(String arg0) { return paramMap.remove(arg0); } public int size() { return paramMap.size(); } public Collection<HashMap<String,Object>> 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<String,Object> 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
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xjj.sys.dict.dao.DictDao"> <select id="getById" resultType="DictItem" parameterType="long"> SELECT * FROM t_sys_dict WHERE id = #{id} </select> <select id="findPage" resultType="DictItem"> SELECT * FROM t_sys_dict <include refid="com.xjj.framework.dao.CommonDao.queryParam"/> <include refid="com.xjj.framework.dao.CommonDao.queryOrder"/> LIMIT #{offset}, #{limit} </select> <select id="findAll" resultType="DictItem"> SELECT * FROM t_sys_dict </select> <select id="findList" resultType="DictItem"> SELECT * FROM t_sys_dict <include refid="com.xjj.framework.dao.CommonDao.queryParam"/> <include refid="com.xjj.framework.dao.CommonDao.queryOrder"/> </select> <insert id="save" useGeneratedKeys="true" keyProperty="id" parameterType="com.xjj.sys.dict.entity.DictItem"> insert into t_sys_dict(name,group_code,code,detail,status,sn) values(#{name},#{groupCode},#{code},#{detail},#{status},#{sn}) </insert> <update id="update" parameterType="DictItem"> UPDATE t_sys_dict SET name = #{name}, group_code = #{groupCode}, code = #{code}, detail = #{detail}, status = #{status}, detail = #{detail}, sn = #{sn} WHERE id = #{id}; </update> <delete id="delete" parameterType="long"> DELETE FROM t_sys_dict WHERE id = #{id} </delete> <select id="getCount" resultType="java.lang.Integer"> select count(id) from t_sys_dict <include refid="com.xjj.framework.dao.CommonDao.queryParam"/> <include refid="com.xjj.framework.dao.CommonDao.queryOrder"/> </select> </mapper>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xjj.framework.dao.CommonDao"> <sql id="queryParam"> <foreach collection="query.keys" item="key" open="where" separator="and"> <if test="'orderBy' != key"> <foreach collection="query[key].keys" item="oper" separator="and"> ${key} ${oper} #{query.${key}[${oper}]} </foreach> </if> </foreach> </sql> <sql id="queryOrder"> <if test="null != query['orderBy']"> <foreach collection="query['orderBy'].keys" item="key" open="order by" separator=","> ${key} ${query["orderBy"][key]} </foreach> </if> </sql> <select id="checkUniqueVal" resultType="java.lang.Integer"> select count(id) from ${tableName} where ${columnName}=#{columnVal} <if test="null != id"> and and id !=#{id} </if> </select> </mapper>
三、页面设计
后台框架页面一次性加载css和js,另打开的tab页面,只是从后台调取的html片断,不再加截css和js,节约流量,提高访问效率。
四、代码生成
读取数据库、使用freemarker做为模版统一生成增删改查、分页、查询、页面等代码。节约开发工作量。
五、字典管理
统一的字典管理设计,并在系统启动时加载到缓存,在前台使用freemaker可以直接根据key查询value.
六、权限设计
使用注解初始化功能权限、使用拦截器实现权限的控制、使用freemarker的判断把权限精确到按钮。
....越写越粗,请大家还是下载代码看看吧,欢迎批评指点拍砖。