2

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)
  1. 关键属性
// xxSo -> xxEntity.class,初始化时根据Entity构建全部映射
private static Map<String,Class> SO_ENTITY_MAP;

// entity.class -> dao,dao对象缓存
private static Map<Class,AbstractEntityDao> DAO_MAP;
  1. 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);
    }
}
  1. 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的查询条件

image.png

设置条件:

<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)的工具

  1. 封装前后比对:
# 封装前:
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);
});
  1. 步长自动调节的批量工具
# 使用举例:
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) 已完成


青鱼
268 声望25 粉丝

山就在那里,每走一步就近一些