tags: [小小商城, SSM]
categories: [技术实战]


提要

  本文是 小小商城-SSM版的 细节详解系列 之一,项目 github:https://github.com/xenv/S-mall-ssm 本文代码大部分在 github 中 可以找到。

  这篇文章其实和 SSH开发 | 配合Hibernate,通过泛型实现 BaseService ,抽象增改删查方法 大致一样,只是具体实现有一点区别。

  在 SSM 开发中,Service层会有许多重复的,调用 mapper 层的,增改删查的方法。对于重复的方法,我们可以用一个 BaseService 抽象出来。如图所示:

SSM开发 - 配合Mybatis,通过泛型实现 BaseService ,抽象增改删查方法 - 图1   普通 Service 的增改删查操作统一被抽象到 BaseService,普通Service继承BaseService后,只需实现少量独有的代码即可

  这样,在 Controller 层调用的时候,只需直接 categoryService.get(id) 就可以完成获取操作。

具体实现

  BaseService实现的难点在于,不知道 继承它的 普通 Service 对应的 mapper和example 是哪个,从而不能获取到相应的 mapper 类(mybatis动态代理过之后的)和example类,那么,怎么来解决这个问题呢?

  其实很简单,利用 泛型 ,子类在继承父类的时候实现泛型,初始化时,父类就可以读取到子类的泛型信息,从而实现了 子类 向 父类 传 数据。

  父类根据子类携带的实体类信息,把mapper接口变成 mapper 实体,子类在泛型继承的时候,就自动会调用对应mapper,从而做到了抽象的效果。

  在 小小商城 项目中,核心实现是这两个文件,BaseServiceImpl.javaService4DAOImpl.java下面,我会一步步教大家实现。

1.初始化时读取子类的泛型信息,获取 mapper实体类 和 example 类

  完整实现:Service4DAOImpl.java,如果不使用通用 mapper (SSM开发 | 实现 Mybatis 的通用 Mapper),则注入的是mybatis 的 SqlSessionTemplate而不是MapperFactory,注入SqlSessionTemplate需要配置 Spring文件 ,具体可参见前面的链接

  1. public class Service4DAOImpl<M, E> implements Service4DAO, InitializingBean {
  2. @Resource
  3. private MapperFactory mapperFactory;
  4. Mapper mapper;
  5. @Override
  6. /*
  7. 加载完 Service 后执行,获取对应 Service 和 Mapper
  8. */
  9. public void afterPropertiesSet() throws Exception {
  10. mapper = getMapper();
  11. }
  12. public Mapper getMapper() throws Exception {
  13. // 读取泛型第一个 M 的类型,
  14. ParameterizedType t = (ParameterizedType) (getClass().getGenericSuperclass());
  15. Class mapperClass = null;
  16. if (t != null) {
  17. mapperClass = (Class) t.getActualTypeArguments()[0];
  18. }
  19. return getMapper(mapperClass);
  20. }
  21. public Mapper getMapper(Class mapperInterface) throws Exception {
  22. return mapperFactory.getMapper(mapperInterface);
  23. }
  24. public BaseExample getExample() throws Exception {
  25. ParameterizedType t = (ParameterizedType) (getClass().getGenericSuperclass());
  26. Class exampleClass = null;
  27. if (t != null) {
  28. exampleClass = (Class) t.getActualTypeArguments()[1];
  29. }
  30. return (BaseExample) exampleClass.newInstance();
  31. }
  32. }

  这里其实暗藏一个大坑,就是 SqlSessionTemplate 的注入是在类初始化之后的,这样我们就没有办法使用在构造函数中使用 SqlSessionTemplate,正确的做法是 afterPropertiesSet(…)函数,Spring会自动在全部注入完成后调用这个函数,往这个Service里面注入对应的mapper。Example也是同理的。

2.实现 BaseService ,抽象增改删查方法

  1. public class BaseServiceImpl<M, E> extends Service4DAOImpl<M, E> implements BaseService {
  2. @Override
  3. public List list(Object... paramAndObjects) throws Exception {
  4. BaseExample example = getExample();
  5. Object criteria = example.createCriteria();
  6. if (paramAndObjects.length % 2 != 0) {
  7. return null;
  8. }
  9. Pagination pagination = null;
  10. //默认按id排序
  11. example.setOrderByClause("id desc");
  12. //默认对查询进行两次遍历查询
  13. int depth = 2;
  14. for (int i = 0; i < paramAndObjects.length; i += 2) {
  15. if (paramAndObjects[i].equals("order") && paramAndObjects[i + 1] instanceof String) {
  16. example.setOrderByClause(paramAndObjects[i + 1].toString());
  17. continue;
  18. }
  19. if (paramAndObjects[i].equals("depth") && paramAndObjects[i + 1] instanceof Integer) {
  20. depth = (Integer) paramAndObjects[i + 1];
  21. continue;
  22. }
  23. if (paramAndObjects[i].toString().contains("_like") && paramAndObjects[i + 1] instanceof String) {
  24. String column = StringUtils.replace(paramAndObjects[i].toString(), "_like", "");
  25. criteria.getClass()
  26. .getMethod("and" + StringUtils.capitalize(column) + "Like", String.class)
  27. .invoke(criteria, "%" + paramAndObjects[i + 1].toString() + "%");
  28. continue;
  29. }
  30. if (paramAndObjects[i].toString().contains("_gt") && paramAndObjects[i + 1] instanceof Integer) {
  31. String column = StringUtils.replace(paramAndObjects[i].toString(), "_gt", "");
  32. criteria.getClass()
  33. .getMethod("and" + StringUtils.capitalize(column) + "GreaterThan", Integer.class)
  34. .invoke(criteria, paramAndObjects[i + 1]);
  35. continue;
  36. }
  37. if (paramAndObjects[i].equals("pagination") && paramAndObjects[i + 1] instanceof Pagination) {
  38. pagination = (Pagination) paramAndObjects[i + 1];
  39. continue;
  40. }
  41. criteria.getClass()
  42. .getMethod("and" + StringUtils.capitalize(paramAndObjects[i].toString()) + "EqualTo"
  43. , paramAndObjects[i + 1].getClass())
  44. .invoke(criteria, paramAndObjects[i + 1]);
  45. }
  46. List list;
  47. if (pagination != null) {
  48. PageHelper.offsetPage(pagination.getStart(), pagination.getCount());
  49. list = mapper.selectByExample(example, depth);
  50. pagination.setTotal((int) new PageInfo<>(list).getTotal());
  51. } else {
  52. list = mapper.selectByExample(example, depth);
  53. }
  54. return list;
  55. }
  56. @Override
  57. public Integer add(BasePOJO object) throws Exception {
  58. return mapper.insert(object);
  59. }
  60. @Override
  61. public void update(BasePOJO object) throws Exception {
  62. mapper.updateByPrimaryKey(object);
  63. }
  64. @Override
  65. public BasePOJO get(int id) throws Exception {
  66. BasePOJO object = (BasePOJO) mapper.selectByPrimaryKey(id);
  67. if (object == null) {
  68. throw new NoSuchObjectException("访问的id不存在或已经被删除");
  69. }
  70. return object;
  71. }
  72. /**
  73. * @see BaseService
  74. */
  75. @Override
  76. public void delete(BasePOJO object) throws Exception {
  77. object.setDeleteAt(new Date());
  78. mapper.updateByPrimaryKey(object);
  79. }
  80. }

  这里节选了项目里的文件的一部分代码,我们先看 update(Object object) ,我们直接调用 mapper 的 update 方法即可

  在list方法中,我又对example的使用进行进一步抽象,这样,我们在调用的时候,就可以顺带指定分页、顺序、搜索条件等,更加方便

3.普通Service 附带 上 泛型 信息,继承 BaseService

  以ProductService为例:

  1. @Service
  2. public class ProductServiceImpl extends BaseServiceImpl<ProductMapper,ProductExample> implements ProductService {
  3. }

  无需再写任何代码,只需要加上泛型信息即可获取完整的BaseService的所有功能。

4. 在 Controller 中调用

  1. 例如,注入ProductService之后,直接可以调用 BaseService list 方法
  1. productService.list("cid",category.getId(),"order",handleSort(sort),"stock_gt",0);

OK,大功告成。