美文网首页
MiniMall:CRUD的代码是不可能写得?是的,我都帮你写好

MiniMall:CRUD的代码是不可能写得?是的,我都帮你写好

作者: Anbang713 | 来源:发表于2020-04-24 21:27 被阅读0次

虽然从整个项目的角度来说,我们把整个项目拆成了一个个微服务,这一点是有别于单体应用的。但是从一个业务模块本身的实现,不管是单体应用架构还是微服务架构,都会有控制层、业务层、持久层,这一点是不会变的。而我们今天要说的,就是针对每一层的增删改查操作进行架构封装,这几个常见操作的实现基本上都是一样的,实在没有必要在每个业务模块里写重复的代码,我们也不是这样的程序员对不对?

友情提示:看本篇博客,最好结合源码一起来。每次访问github比较慢的,可以将源码检出下载到本地。

1. 持久层

因为我们的持久层使用的是Spring Data JPA框架实现,站在巨人的肩膀上,自然也就没我们啥事了。Spring Data JPA提供核心的接口Repository来表示持久层的接口定义,该接口有很多的子接口,其中就有一个CrudRepository接口,里面定义了常见的CRUD操作,比如savesaveAllfindById等,具体如下:

@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
    <S extends T> S save(S var1);

    <S extends T> Iterable<S> saveAll(Iterable<S> var1);

    Optional<T> findById(ID var1);

    boolean existsById(ID var1);

    Iterable<T> findAll();

    Iterable<T> findAllById(Iterable<ID> var1);

    long count();

    void deleteById(ID var1);

    void delete(T var1);

    void deleteAll(Iterable<? extends T> var1);

    void deleteAll();
}

但是大家可以看到,在CrudRepository接口中并没有分页查询以及规范查询方法。这就不得不提到另一个常见的接口JpaSpecificationExecutor,其代码如下:

public interface JpaSpecificationExecutor<T> {
    Optional<T> findOne(@Nullable Specification<T> var1);

    List<T> findAll(@Nullable Specification<T> var1);

    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);

    List<T> findAll(@Nullable Specification<T> var1, Sort var2);

    long count(@Nullable Specification<T> var1);
}

而在我们的项目中,通常一个业务模块有搜索界面、新建编辑界面、查看界面,也就表示一个模块的持久化接口往往需要实现CrudRepositoryJpaSpecificationExecutor两个接口,也正是因为如此,在我们的项目持久层中,提供了BaseRepository接口作为所有具备CRUD以及分页查询功能的父接口:

@NoRepositoryBean
public interface BaseRepository<T extends IsEntity> extends CrudRepository<T, String>, JpaSpecificationExecutor<T> {

}

那么以项目模块举例,其持久层接口直接继承BaseRepository即可:

public interface StoreRepository extends BaseRepository<Store> {

    /**
     * 根据代码查找
     *
     * @param code
     * @return
     */
    Optional<Store> findByCode(String code);
}

2. 业务层

2.1 接口封装

业务层主要是接收控制层的方法调用以及调用持久层进行数据的处理,这一层是集中处理业务的地方,也即真正的业务核心所在。对于业务层这一层的CRUD封装,我们定义了CrudService接口,其代码如下:

public interface CrudService<T extends IsEntity> {

    /**
     * 新建实体,新增一条记录
     *
     * @param entity 实体
     * @return 新增记录的主键值
     */
    String save(T entity);

    /**
     * 根据主键uuid,删除一条记录
     *
     * @param uuid 主键uuid
     */
    void deleteById(String uuid);

    /**
     * 通过主键id,获取实体
     *
     * @param uuid 主键id
     * @return 实体
     */
    T findById(String uuid);

    /**
     * 通过主键集合获取实体,并将结果转换成map
     *
     * @param uuids
     * @return
     */
    Map<String, T> findAllByIds(Set<String> uuids);

    /**
     * 根据关键字分页查询
     *
     * @param definition
     * @return
     */
    QueryResult<T> query(QueryDefinition definition);
}

额外的,除了对CRUD进行封装外,我们还提供了CacheServiceSupportStateService两个接口分别用于支持缓存和状态改变操作(基础资料是有状态的,比如项目有使用中和已停用两种状态)。其代码分别如下:

  • CacheService
public interface CacheService {

    /**
     * 获取模块的key前缀常量定义,可选值{@link com.autumn.mall.commons.api.MallModuleKeyPrefixes}
     *
     * @return
     */
    String getModuleKeyPrefix();
}
  • SupportStateService
public interface SupportStateService<S> {

    /**
     * 修改状态
     *
     * @param uuid        唯一标识
     * @param targetState 目标状态
     */
    void changeState(String uuid, S targetState);
}

还是以项目模块举例,其业务层接口如下:

public interface StoreService extends CrudService<Store>, CacheService, SupportStateService<UsingState> {

}

2.2 实现类封装

除了对接口的封装外,我们还需要对CRUD的实现进行封装,这才是我们的重点,否则只是简简单单地对接口进行封装,实际上其意义并不大。

封装后的业务层接口实现类,是一个抽象类。该实现类实现了CrudServiceCacheService两个接口,也即表示对于大部分的业务模块来说,都是支持CRUD操作和缓存的。其代码如下:

public abstract class AbstractServiceImpl<T extends IsEntity> implements CrudService<T>, CacheService {
    public abstract BaseRepository<T> getRepository();

    public abstract SpecificationBuilder getSpecificationBuilder();
}

具体实现见源代码,重点说一说继承该业务层接口抽象实现类需要实现的两个抽象方法。

  • getRepository():返回一个持久层接口,其返回值就是我们持久层的父接口。
  • getSpecificationBuilder():返回一个规范查询定义的构造器,用于针对规范查询的条件构造。

老规矩,还是以项目模块的业务层实现类举例,其代码如下(细节见源码):

@Service
public class StoreServiceImpl extends AbstractServiceImpl<Store> implements StoreService {

    @Autowired
    private StoreRepository storeRepository;
    @Autowired
    private StoreSpecificationBuilder specificationBuilder;

    @Override
    public StoreRepository getRepository() {
        return storeRepository;
    }

    @Override
    public SpecificationBuilder getSpecificationBuilder() {
        return specificationBuilder;
    }

    @Override
    public String getModuleKeyPrefix() {
        return MallModuleKeyPrefixes.INVEST_KEY_PREFIX_OF_STORE;
    }
}

3. 控制层

说完持久层和业务层,我们再说看看控制层是怎么对CRUD操作进行封装的。当然……还是以项目模块举例,先来看看项目模块控制层的代码:

@Api(value = "项目管理")
@RestController
@RequestMapping("/store")
public class StoreController extends AbstractSupportStateController<Store, UsingState> implements StoreApi {

    @Autowired
    private StoreService storeService;

    @Override
    public SupportStateService<UsingState> getSupportStateService() {
        return storeService;
    }

    @Override
    public CrudService<Store> getCrudService() {
        return storeService;
    }

    @Override
    public List<String> getSummaryFields() {
        return Arrays.asList(UsingState.using.name(), UsingState.disabled.name());
    }
}

是不是很意外,是不是很惊喜,没有看到一行CRUD的代码。来捋一捋类的继承关系:

  • AbstractSupportStateController

其中AbstractSupportStateController用来封装状态改变的操作,子类需要实现getSupportStateService()方法,还记得项目模块的业务层接口是怎么定义的吗?StoreService继承了SupportStateService来表示这是一个支持实体状态改变的业务。

public abstract class AbstractSupportStateController<T extends IsEntity, S extends Enum> extends AbstractController<T> {

    @PutMapping("/{uuid}")
    public ResponseResult changeState(@PathVariable("uuid") String uuid, @RequestParam("targetState") S targetState) {
        getSupportStateService().changeState(uuid, targetState);
        return new ResponseResult(CommonsResultCode.SUCCESS);
    }

    public abstract SupportStateService<S> getSupportStateService();
}
  • AbstractController

在这个抽象实现类中用来封装了控制层的CRUD操作,其中有一个很重要的抽象方法就是子类需要实现一个返回值为CrudService的方法:

public abstract CrudService<T> getCrudService();

至此,我们项目对CRUD的代码已经封装的差不多了,每个业务模块的各个层需要做的就是实现各种抽象方法和做个性化的实现。

相关文章

  • MiniMall:如何优雅地实现错综复杂的条件查询

    在上一篇《MiniMall:CRUD的代码是不可能写得?是的,我都帮你写好了》博客中,我们主要分析了MiniMal...

  • MiniMall:CRUD的代码是不可能写得?是的,我都帮你写好

    虽然从整个项目的角度来说,我们把整个项目拆成了一个个微服务,这一点是有别于单体应用的。但是从一个业务模块本身的实现...

  • 2018-01-21

    1.思考如何写好代码。 自己可能写过的代码少,思维可能有局限。所以对于代码如何能够写得更好。没有改进的思路。 对于...

  • #prama mark的作用

    善用#waring,#pragma mark 标记 在项目开发中,我们不可能对着需求一口气将代码都写好。开发过程中...

  • 0529 表白文

    一明校长,照我这种每天闲暇就写一篇文章的速度,我感觉,今后绝对不可能没有内容可说了。我打算是,先写,再写好,再写得...

  • <读书笔记>编写整洁代码 10: 类的设计三原则

    之前的部分都是在讲如何将代码单元写得更好, 从现在开始, 就来看看如何将代码单元层面以上的内容写好. 对于大多数系...

  • 零基础学硬笔(2)——坐姿

    每个人都希望写得一手好字,因为从某种程度上说,它是我们的脸面。 不管是自己想写好字也好,还是希望自己的孩子写好字也...

  • 不仅要写出漂亮的代码,也要写出漂亮的注释

    几乎所有的软件工程师,都追求写出漂亮的代码,但是在学如何写好代码的同时,却很少有人关注写好代码注释的重要性,有的工...

  • Mybatis的CRUD代码生成

    近期准备做一下Mybatis的源码解读,整个系列大概会有6-7篇文章。先释放一下目录:1.Mybatis框架组件设...

  • 硬笔挑战前30天小结

    吾日三省吾字:以前写不好的字现在能写好了吗?以前能写好的字现在能写得更好吗?新学的字都完全搞定了吗? 我进挑战营的...

网友评论

      本文标题:MiniMall:CRUD的代码是不可能写得?是的,我都帮你写好

      本文链接:https://www.haomeiwen.com/subject/bawqwhtx.html