提要
本文是 小小商城-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 文件 ,具体可参见前面的链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class Service4DAOImpl<M, E> implements Service4DAO, InitializingBean {
@Resource private MapperFactory mapperFactory;
Mapper mapper;
@Override
public void afterPropertiesSet() throws Exception { mapper = getMapper(); }
public Mapper getMapper() throws Exception { 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 ,抽象增改删查方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| public class BaseServiceImpl<M, E> extends Service4DAOImpl<M, E> implements BaseService {
@Override public List list(Object... paramAndObjects) throws Exception { BaseExample example = getExample(); Object criteria = example.createCriteria(); if (paramAndObjects.length % 2 != 0) { return null; } Pagination pagination = null; 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; }
@Override public Integer add(BasePOJO object) throws Exception { return mapper.insert(object); }
@Override public void update(BasePOJO object) throws Exception { mapper.updateByPrimaryKey(object); }
@Override public BasePOJO get(int id) throws Exception { BasePOJO object = (BasePOJO) mapper.selectByPrimaryKey(id); if (object == null) { throw new NoSuchObjectException("访问的id不存在或已经被删除"); } return object; }
@Override public void delete(BasePOJO object) throws Exception { object.setDeleteAt(new Date()); mapper.updateByPrimaryKey(object); } }
|
这里节选了项目里的文件的一部分代码,我们先看 update(Object object) ,我们直接调用 mapper 的 update 方法即可
在 list 方法中,我又对 example 的使用进行进一步抽象,这样,我们在调用的时候,就可以顺带指定分页、顺序、搜索条件等,更加方便
3.普通 Service 附带 上 泛型 信息,继承 BaseService
以 ProductService 为例:
1 2 3 4
| @Service public class ProductServiceImpl extends BaseServiceImpl<ProductMapper,ProductExample> implements ProductService {
}
|
无需再写任何代码,只需要加上泛型信息即可获取完整的 BaseService 的所有功能。
4. 在 Controller 中调用
例如,注入 ProductService 之后,直接可以调用 BaseService 的 list 方法
1
| productService.list("cid",category.getId(),"order",handleSort(sort),"stock_gt",0);
|
OK,大功告成。