SSM搭建企业级快速开发框架(SpringMVC+Spring+MyBatis)

      开发工作中,我使用过很多框架,最近使用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的判断把权限精确到按钮。

....越写越粗,请大家还是下载代码看看吧,欢迎批评指点拍砖。


猜你喜欢

转载自blog.csdn.net/jlsdzhj/article/details/80354742
今日推荐