虽然从整个项目的角度来说,我们把整个项目拆成了一个个微服务,这一点是有别于单体应用的。但是从一个业务模块本身的实现,不管是单体应用架构还是微服务架构,都会有控制层、业务层、持久层,这一点是不会变的。而我们今天要说的,就是针对每一层的增删改查操作进行架构封装,这几个常见操作的实现基本上都是一样的,实在没有必要在每个业务模块里写重复的代码,我们也不是这样的程序员对不对?
友情提示:看本篇博客,最好结合源码一起来。每次访问github比较慢的,可以将源码检出下载到本地。
1. 持久层
因为我们的持久层使用的是Spring Data JPA
框架实现,站在巨人的肩膀上,自然也就没我们啥事了。Spring Data JPA
提供核心的接口Repository
来表示持久层的接口定义,该接口有很多的子接口,其中就有一个CrudRepository
接口,里面定义了常见的CRUD
操作,比如save
、saveAll
、findById
等,具体如下:
@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);
}
而在我们的项目中,通常一个业务模块有搜索界面、新建编辑界面、查看界面,也就表示一个模块的持久化接口往往需要实现CrudRepository
和JpaSpecificationExecutor
两个接口,也正是因为如此,在我们的项目持久层中,提供了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
进行封装外,我们还提供了CacheService
和SupportStateService
两个接口分别用于支持缓存和状态改变操作(基础资料是有状态的,比如项目有使用中和已停用两种状态)。其代码分别如下:
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
的实现进行封装,这才是我们的重点,否则只是简简单单地对接口进行封装,实际上其意义并不大。
封装后的业务层接口实现类,是一个抽象类。该实现类实现了CrudService
和CacheService
两个接口,也即表示对于大部分的业务模块来说,都是支持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
的代码。来捋一捋类的继承关系:
![](https://img.haomeiwen.com/i22925006/c09f7426dabecc6f.png)
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
的代码已经封装的差不多了,每个业务模块的各个层需要做的就是实现各种抽象方法和做个性化的实现。
![](https://img.haomeiwen.com/i22925006/66d4188c43232ea4.png)
网友评论