自研cloud框架专题–service模块(二)

本文介绍了自研cloud框架中的service模块,包括框架集成、依赖管理、DO-DTO自动装载(基础字段和数据库字段)、数据缓存机制以及使用示例。此外,还解答了关于自动装载和数据缓存的问题。
摘要由CSDN通过智能技术生成


开源地址:https://github.com/2892824942/ty-cloud/blob/main/ty-framework/ty-framework-service

项目特点

1.自动依赖mybatis-plus模块,拥有cloud下mybatis-plus模块所有能力.具体见:https://github.com/2892824942/ty-cloud/blob/main/ty-framework/ty-framework-mybatis-plus
2. 提供实体对象缓存能力,简化简单缓存业务代码开发
3. DO<–>DTO 通过Mapstruct转换,增强DO–>DTO转换,支持简单关联字段自动映射,支持审计字段自动映射

一:框架集成

1.引入核心依赖

暂时未发到中央仓库(准备中)…


<dependency>
    <groupId>com.ty</groupId>
    <artifactId>ty-framework-service-starter</artifactId>
    <version>${最新版本}</version>
</dependency>

二:使用示例


/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author suyouliang
 * @since 2023-11-27
 */
@Service
public class UserServiceImpl extends AutoWrapService<User, UserFullDTO, UserMapper> implements IUserService, UserNameTranslation {

    @Resource
    private UserMapper userMapper;

    @Override
    public UserFullDTO getById(Long id) {
        if (isNegative(id)) {
            return null;
        }
        return selectOneDTO(User::getId, id);
    }

    @Override
    public PageResult<UserFullBO> getPage(UserQuery userQuery) {
        return userMapper.selectJoinPage(userQuery);
    }

    @Override
    public List<UserFullDTO> getFullList(UserQuery userQuery) {
        userQuery.openSelectAll();
        PageResult<User> userPageResult = userMapper.selectPage(userQuery);
        return convert(userPageResult.getList(), UserFullDTO.class);
    }

    @Override
    public List<UserInfoDTO> getInfoList(UserQuery userQuery) {
        userQuery.setPageNo(PageParam.PAGE_SIZE_NONE);
        PageResult<User> userPageResult = userMapper.selectPage(userQuery);
        return convert(userPageResult.getList(), UserInfoDTO.class);
    }

    @Override
    public Boolean save(UserSaveQuery query) {
        User user = convert(query);
        return userMapper.insert(user) > 0;
    }

    @Override
    public void saveBatch(List<UserSaveQuery> queryList) {
        if (CollUtil.isEmpty(queryList)) {
            return;
        }
        List<User> userList = convert(queryList, User.class);

        userMapper.insertBatch(userList);
    }

    @Override
    public Boolean deleteById(Long id) {
        return userMapper.deleteById(id) > 0;
    }
}

1.Service需要继承父Service以获得对应能力,同时需要指定泛型,泛型1为数据库表对应的实体类,泛型2为主DTO类,泛型3为对应Mapper类(
如存在)
2.主要提供以下父Service

  • GenericService:支持简化开发api
  • AutoWrapService:支持简化开发api,支持DO->DTO自动装载
  • CacheAutoWrapService:支持简化开发api,支持DO->DTO自动装载,支持自定义数据缓存
  • AllCacheAutoWrapService:支持简化开发api,支持DO->DTO自动装载,支持全量数据缓存

三:功能详情

1.DO->DTO自动装载

1.1 基础字段自动装载

项目集成了mapstruct-plus,通过一下方式实现自动装载,具体参见https://www.mapstruct.plus/

@Schema(description = "用户全量对象")
@Getter
@Setter
@AutoMapper(target = User.class)
public class UserFullDTO extends AbstractNameDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    @Schema(description = "姓名")
    @ChineseNameDesensitize
    private String name;

    @Schema(description = "密码")
    @PasswordDesensitize
    private String password;

    @Schema(description = "年龄")
    private Integer age;

    @Schema(description = "邮箱")
    @EmailDesensitize
    private String email;

    @Schema(description = "角色信息")
    private List<RoleDTO> roleInfos;

    @Schema(description = "用户地址code")
    private AddrDTO addrInfo;

}

本框架已在AutoWrapperService中提供covert方法,直接转换,也可通过xxxDTO()的方法直接获取转换后的DTO.

1.2 数据库字段自动装载

一张表中有几个字段是映射的其他表,比如用户拥有areaId,roleCode属性,分别对应区域表及角色表.这种一个字段映射其他表是非常常见的,
在开发过程中,我们通常会定义一个DTO,将类似以上的主表内容以及其他表内容通过DTO进行封装,这就需要针对这种其他表映射字段进行封装,
当字段较多时,开发难度虽不大,但是比较繁琐.而在实际开发过程,这种情况非常常见

DO->DTO自动装载主要是针对以上场景,简化开发过程
整个自动装载流程,以User表的roleCode为例:
RoleId->查询RoleDO->转换为RoleDTO->写入到UserDTO对应属性roleDTO中 通用的步骤即:

  • [字段->DO]
  • [DO->DTO]
  • [DTO写入目标属性]

使用方法:
(1)定义自动装载映射
当继承Service支持自动装载时,例如AutoWrapService,则[字段->查询DO]默认定义为id查询,[DO->DTO]则通过mapstruct-plus自动转换
(2)在需要自动装载的字段上,添加注解@AutoWrap,并定义需要自动装载的DTO类

public class User extends BaseDO {
    
    @AutoWrap(values = {RoleDTO.class, RoleSimpleDTO.class})
    private List<Long> roleIds;
    
}

(3)需使用AutoWrapperService中提供covert方法,直接转换,或通过xxxDTO()的方法直接获取转换后的DTO.

  • 支持字段一对一,一对多,多对多
  • 支持自动装载嵌套.例如A中的b字段映射B表,B中的c字段映射C表,当b->B,c->C自动装载配置后,则A->B->C自动装载

2.数据缓存

(1)能力介绍

当继承CacheAutoWrapService或AllCacheAutoWrapService支持数据缓存

  • 数据会按照key,value的形式存储在缓存中
  • 支持key自定义
  • 缓存基于Jcache标准,可自动替换其他实现
  • 缓存使用ReadThrough模式
  • 支持列表查询,如List ids.
  • 自动装载过程支持缓存,如果缓存的key定义和自动装载的字段相同,则自动装载过程将走缓存

(2)使用示例

@Service
public class AddressServiceImpl extends AllCacheAutoWrapService<Address, AddrDTO, AddressMapper> implements IAddressService {

  @Override
  public AddrDTO getByCode(String code) {
    return selectOneDTO(Address::getCode, code);
  }

  @Override
  public List<AddrDTO> getByCodesFromCache(List<String> codes) {
    Map<String, AddrDTO> all = cacheGetAll(codes);
    return new ArrayList<>(all.values());
  }

  @Override
  public AddrDTO getByCodeFromCache(String code) {
    return cacheGetByKey(code);
  }

  @Override
  public List<AddrDTO> getList(AddrQuery addrQuery) {
    addrQuery.setPageNo(PageParam.PAGE_SIZE_NONE);
    PageResult<Address> pageResult = baseMapper.getPage(addrQuery);
    return convert(pageResult.getList(), AddrDTO.class);
  }


  /**
   *↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓增删改部分demo↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
   */

  /**
   * 默认开启了ReadThrough,读取不到会自动查询数据库
   *
   * @param query
   * @return
   */

  @Override
  public Boolean save(AddrSaveQuery query) {
    Address address = convert(query, Address.class);
    return save(address);
  }

  /**
   * 1.如更新不会更新key相关字段,直接删除缓存即可
   * 2.如更新会更新key相关字段,则需要先查询数据库原始数据,update后需要删除前后两个值已达到缓存重新加载的目的
   *
   * @param addrUpdateQuery
   * @return
   */
  @Override
  @Transactional
  public Boolean update(AddrUpdateQuery addrUpdateQuery) {
    Address address = convert(addrUpdateQuery, Address.class);
    Address dbAddress = getById(address.getId());
    boolean result = updateById(address);
    if (result) {
      cacheClear(Lists.newArrayList(address, dbAddress));
    }
    return null;
  }

  /**
   * 默认开启了ReadThrough,读取不到会自动查询数据库
   *
   * @param query
   * @return
   */
  @Override
  public void saveBatch(List<AddrSaveQuery> query) {
    List<Address> dataList = convert(query, Address.class);
    super.saveBatch(dataList);
  }

  /**
   * 删除需要手动操作缓存,更新类似
   *
   * @param id
   * @return
   */
  @Override
  @Transactional
  public Boolean deleteById(Long id) {
    Address address = selectOne(Address::getId, id);
    if (address == null) {
      return Boolean.TRUE;
    }
    boolean result = removeById(id);
    cacheClear(address);
    return result;
  }

  /**
   * ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓框架父类方法重写↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
   * @return
   */

//    /**
//     * 缓存Service:
//     * 重写缓存定义key,这里缓存两个key没有实际意义,只是为了测试多个key作为缓存的场景
//     * @return
//     */
//    @Override
//    public List<SFunction<Address, ?>> cacheDefineDOMapKeys() {
//        return Lists.newArrayList(Address::getCode, Address::getName);
//    }

  /**
   * 缓存Service:
   * 缓存key和自动装载字段一致,自动装载将走缓存
   *
   * @return
   */
  @Override
  public SFunction<Address, ?> cacheDefineDOMapKey() {
    return Address::getCode;
  }

  /**
   * 自动装载Service:
   * 定义code->AddrDTO自动装载
   *
   * @return
   */
  @Override
  public Map<?, AddrDTO> autoWrap(Collection<?> collection) {
    return convertMap(GenericsUtil.check2Collection(collection), Address::getCode, AddrDTO::getCode);
  }
}

3.问题答疑

问题1:如果定义的字段不是id或者[DO->DTO]过程部分字段名称不一致无法自动转换怎么办

答:首先,如果之间名称不一致,使用mapstruct-plus注解可标注对应映射,另外,如果此类情况较多,转换比较复杂,顶层接口定义了autoWrap的方法,支持重写[字段->DO]和[DO->DTO]过程

@Service
public class AddressServiceImpl extends AllCacheAutoWrapService<Address, AddrDTO, AddressMapper> implements IAddressService {
    /**
     * 自动装载Service:
     * 定义code->AddrDTO自动装载
     *
     * @return
     */
    @Override
    public Map<?, AddrDTO> autoWrap(Collection<?> collection) {
        //通过Mapstruct重新重新定义[DO->DTO]过程
        Function<List<Address>, List<AddrDTO>> function = AddrDTOConvert.INSTANCE::convert;
        //指定[字段->查询DO]使用code查询
        return convert(GenericsUtil.check2Collection(collection), Address::getCode, AddrDTO::getCode, function);
    }
}

问题2:一个表中既有id又有code多字段自动映射,或者一个字段可能不同的场景返回的DTO不同怎么办?

答:自定义AutoWrapper,可以通过再次注册AutoWrapper来为当前表定义其他字段的自动装载

@Service
public class RoleServiceImpl extends GenericService<Role, RoleDTO, RoleMapper> implements IRoleService {
   /**
   * 为角色表定义code->RoleSimpleDTO自动装载
   * @return
   */
    @Bean public AutoWrapper<Role> roleSimpleDTOAutoWrapper() {
        //注意:不可以省略后面的泛型否则报错,默认使用maperstruct-plus能力自动转换,也可重写对应方法
        return new AutoWrapService<Role, RoleSimpleDTO, RoleMapper>() {
        };
    }
}

如上,此时Role支持[code->RoleSimpleDTO]和[code->RoleDTO]两种自动装载

问题3:自动装配和JPA好像啊?

是的,自动装配也参考了JPA的思路,但是JPA的级联join查询不被大多数互联网公司接受.但是自动将目标数据装载到当前DTO确实是个非常方便的方式,尤其在常见的id,code等单字段映射场景.
自动装配采用的是sql in的方式查询,不使用join.

注意:目前AutoWrapper是按照DTO类型做自动识别,所以暂时不支持多个字段转换为同一个DTO.

更详细的使用案例,见:https://github.com/2892824942/framework-demo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值