# 代码层级介绍
该工程的分级规范和标准J2EE规范大致一致,你应当对它很熟悉了。
Controller > Service > ServiceImpl > Dao > DaoImpl
这部分内容还会穿插部分Swagger2
注解的使用方法。
提示
需要注意的是,这里不需要自己写Dao的实现层
,也就是上述红色部分
。
DAO
的实现均交给JPA
和QueryDSL
自动实现。
这里对Service接口层
存在的必要性相关的争论不做讨论,总之公司现行的规范就是多写一个文件,其实也浪费不了大家多少时间。您说是吧?
# Controller 控制器层
该层主要负责,数据的接收和响应参数的构造,另外可以负责复杂业务模块的流程控制。
代码参考:
/**
* 示例表MVC控制器类
* @author luosz@jfbrother.com 2021-05-26
*/
@ApiSort(10)
@Api(tags={"示例表接口-1.0.0-20210526"})
@Validated
@RestController("DemoTable")
@RequestMapping("/api/v1/demoTable")
public class DemoTableController {
@Autowired
private DemoTableService service;
@ApiOperationSupport(author = "luosz@jfbrother.com", order = 4, ignoreParameters = {"model.id", "model.createTime", "model.createUser", "model.updateTime", "model.updateUser"})
@ApiOperation(value = "示例表添加", notes = "示例表添加接口")
@ApiVersion(1)
@PostMapping
public Result<DemoTableModelExtend> post(@RequestBody @Valid DemoTableModelParam model) {
return ResultGenerator.genSuccessResult(ResultCode.CREATED, service.post(model));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
部分注解说明:
ApiSort
:用于控制在接口文档中的排序
Api
: tags用于负责控制创建接口的组,一组里通常含有增删改查等接口
Validated
: 用于开启参数校验,配合方法中的@Valid
注解
RestController
: 可不写,如果写了就要保证整个API文档中不可重复。一般代码生成器会根据你的表名自动命名。
ApiOperationSupport
: 用于描述接口的作者,在组中的排序,和文档中需要忽略的字段
ApiOperation
: 用于描述这个接口的名称
ApiVersion
: 用于未来接口版本升级迭代(需要同时保留新旧版本接口时用,一般的项目不会有这种需求,除非是多客户端情况下,前端实现不同步时),暂时可全部写初始化为@ApiVersion(1)
警告
接口URI定义请尽量遵守Restful
规范。
举几个经典案例:
Method | URI | 描述 |
---|---|---|
GET | /user/{id} | 获取单个用户 |
GET | /user | 获取多个用户 |
GET | /user/{id}/permit | 获取单个用户的所有权限 |
POST | /user/{id} | 新增/修改单个用户信息 |
DELETE | /user/{id} | 删除单个用户 |
POST | /user/{id}/name | 修改单个用户的名字 |
也有部分特殊的业务场景,可能不能完全照搬Restful
规范。这时候可以进行适当变种。
但不要严重偏离规范,尽量保证Method
描述动词,URI
描述名词。
这样有助于前端开发者更好的阅读你的API文档。
# Service层
该层主要负责具体业务逻辑的实现,事务控制也是在这一层实现。
注意
Service必须继承统一的父类com.jfbrother.baseserver.service.impl.BaseServiceImpl
点击查看父类的实现
package com.jfbrother.baseserver.service.impl;
import com.jfbrother.baseserver.common.Constant;
import com.jfbrother.baseserver.dao.BaseRepository;
import com.jfbrother.baseserver.jpa.base.BaseEntity;
import com.jfbrother.baseserver.service.BaseService;
import com.jfbrother.baseserver.utils.CopyUtils;
import com.jfbrother.baseserver.utils.ReflectUtils;
import com.jfbrother.baseserver.utils.StringUtils;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.EntityPathBase;
import com.querydsl.core.types.dsl.StringPath;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author luosz
*/
@NoRepositoryBean
@Slf4j
public class BaseServiceImpl implements BaseService {
@PersistenceContext
protected EntityManager entityManager;
/**
* JPA查询工厂
*/
protected JPAQueryFactory queryFactory;
/**
* 上传路径
*/
@Value("${system.file.upload}")
protected String uploadPath;
/**
* 查询全部数据时,数据的上限
*/
@Value("${system.api.listAll.max}")
protected Integer max;
@PostConstruct
public void initFactory() {
queryFactory = new JPAQueryFactory(entityManager);
}
/**
* 删除子表数据
*
* @param entityPath 子表QueryDSL对象
* @param parentKey 父节点字段名称
* @param parentIds 父节点ids
* @param <T> 子表的JPA对象类型
* @param <E> QueryDSL对象类型
*/
protected <T extends BaseEntity, E extends EntityPathBase<T>> void removeChildren(E entityPath
, String parentKey, List<String> parentIds) throws Exception {
parentKey = StringUtils.isEmpty(parentKey) ? "pid" : parentKey;
try {
StringPath idPath = (StringPath) ReflectUtils.getFiled(entityPath, parentKey);
queryFactory.delete(entityPath).where(idPath.in(parentIds)).execute();
} catch (Exception e) {
throw e;
}
}
/**
* 保存子表数据
*
* @param news 最新的子表数据
* @param entityPath QueryDsl对象
* @param id 主表主键id
* @param parentKey 子表关联主表的字段名称
* @param repository 子表的JPA操作对象
* @param clazz 子表的POJO类型
* @param <T>
* @param <N>
* @param <E>
* @return
*/
protected <T extends BaseEntity, N, E extends EntityPathBase<T>> List<T> saveChildren(
List<N> news, E entityPath, String id, String parentKey, BaseRepository<T> repository, Class<T> clazz) throws Exception {
try {
parentKey = StringUtils.isEmpty(parentKey) ? "pid" : parentKey;
StringPath pidPath = (StringPath) ReflectUtils.getFiled(entityPath, parentKey);
StringPath idPath = (StringPath) ReflectUtils.getFiled(entityPath, "id");
if (news != null && news.size() > 0) {
//查询原来的所有子表
List<T> olds = queryFactory.selectFrom(entityPath)
.where(pidPath.eq(id))
.fetch();
//新增的
List<N> adds = new ArrayList<>();
//需要删除的id
List<String> removes = new ArrayList<>();
//需要更新的
List<N> updates = new ArrayList<>();
outer:
for (N _new : news) {
for (T _old : olds) {
if (ReflectUtils.getFiled(_old, "id").equals(ReflectUtils.getFiled(_new, "id"))) {
//如果在原子表中能找到,则说明是需要更新的
updates.add(_new);
continue outer;
}
}
//如果在原子表中找不到,说明是新增的
adds.add(_new);
}
outer:
for (T _old : olds) {
for (N _new : news) {
if (ReflectUtils.getFiled(_old, "id").equals(ReflectUtils.getFiled(_new, "id"))) {
continue outer;
}
}
removes.add(ReflectUtils.getFiled(_old, "id").toString());
}
if (removes.size() > 0) {
//按主键删除
queryFactory.delete(entityPath)
.where(idPath.in(removes)).execute();
}
//更新
for (N update : updates) {
//设置主表id
ReflectUtils.setFiled(update, parentKey, id);
T updatePojo = repository.findById(ReflectUtils.getFiled(update, "id").toString()).get();
CopyUtils.copyBean(update, updatePojo, Constant.UPDATE_IGNORE_FIELDS);
repository.save(updatePojo);
}
//新增
for (N add : adds) {
//设置主表id
ReflectUtils.setFiled(add, parentKey, id);
T addPojo = CopyUtils.copyBean(add, clazz, "id");
repository.save(addPojo);
}
} else {
//如果用户没有任何的子表信息传过来,则删除全部的子表信息
queryFactory.delete(entityPath)
.where(pidPath.eq(id)).execute();
}
//查询最新的子数据
return queryFactory.selectFrom(entityPath)
.where(pidPath.eq(id))
.fetch();
} catch (Exception e) {
throw e;
}
}
/**
* 设置分页参数
*
* @param jpaQuery
* @param pageable
* @param entityPathBase
*/
protected void setPageParams(JPAQuery jpaQuery, Pageable pageable, EntityPathBase entityPathBase) {
OrderSpecifier[] orderSpecifierArray = getOrderSpecifiers(pageable, entityPathBase);
jpaQuery
.offset(pageable.getOffset())
.limit(pageable.getPageSize());
if (orderSpecifierArray.length > 0) {
jpaQuery.orderBy(orderSpecifierArray);
}
}
protected void setPageParams(JPAQuery jpaQuery, Pageable pageable, EntityPathBase entityPathBase, Map<String, Expression> expressExpand) {
OrderSpecifier[] orderSpecifierArray = getOrderSpecifiers(pageable, entityPathBase, expressExpand);
jpaQuery
.offset(pageable.getOffset())
.limit(pageable.getPageSize());
if (orderSpecifierArray.length > 0) {
jpaQuery.orderBy(orderSpecifierArray);
}
}
/**
* 将spring MVC的排序对象转化为queryDSL的排序对象
*
* @param pageable
* @param entityPathBase
* @return
*/
protected OrderSpecifier[] getOrderSpecifiers(Pageable pageable, EntityPathBase entityPathBase) {
return getOrderSpecifiers(pageable.getSort(), entityPathBase, null);
}
/**
* 将spring MVC的排序对象转化为queryDSL的排序对象
*
* @param pageable
* @param entityPathBase
* @return
*/
protected OrderSpecifier[] getOrderSpecifiers(Pageable pageable, EntityPathBase entityPathBase, Map<String, Expression> expressExpand) {
return getOrderSpecifiers(pageable.getSort(), entityPathBase, expressExpand);
}
/**
* 将spring MVC的排序对象转化为queryDSL的排序对象
*
* @param sort
* @param entityPathBase
* @return
*/
protected OrderSpecifier[] getOrderSpecifiers(Sort sort, EntityPathBase entityPathBase) {
return getOrderSpecifiers(sort, entityPathBase, null);
}
protected OrderSpecifier[] getOrderSpecifiers(Sort sort, EntityPathBase entityPathBase, Map<String, Expression> expressExpand) {
Iterator<Sort.Order> iterator = sort.iterator();
List<OrderSpecifier> listOrderSpecifier = new ArrayList<>();
outer:
while (iterator.hasNext()) {
Sort.Order _order = iterator.next();
if (expressExpand != null) {
String property = _order.getProperty();
for (Map.Entry<String, Expression> expressionEntry : expressExpand.entrySet()) {
String key = expressionEntry.getKey();
Expression value = expressionEntry.getValue();
if (property.equals(key)) {
//如果能匹配到,则使用key进行排序
OrderSpecifier<Integer> order = new OrderSpecifier(
_order.getDirection().isAscending() ? Order.ASC : Order.DESC, value
);
listOrderSpecifier.add(order);
//当匹配到直接进入下一个属性的判断
continue outer;
}
}
}
try {
Object filed = ReflectUtils.getFiled(entityPathBase, _order.getProperty());
if (filed == null) {
log.warn("您的排序参数{{},{}}可能有误,请检查!", _order.getProperty(), _order.getDirection());
} else {
OrderSpecifier<Integer> order = new OrderSpecifier(
_order.getDirection().isAscending() ? Order.ASC : Order.DESC,
(Expression) filed
);
listOrderSpecifier.add(order);
}
} catch (Exception e) {
log.warn("您的排序参数可能有误,请检查!");
}
}
OrderSpecifier[] orderSpecifierArray = new OrderSpecifier[listOrderSpecifier.size()];
listOrderSpecifier.toArray(orderSpecifierArray);
return orderSpecifierArray;
}
}
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# BaseServiceImpl.java
BaseServiceImpl.java
最重要的工作,就是帮助你创建了queryFactory
。该对象是使用QueryDSL
的工厂类,所有的sql语句的定义均是由它开始创建。内置统一的翻页和排序设置逻辑。因为
SpringMVC
的翻页和排序参数默认通过Pageable
进行传递。和QueryDSL
要求的翻页和排序参数的使用不尽相同,一般可使用
protected void setPageParams(JPAQuery jpaQuery, Pageable pageable, EntityPathBase entityPathBase)
方法进行翻页参数和排序参数的设置jpaQuery
就是queryFactory
对象创建后SQL的中间产物,entityPathBase
一般为QueryDSL
的操作对象QXxxYyy
对象实例。最常见的例子如下:
setPageParams(jpaQuery, pageable, qSysLogAccess);
1内置常见子表处理能力,暂不详述。
# DO,DTO,VO相互转换
# 拷贝方式:
现阶段,直接使用CopyUtils.copyBean
方法进行属性的拷贝。后期将使用MapStruct
来统一控制转换逻辑。
# 关于POJO对象的命名规范(现阶段和后期):
在控制器层接参的对象取名为XxxxModelParam
,将来会统一为XxxxDTO
;
持久层的对象取名为Xxxx
,将来会统一为XxxxDO
;
在Service层回传的对象取名为XxxxModelExtend
,将来会统一为XxxxVO
;
在控制器层专用于查询封装的对象取名为XxxxModelSearch
,将来会统一为XxxxQuery
;
警告
永远不要将异常原因夹杂在你的响应对象内,如果发生了异常,请封装成
com.jfbrother.baseserver.core.ServiceException
直接向外抛出。全局异常管理
会自动帮你解决并封装合适的响应。例如:
if (StringUtils.isEmpty(id)) { throw new ServiceException(ResultCode.FORBIDDEN, "This method does not support data addition."); }
1
2
3事务注解
@Transactional
必须设置为@Transactional(rollbackFor = Exception.class)
防止运行时异常
RuntimeException
回滚失败。
# Dao层
其实在现行的Spring框架中,所有操作数据的持久化层均被定义成repository
,即仓库
的意思。
在分层结构中称呼为Dao,主要是为了方便大多数的小伙伴理解。
所以虽说是Dao层,但是对象类名为XxxxRepository
。
正常来说,该类中不允许写任何实现。
比如:
/**
* 示例表数据库操作对象
* @author luosz@jfbrother.com 2021-05-26
*/
@Repository
public interface DemoTableRepository extends BaseRepository<DemoTable> {
}
2
3
4
5
6
7
8
建议
你如果看过很多JPA
的文章,对于复杂查询,很多博主会将自定义的查询方法定义在这里。
但是现在我们有了QueryDSL
后,已经可以实现多表复杂查询了,我个人不建议在这边定义方法。
因为JPQL
虽说是面向对象的SQL语句,但他本质上还是一堆字符串。
而字符串本身的提示功能,编排能力,纠错能力是比较弱的。
还有一种就是基于命名规则的简单查询。如List<Student> findByNameAndSex(String name,Integer sex)
但是总体来说不够灵活,不够直观。
比方说你要在现有的方法上加一个参数,那就得修改方法名称和方法参数。远没有QueryDSL
中直接添加一个.and()
方法来的高效。