优雅书写业务代码-开发常用技术

220 阅读4分钟

1 注解@Mapper(componentModel = "spring")-自动生成DTO-DO的模型映射

场景: 插件可以自动生成实现类,实现DTO-DO各种模型之间的字段映射(不仅仅限制于DTO-DO)

一,用法说明:

//通常在业务实现类中创建一个用于转换的内部接口
@Mapper(componentModel = "spring")
public interface RoleConvert {
    RoleDO convert(RoleDTO roleDTO);
}

使用:
//依赖注入
@Autowired
private RoleConvert roleConvert;

//转化
RoleDO roleDO = roleConvert.convert(roleDTO);

二,高级用法,指定映射字段(支持表达式):

//通常在业务实现类中创建一个用于转换的内部接口

/**
ProductDO中 config字段为json字符串
ProductDTO中  config字段为map类型
*/
@Mapper(componentModel = "spring")
public interface ProductConverter {

    @Mappings({
            @Mapping(source = "id",target = "productId"),
            @Mapping(source = "title",target = "productName"),
            @Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
            @Mapping(target = "config", expression = "java( com.alibaba.fastjson.JSON.parseObject(pdo.getConfig(),new com.alibaba.fastjson.TypeReference<java.util.Map<java.lang.String,java.lang.String>>(){}) )"),
            @Mapping(target = "email", ignore = true) //这个字段忽略不转换
    })
    ProductDTO convert(ProductDO pdo);
    
    @Mappings({
            @Mapping(source = "productId",target = "id"),
            @Mapping(source = "productName",target = "title"),
            @Mapping(target = "config", expression = "java( com.alibaba.fastjson.JSON.toJSONString(pdto.getConfig()) )")
    })
    ProductDO convert(ProductDTO pdto);
}
  • 还可以支持多个对象转换成一个对象,多对一的写法
  • 不单单可以支持spring组件,还可以支持其他组件,componentModel = "spring"
  • 此工具更多用法学习链接:blog.csdn.net/qq_34614236…

2 使用Guava优雅实现本地缓存

2.1 本地缓存通常用来解决一些数据量相对较小但是频繁访问的数据

2.2 Guava Cache与ConcurrentHashMap很相似,但也不完全一样。最基本的区别是:

  • ConcurrentHashMap会一直保存所有添加的元素,直到显式地移除。
  • 相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。
  • 在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。

2.3 通常来说,Guava Cache适用于:

  • 你愿意消耗一些内存空间来提升速度。
  • 你预料到某些键会被查询一次以上。
  • 缓存中存放的数据总量不会超出内存容量。
  • Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached或者Redis这类工具
  • 如果你的场景符合上述的每一条,Guava Cache就适合你。

学习链接:gourderwa.blog.csdn.net/article/det…

2.4 代码举例实现:

/**
dao层对应的数据操作实现类:加缓存 查询条件实现等
service业务层 可直接引用
*/
@Component
public class VariableRepositoryImpl implements VariableRepository, InitializingBean {

    @Resource
    private ArtooSceneVariableDAO variableDAO;

    @Resource
    private RefreshLocalCacheBuilder cacheBuilder;

    private LoadingCache<Long, ArtooSceneVariableDO> idCache;

    private LoadingCache<Long, List<ArtooSceneVariableDO>> sceneIdCache;
    
    //删除
    @Override
    public int delete(Long id) {
        // 缓存清除  idCache.invalidateAll(ids) -代表清除多个缓存
        idCache.invalidate(id);
        //物理删除
        return variableDAO.deleteByPrimaryKey(id);
    }

   //修改
    @Override
    public Variable update(Variable variable) {
        ArtooSceneVariableDO variableDO = convert.convert(variable);
        variableDO.setGmtModified(new Date());
        variableDAO.updateByPrimaryKeySelective(variableDO);
        // 缓存清除
        idCache.invalidate(variableDO.getId());
        return query(variableDO.getId());
    }
    
    //查询一个
    @Override
    public Variable query(Long id) {
        ArtooSceneVariableDO variableDO = idCache.getUnchecked(id);//get不抛出异常
        return convert.convert(variableDO);
    }
    
    //查询多个
    @Override
    public List<Variable> queryByIds(List<Long> ids) {
        try {
            return idCache.getAll(ids) //getAll获取多个 会抛出异常
                    .values()
                    .stream()
                    .map(convert::convert)
                    .collect(Collectors.toList());
        } catch (ExecutionException e) {
            LOGGER.warn("found variable fail by ids {}", ids, e);
        }
        return Collections.emptyList();
    }

    @Override
    public List<Variable> queryBySceneId(Long sceneId) {
        try {
            return sceneIdCache.get(sceneId) //get获取一个 会抛出异常
                    .stream()
                    .map(convert::convert)
                    .collect(Collectors.toList());
        } catch (ExecutionException e) {
            LOGGER.warn("found variable fail by sceneId {}", sceneId, e);
        }
        return Collections.emptyList();
    }

    //这里实现InitializingBean接口方法 在bean初始化后构建缓存
    @Override
    public void afterPropertiesSet() throws Exception {
       //调用封装的guava缓存方法cacheBuilder.build(**) 实现根据ID主键查询的缓存
        this.idCache = cacheBuilder.build(5*60, 25, 500, new CacheLoader<Long, ArtooSceneVariableDO>() {
            @Override
            public ArtooSceneVariableDO load(Long key) throws Exception {
                return variableDAO.selectByPrimaryKey(key);
            }
        });

       //调用封装的guava缓存方法cacheBuilder.build(**) 实现根据外键查询的缓存
        this.sceneIdCache = cacheBuilder.build(5*60, 25, 500, new CacheLoader<Long, List<ArtooSceneVariableDO>>() {
            @Override
            public List<ArtooSceneVariableDO> load(Long key) throws Exception {
                ArtooSceneVariableParam variableParam = new ArtooSceneVariableParam();
                variableParam.createCriteria().andSceneIdEqualTo(key);
                return variableDAO.selectByParam(variableParam);
            }
        });
    }
   
}    

/**
封装的通用的guava缓存实现类
*/
@Component
public class RefreshLocalCacheBuilder {

    @Resource(name = "cacheRefreshDbExecutor")
    private ExecutorService executor;

    /**
     * 支持异步刷新的本地缓存实现,会返回一个 LoadingCache 实例
     * @param expireTime 数据过期时间 ,单位为秒
     * @param refreshTime 数据刷新时间,单位为秒, 需要注意的是 刷新为异步,且只会刷新存在的数据,因此  要小于 expireTime
     * @param initialCapacity 初始的容量
     * @param loader load
     * @return
     */
    public <K, V> LoadingCache<K, V> build(long expireTime, long refreshTime, int initialCapacity, CacheLoader<K, V> loader) {
        return CacheBuilder.newBuilder()
                .expireAfterWrite(expireTime, TimeUnit.SECONDS)
                .refreshAfterWrite(refreshTime, TimeUnit.SECONDS)
                .initialCapacity(initialCapacity)
                .build(CacheLoader.asyncReloading(loader, executor));
    }
}


---------------------------------------------------------------------------
/**
springBoot启动配置类
*/
@Configuration
public class SpringBeanConfig {
    @Bean
    public OkHttpClient build() {
        return new OkHttpClient.Builder()
                .readTimeout(2000, TimeUnit.MILLISECONDS)
                .build();
    }

    @Bean(name = "cacheRefreshDbExecutor")
    public ExecutorService dbRead(){
        ThreadFactory threadFactory= new ThreadFactoryBuilder()
                .setNameFormat("cache-db-read-thread-pool-%d")
                .build();
        //用于统一的缓存刷新,因此根据单机tddl 的线程池配置,由于是用于缓存,因此可以丢弃老的任务
        //假设db查询在5ms 以内,1s 可吞吐 2000 个  2秒超时,  可设置4000个队列,增加一些冗余4000*1.3
        return new ThreadPoolExecutor(
                8,10,0L,TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<>(5200),threadFactory,new ThreadPoolExecutor.DiscardOldestPolicy());
    }
}

3 利用jdk的Consumer实现通用的接口回调

java.util.function中 FunctionSupplierConsumerPredicate和其他函数式接口广泛用在支持lambda表达式的API中。这些接口有一个抽象方法,会被lambda表达式的定义所覆盖。

Consumer的作用顾名思义,是给定义一个参数,对其进行(消费)处理,处理的方式可以是任意操作.

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

3.1 其核心方法如下:

  • void accept(T t);
    对给定的参数T执行定义的操作

  • default Consumer andThen(Consumer<? super T> after)
    对给定的参数T执行定义的操作执行再继续执行after定义的操作

    学习链接:www.cnblogs.com/coderxx/p/1…

3.2 接口回调代码举例:

public class GollHandle{

    @Resource
    private UserService userService;
    
    @Resource
    private LogService logService;

    /**
     业务处理逻辑
    */
    public void handle() {
    //上下文参数
    HandleContext handleContext = new HandleContext();

    Consumer<Product> productConsumer = product -> {
        //函数式编程实现接口回调的业务方法accept(T),参数product
        logService.recordProductLog(product, handleContext);
    };
    
    //购买商品
    userService.buyProduct(product, productConsumer);
    }

}



/**
 用户业务类
*/
public class UserService {
    @Resource
    private UserDao userDao;

    /**
     购买商品
    */
    public void buyProduct(Product product, Consumer<Product> consumer){
     userDao.buy(product);
     Optional.ofNullable(consumer).ifPresent(c -> c.accept(product));
    }
    
}


/**
日志业务类
*/
public class LogService {
    /**
     记录购买商品日志
    */
    public void recordProductLog(Product product, HandleContext context){
       //记录日志 context上下文参数
    }
}