tags: [小小商城, SSM]
categories: [技术实战]
提要
本文是 小小商城-SSM版的 细节详解系列 之一,项目 github:https://github.com/xenv/S-mall-ssm 本文代码大部分在 github 中 可以找到。
这篇文章其实和 SSH开发 | 配合Hibernate,通过泛型实现 BaseService ,抽象增改删查方法 大致一样,只是具体实现有一点区别。
在 SSM 开发中,Service层会有许多重复的,调用 mapper 层的,增改删查的方法。对于重复的方法,我们可以用一个 BaseService 抽象出来。如图所示:
普通 Service 的增改删查操作统一被抽象到 BaseService,普通Service继承BaseService后,只需实现少量独有的代码即可
这样,在 Controller 层调用的时候,只需直接 categoryService.get(id) 就可以完成获取操作。
具体实现
BaseService实现的难点在于,不知道 继承它的 普通 Service 对应的 mapper和example 是哪个,从而不能获取到相应的 mapper 类(mybatis动态代理过之后的)和example类,那么,怎么来解决这个问题呢?
其实很简单,利用 泛型 ,子类在继承父类的时候实现泛型,初始化时,父类就可以读取到子类的泛型信息,从而实现了 子类 向 父类 传 数据。
父类根据子类携带的实体类信息,把mapper接口变成 mapper 实体,子类在泛型继承的时候,就自动会调用对应mapper,从而做到了抽象的效果。
在 小小商城 项目中,核心实现是这两个文件,BaseServiceImpl.java 和 Service4DAOImpl.java下面,我会一步步教大家实现。
1.初始化时读取子类的泛型信息,获取 mapper实体类 和 example 类
完整实现:Service4DAOImpl.java,如果不使用通用 mapper (SSM开发 | 实现 Mybatis 的通用 Mapper),则注入的是mybatis 的 SqlSessionTemplate而不是MapperFactory,注入SqlSessionTemplate需要配置 Spring文件 ,具体可参见前面的链接
public class Service4DAOImpl<M, E> implements Service4DAO, InitializingBean {@Resourceprivate MapperFactory mapperFactory;Mapper mapper;@Override/*加载完 Service 后执行,获取对应 Service 和 Mapper*/public void afterPropertiesSet() throws Exception {mapper = getMapper();}public Mapper getMapper() throws Exception {// 读取泛型第一个 M 的类型,ParameterizedType t = (ParameterizedType) (getClass().getGenericSuperclass());Class mapperClass = null;if (t != null) {mapperClass = (Class) t.getActualTypeArguments()[0];}return getMapper(mapperClass);}public Mapper getMapper(Class mapperInterface) throws Exception {return mapperFactory.getMapper(mapperInterface);}public BaseExample getExample() throws Exception {ParameterizedType t = (ParameterizedType) (getClass().getGenericSuperclass());Class exampleClass = null;if (t != null) {exampleClass = (Class) t.getActualTypeArguments()[1];}return (BaseExample) exampleClass.newInstance();}}
这里其实暗藏一个大坑,就是 SqlSessionTemplate 的注入是在类初始化之后的,这样我们就没有办法使用在构造函数中使用 SqlSessionTemplate,正确的做法是 afterPropertiesSet(…)函数,Spring会自动在全部注入完成后调用这个函数,往这个Service里面注入对应的mapper。Example也是同理的。
2.实现 BaseService ,抽象增改删查方法
public class BaseServiceImpl<M, E> extends Service4DAOImpl<M, E> implements BaseService {@Overridepublic List list(Object... paramAndObjects) throws Exception {BaseExample example = getExample();Object criteria = example.createCriteria();if (paramAndObjects.length % 2 != 0) {return null;}Pagination pagination = null;//默认按id排序example.setOrderByClause("id desc");//默认对查询进行两次遍历查询int depth = 2;for (int i = 0; i < paramAndObjects.length; i += 2) {if (paramAndObjects[i].equals("order") && paramAndObjects[i + 1] instanceof String) {example.setOrderByClause(paramAndObjects[i + 1].toString());continue;}if (paramAndObjects[i].equals("depth") && paramAndObjects[i + 1] instanceof Integer) {depth = (Integer) paramAndObjects[i + 1];continue;}if (paramAndObjects[i].toString().contains("_like") && paramAndObjects[i + 1] instanceof String) {String column = StringUtils.replace(paramAndObjects[i].toString(), "_like", "");criteria.getClass().getMethod("and" + StringUtils.capitalize(column) + "Like", String.class).invoke(criteria, "%" + paramAndObjects[i + 1].toString() + "%");continue;}if (paramAndObjects[i].toString().contains("_gt") && paramAndObjects[i + 1] instanceof Integer) {String column = StringUtils.replace(paramAndObjects[i].toString(), "_gt", "");criteria.getClass().getMethod("and" + StringUtils.capitalize(column) + "GreaterThan", Integer.class).invoke(criteria, paramAndObjects[i + 1]);continue;}if (paramAndObjects[i].equals("pagination") && paramAndObjects[i + 1] instanceof Pagination) {pagination = (Pagination) paramAndObjects[i + 1];continue;}criteria.getClass().getMethod("and" + StringUtils.capitalize(paramAndObjects[i].toString()) + "EqualTo", paramAndObjects[i + 1].getClass()).invoke(criteria, paramAndObjects[i + 1]);}List list;if (pagination != null) {PageHelper.offsetPage(pagination.getStart(), pagination.getCount());list = mapper.selectByExample(example, depth);pagination.setTotal((int) new PageInfo<>(list).getTotal());} else {list = mapper.selectByExample(example, depth);}return list;}@Overridepublic Integer add(BasePOJO object) throws Exception {return mapper.insert(object);}@Overridepublic void update(BasePOJO object) throws Exception {mapper.updateByPrimaryKey(object);}@Overridepublic BasePOJO get(int id) throws Exception {BasePOJO object = (BasePOJO) mapper.selectByPrimaryKey(id);if (object == null) {throw new NoSuchObjectException("访问的id不存在或已经被删除");}return object;}/*** @see BaseService*/@Overridepublic void delete(BasePOJO object) throws Exception {object.setDeleteAt(new Date());mapper.updateByPrimaryKey(object);}}
这里节选了项目里的文件的一部分代码,我们先看 update(Object object) ,我们直接调用 mapper 的 update 方法即可
在list方法中,我又对example的使用进行进一步抽象,这样,我们在调用的时候,就可以顺带指定分页、顺序、搜索条件等,更加方便
3.普通Service 附带 上 泛型 信息,继承 BaseService
以ProductService为例:
@Servicepublic class ProductServiceImpl extends BaseServiceImpl<ProductMapper,ProductExample> implements ProductService {}
无需再写任何代码,只需要加上泛型信息即可获取完整的BaseService的所有功能。
4. 在 Controller 中调用
例如,注入ProductService之后,直接可以调用 BaseService 的 list 方法
productService.list("cid",category.getId(),"order",handleSort(sort),"stock_gt",0);
OK,大功告成。
