So是什么
So,即Select Object,为⽅便查询创造的Dto对象,为了与Dto做区分,以So命名
基于So对象,开发了⼀个辅助查询数据库的框架,⽅便进⾏单表查询。
因此本⽂中的So,通常指这个查询框架。
为什么使⽤So
我们新增⼀张数据库表(如:questionnaire_answer)时,如果作为服务端(如:courselbb-platform项⽬),⼀般需要创建如下对象:
QuestionnaireAnswerEntity
IQuestionnaireAnswerDao
QuestionnaireAnswerDaoImpl
IQuestionnaireAnswerBiz
QuestionnaireAnswerBizImpl
IQuestionnaireAnswerService
QuestionnaireAnswerServiceImpl
创建对象的同时,biz和service层还需要增加相关⽅法,提供给客⼾端使⽤。
以⼀个分⻚查询举例,⼤致需要增加如下编码:
// -- 1.service
@SwiftInterface
QueryResultsDto<QuestionnaireAnswerDto> getQuestionnaireAnswer(Integer curPage, Integer pageSize, Long compId, Long studyPlanId, Long questionnaireId) throws BizException;
// -- 2.serviceImpl
@Autowired
private IQuestionnaireAnswerBiz questionnaireAnswerBiz;
@Override
public QueryResultsDto<QuestionnaireAnswerDto> getQuestionnaireAnswer(Integer curPage, Integer pageSize,Long compId, Long studyPlanId, Long questionnaireId) throws BizException {
return questionnaireAnswerBiz.getQuestionnaireAnswer(curPage, pageSize, compId, studyPlanId, questionnaireId);
}
// -- 3.biz
QueryResultsDto<QuestionnaireAnswerDto> getQuestionnaireAnswer(Integer curPage, Integer pageSize, Long compId, Long studyPlanId, Long questionnaireId) throws BizException;
// -- 4.bizImpl
@Override
public QueryResultsDto<QuestionnaireAnswerDto> getQuestionnaireAnswer(Integer curPage, Integer pageSize,
Long compId, Long studyPlanId, Long questionnaireId) throws BizException {
// 这⾥⽤了ConditionUtils⼯具简化QueryCondition的⽣成(该⼯具也由本⼈开发)
QueryCondition queryCondition = ConditionUtils.createPagedQueryCondition(curPage,pageSize, condition-> {
if(ValidUtils.isValid(questionnaireId)){
condition.appendAnd("questionnaire_id", questionnaireId);
}
condition.appendAnd("comp_id", compId);
});
queryCondition.setHasCount(true);
List<QuestionnaireAnswerEntity> entityList =
questionnaireAnswerDao.selectEntitiesByQueryCondition(queryCondition);
// ...忽略后续业务操作...
}
服务端完成上述编码后,客⼾端(如:lbb2c和lbb2b项⽬)才能够采⽤如下⽅式进⾏调⽤:
IQuestionnaireAnswerService questionnaireAnswerService = RpcFactory.getService(IQuestionnaireAnswerService.class);
questionnaireAnswerService.getQuestionnaireAnswer(xxx)
⚽ 补充
实际场景中,很可能出现更细碎繁琐的场景。
⽐如,对于questionnaire_answer表,服务端提供了基于questionnaire_id字段的分⻚查询接⼝,getPageList(Long questionnaireId)
后续开发中发现要增加另⼀个字段,此时需要对service、serviceImpl、biz、bizImpl进⾏修改
使⽤So之后,某些查询简化⾄只创建⼀个Entity对象,剩下的交给框架本⾝。
使⽤⼿册
⼀、项目内使用
服务端通过Dao查询并返回Entity的场景(⽆需创建XXSo对象)
A.配置
1.修改pom⽂件,引⼊so框架
<dependency>
<groupId>com.liepin</groupId>
<artifactId>ins-lbb-framework</artifactId>
<version>具体版本</version>
</dependency>
2.创建通⽤查询器SoSelector
服务端的查询,基本上都是与SoSelector进⾏交互
/**
* 定义courselbb项⽬的通⽤查询器,要求如下:
*
* 1.继承AbstractSoSelector,泛型指定某entity对象,推荐SoCourseLbbEntity(固定)
* 2.实现abstract⽅法:entityPackage() - 项⽬中的entity对象所在路径
*/
@Component
public class SoSelector extends AbstractSoSelector<SoCourseLbbEntity> {
@Override
protected String entityPackage() {
return "com.liepin.courselbb.platform";
}
}
B.使⽤说明
⼀个例⼦
// 1.注⼊`A步骤`配置的SoSelector
@Autowire
SoSelector soSelector
// 2.通过SoEntityBuilder构建SuperSo
SuperSo so = SoEntityBuilder.of(QuestionnaireStemEntity.class)
.fromMaster(true)
.in(QuestionnaireStemEntity::setId,Lists.newArrayList(1L,2L))
.op(QuestionnaireStemEntity::setOrder_num,Op.BIGGER_EQUAL,4)
.eq(QuestionnaireStemEntity::setQuestionnaire_id,3L)
.addSort(QuestionnaireStemEntity::setOrder_num,Sort.ASC)
.build();
// 3.调⽤SoSelector的查询⽅法
QueryResultsDto queryResultsDto = soSelector.queryPageResult(so,curPage,pageSize);
List<QuestionnaireStemEntity> list = soSelector.queryPageList(so,curPage,pageSize);
int count = soSelector.queryCount(so);
QuestionnaireStemEntity entity = soSelector.queryById(1L,QuestionnaireStemEntity.class);
🌅 说明
这个例⼦中,在第2步相当于构建了这样的sql查询:
Select * from questionnaire_stem_entity
Where id in (1,2)
and order_num >= 4 and questionnaire_id = 3
And deleteflag = 0 --> [这个条件是默认追加的]
Order by order_nu asc
同时,指定了查询master库,且不追加select count(*)语句
⼆、Rpc远程调⽤
A.服务端提供⽀持
1.创建通⽤service和serviceImpl实现类
service
/** * 通⽤的SoService,继承ISoService * 注:ISoService中指定了通⽤查询⽅法,这⾥复制过来,并增加相应的@SwiftService、@SwiftInterface注解 */ @SwiftService public interface ISoCourseLbbService extends ISoService { @Override @SwiftInterface List queryList(SuperSo so); @Override @SwiftInterface QueryResultsDto queryPageResult(SuperSo so,int curPage,int pageSize); @Override @SwiftInterface int queryCount(SuperSo so); @Override @SwiftInterface List queryById(Long id,SuperSo so); }
serviceImpl
/** 固定写法,基本调⽤SoSelector的⽗类同名⽅法即可*/ @Service public class SoCourseLbbServiceImpl implements ISoCourseLbbService { @Autowired SoSelector soSelector; @Override public List queryList(SuperSo so) { return soSelector.queryList(so); } @Override public QueryResultsDto queryPageResult(SuperSo so, int curPage, int pageSize) { return soSelector.queryPageResult(so,curPage,pageSize); } @Override public List queryPageList(SuperSo so, int curPage, int pageSize) { return soSelector.queryPageList(so,curPage,pageSize); } @Override public int queryCount(SuperSo so) { return soSelector.queryCount(so); } @Override public List queryById(Long id, SuperSo so) { SuperSo resSo = soSelector.queryById(id,so); return ImmutableList.of(resSo); } }
2.创建XXEntity对象的XXSo对象
- entity和so对象属性完全⼀样,可复制过来并去掉transient关键字
- XXSo需继承SuperSo对象
- So对象建议package,原dto包下,新增so包。即,完整路径:com.liepin.xxx.dto.so
B.客⼾端配置
/**
* SoClientHelper
* 要求:
* 1.继承AbstractSoClientHelper
* 2.并实现abstract⽅法——指定某个服务端的SoService(上⼀步服务端提供的service)
*/
@Component
public class SoCourseLbbClientHelper extends AbstractSoClientHelper {
@Override
protected ISoService getRpcService() {
return RPCFactory.getService(ISoCourseLbbService.class);
}
}
C.使⽤说明
⼀个例⼦
// 1.注⼊`B步骤`配置的ClientHelper
@Autowired
private SoCourseLbbClientHelper soCourseLbbClientHelper;
// 2.通过SoBuilder构建SuperSo
QuestionnaireStemSo stemSo = SoBuilder.of(QuestionnaireStemSo.class)
.eq(QuestionnaireStemSo::setQuestionnaire_id,id)
.addSort(QuestionnaireStemSo::setOrder_num, Sort.ASC)
.build();
// 3.调⽤ClientHelper的查询⽅法
QueryResultsDto<QuestionnaireStemSo> queryResultsDto = soCourseLbbClientHelper.queryPageList(stemSo,curPage,pageSize);
QuestionnaireStemSo so = queryById(Long id,new QuestionnaireStemSo())
开发手册
I. AbstractSoSelector
根据So对象,找到对应的Entity和Dao,并执行sql;子对象SoSelector单例
查询方法:
<S extends IEntity>S queryById(Long id,Class<S> entityClz)
<T extends SuperSo>T queryById(Long id,SuperSo so)
int queryCount(SuperSo so)
List queryList(SuperSo so)
List queryPageList(SuperSo so,int curPage,int pageSize)
QueryResultsDto queryPageResult(SuperSo so,int curPage,int pageSize)
- 关键属性
// xxSo -> xxEntity.class,初始化时根据Entity构建全部映射
private static Map<String,Class> SO_ENTITY_MAP;
// entity.class -> dao,dao对象缓存
private static Map<Class,AbstractEntityDao> DAO_MAP;
- SO_ENTITY_MAP初始化
/** 初始化映射map,只初始化一次*/
private void initSoEntityMap(){
if(ValidUtils.isNotEmpty(SO_ENTITY_MAP)){
return;
}
// 根据QuestionnaireSo 找到 QuestionnaireEntity.class
Reflections f = new Reflections(entityPackage());
// 获取扫描到的标记注解的集合
Set<Class<?>> set = f.getTypesAnnotatedWith(EntityInfo.class);
for (Class<?> c : set) {
String entityName = c.getSimpleName();
String soName = cutEntityName(entityName) + SO_SUFFIX;
SO_ENTITY_MAP.put(soName,c);
}
}
- DAO_MAP初始化
private AbstractEntityDao getByEntityClz(Class entityClz) {
// -- 1.从缓存中获取
AbstractEntityDao entityDao = DAO_MAP.get(entityClz);
if(entityDao!=null){
return entityDao;
}
// -- 2.复用spring context中的dao,逻辑:QuestionnaireEntity -> questionnaireDaoImpl -> QuestionnaireDaoImpl
String entityName = entityClz.getSimpleName();
String daoName = MyStrUtil.lowerFirst(cutEntityName(entityName) + "DaoImpl");
try {
entityDao = (AbstractEntityDao) SpringContextUtil.getBean(daoName);
}catch (BeansException e){
catalinaLog.warn(entityClz.getName()+"对应的Dao,未找到spring中的缓存对象"+e.getMessage());
}
if(entityDao!=null){
DAO_MAP.put(entityClz,entityDao);
return entityDao;
}
// -- 3.利用mybatis创建一个新的dao
Class selectorClz = getClass();
Assert.notNull(selectorClz,"childClass()返回值不为空");
try {
entityDao = (AbstractEntityDao) selectorClz.newInstance();
Invoker clazzInvoker = REFLECTOR.getSetInvoker("clazz");
Invoker templateDaoInvoker = REFLECTOR.getSetInvoker("templateDao");
Invoker readyMoveInvoker = REFLECTOR.getSetInvoker("readyMove");
clazzInvoker.invoke(entityDao,new Object[]{entityClz});
templateDaoInvoker.invoke(entityDao,new Object[]{DaoTool.getTemplateDao(entityClz)});
readyMoveInvoker.invoke(entityDao,new Object[]{TableMove.listen(entityClz, entityDao)});
DAO_MAP.put(entityClz,entityDao);
} catch (Exception e) {
catalinaLog.error(entityClz.getName()+"对应的Dao,初始化异常"+e.getMessage(),e);
throw new RuntimeException("Dao初始化异常");
}
return entityDao;
}
II. SoEntityBuilder
使用entity的set方法,设置sql的查询条件
设置条件:
<E extends IEntity, P> SoEntityBuilder eq(SoFunction<E, P> fun, P p)
<E extends IEntity, P> SoEntityBuilder isNull(SoFunction<E, P> fun)
<E extends IEntity, P> SoEntityBuilder in(SoFunction<E, P> fun, List<P> list)
<E extends IEntity, P> SoEntityBuilder in(SoFunction<E, P> fun, Set<P> set)
<E extends IEntity, P> SoEntityBuilder op(SoFunction<E, P> fun, Op op, Object param)
// 排序
<E extends IEntity, P> SoEntityBuilder addSort(SoFunction<E, P> fun, Sort sort)
// 是否指定查询主库
SoEntityBuilder fromMaster(boolean fromMaster)
# 通过SuperSoBuilder的方法添加条件
protected SuperSoBuilder addOpList(SoFunction fun, Op op,Object param) {
Assert.notNull(op,"op不能为空");
Pair<Op,Object> pair = checkOpAndParam(op,param);
String key = getFieldName(fun.getImplMethodName());
if(PROPERTY_DELETEFLAG.equals(key)){
userSetDel = true;
}
List<SoCondition> list = so.getConditions();
if(list==null){
list = Lists.newArrayList();
so.setConditions(list);
}
list.add(SoCondition.of(key,pair.getLeft(),pair.getRight()));
return this;
}
III. ConditionUtils
便于拼接公司sql条件对象
(QueryCondition)的工具
- 封装前后比对:
# 封装前:
QueryCondition queryCondition = new QueryCondition();
queryCondition.setPaged(false);
/** 拼接条件 */
InnerCondition innerCondition = new InnerCondition(null, null);
innerCondition.appendAnd("target_id", targetId);
innerCondition.appendAnd("object_type", EnumObjectType.THIRD_DEPT.getCode());
innerCondition.appendAnd("object_id", deptIdList, Op.IN);
innerCondition.appendAnd("deleteflag", EnumZeroOne.ZERO.getCode()); //指定逻辑删除字段
queryCondition.setQueryCondition(innerCondition);
+++++++++++++++++++++++++++++++++++++++
# 封装后:
QueryCondition queryCondition = ConditionUtils.createNoPagedQueryCondition(condition -> {
/** 拼接条件 */
condition.appendAnd("target_id", targetId);
condition.appendAnd("object_type", EnumObjectType.THIRD_DEPT.getCode());
condition.appendAnd("object_id", deptIdList, Op.IN);
});
- 步长自动调节的批量工具
# 使用举例:
ConditionUtils.batchHandle(condition -> { -- 1.指定条件
condition.appendAnd("deleteflag", EnumDeleteFlag.UNDELETE.getCode());
},
mallCourseCategoryDao, -- 2.指定dao
list->{ -- 3. 批处理逻辑
List<MallCourseCategoryDto> tmpList = convertEntites2Dtos(list);
resultDtoList.addAll(tmpList);
});
下个版本
• 增加insert(SuperSo so) 已完成
• 增加update(SuperSo so) 已完成
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。