提要

本文是 小小商城-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.javaService4DAOImpl.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
/*
加载完 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 ,抽象增改删查方法

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;
//默认按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;
}

@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;
}

/**
* @see BaseService
*/
@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,大功告成。