spring boot 应用(二) 2021-01-25 程序之旅,记录 暂无评论 643 次阅读 ## spring boot 应用(二) [TOC] 时过一年,在自己spring boot 的项目使用中有了一些小小的改善,添加了一些 数据库和第三方使用请求的框架,用于提高开发的效率。这里只是泛泛的说明使用,不做详细的解说。 ### 数据库版本管理 比较常见的数据库版本管理是 flyway 和 liquibase,flyway 比较简单方便,推荐使用。 ### ROM框架 - mybatis plus mybatis plus 官网地址:https://baomidou.com/ 开源地址:https://github.com/baomidou/mybatis-plus 对比 tk-mapper(通过mapper)的开源:https://github.com/abel533/Mapper mybatis plus的star数量比tk-mapper多 4.1 k(2021年1月25日),社区活跃度也是mybatis plus 比较高。 实际使用中的情况,个人觉得mybatis plus 比 tk-mapper好用许多。 maven 依赖 ```xml 3.4.0 com.baomidou mybatis-plus-boot-starter ${mybatis.plus.version} ``` spring boot 配置 ```java @Configuration // flyway 可以不要 @DependsOn("flywayConfig") public class MybatisPlusConfig { /** * 分页插件 * @Author liurui * @Description 分页插件 * @Date 9:53 2021/1/19 * @param * @return com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor **/ @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } } ``` yaml 配置 ```yaml mybatis-plus: global-config: db-config: select-strategy: not_empty mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl auto-mapping-behavior: full dbType: mysql ``` - select-strategy:select entity 查找的时候不查找空数据 - mapper-locations:mapper 路径 - map-underscore-to-camel-case:为true来开启驼峰功能 - dbType:数据库类型 #### 实际使用 *.xml 文件与 mybatis一样,主要比较常用的是 CRUD 接口、条件构造器、分页插件。 ##### CURD 接口 文件也有很详细的说明,https://baomidou.com/guide/crud-interface.html#mapper-crud-%E6%8E%A5%E5%8F%A3,直接在接口后加 `BaseMapper<>` ```java /** * * 参数映射表 Mapper 接口 * * * @author liurui * @since 2021-01-20 */ public interface ParameterComparisionMapper extends BaseMapper { } ``` 提供的接口够项目初期简单的 curd 使用,这里主要的还是说说代码生成器这一块,貌似现在 tk-mapper 也继承代码生成器,感兴趣的人可以自行研究。 分页也比较简单,直接使用自带的Page类,传入查找方法就行。还能顺便直接查询总数,是个很方便的实现。 ```java import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @Override public List getParameterComparisionByType(int type, Page page) { QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq((type != -1), "parameter_type", type); baseMapper.selectPage(page, wrapper); return page.getRecords(); } ``` ##### 代码生成 maven ```xml com.baomidou mybatis-plus-generator 3.1.1 org.apache.velocity velocity-engine-core 2.1 org.freemarker freemarker 2.3.28 ``` 实现代码 ```java @Slf4j public class CodeGenerator { public static final String DB_URL = "jdbc:mysql://localhost:3306/crawler_platform?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true"; public static final String USER_NAME = "root"; public static final String PASSWORD = "root"; public static final String DRIVER = "com.mysql.cj.jdbc.Driver"; public static final String AUTHOR = "liurui"; //生成的文件输出到哪个目录 public static final String OUTPUT_FILE = System.getProperty("user.dir") + "/crawler-platform-mbg/src/main/java"; //包名,会按照com/example/demo这种形式生成类 public static final String PACKAGE = "com.***.crawler.core"; public void generateByTables(boolean serviceNameStartWithI, String... tableNames) { GlobalConfig config = new GlobalConfig(); DataSourceConfig dataSourceConfig = new DataSourceConfig(); dataSourceConfig.setDbType(DbType.MYSQL) .setUrl(DB_URL) .setUsername(USER_NAME) .setPassword(PASSWORD) .setDriverName(DRIVER); StrategyConfig strategyConfig = new StrategyConfig(); strategyConfig .setCapitalMode(true) // 添加 lombok 依赖 .setEntityLombokModel(true) // .setDbColumnUnderline(true) .setNaming(NamingStrategy.underline_to_camel) .setInclude(tableNames);//修改替换成你需要的表名,多个表名传数组 config.setActiveRecord(false) .setAuthor(AUTHOR) .setOutputDir(OUTPUT_FILE) .setOpen(false) // xml 生成resultmap 标签 .setBaseResultMap(true) .setBaseColumnList(true) .setSwagger2(true) .setFileOverride(true); if (!serviceNameStartWithI) { config.setServiceName("%sService"); } new AutoGenerator().setGlobalConfig(config) .setDataSource(dataSourceConfig) .setStrategy(strategyConfig) .setPackageInfo( new PackageConfig() .setParent(PACKAGE) .setController("controller") .setEntity("entity") ).execute(); } public static void main(String[] args) { CodeGenerator gse = new CodeGenerator(); //要给那些表生成 gse.generateByTables(true, "upload_record", "sync_record"); } } ``` 运行后直接生成 controller、service、entity和mapper等代码实体,简化了许多配置,推荐使用。 ### 对象转换 - mapstruct POJO的各种 PO、VO、BO、DTO对象,不免会互相之间传输成员参数内容,这里有两种传输参数的方法 #### BeanUtil.copyProperties 直接使用 spring 框架中的 BeanUtil 工具,根据参数名拷贝参数,但是拷贝的过程中会出现部分参数名不一致导致不能完全拷贝全,并且会存在空值覆盖的问题。前一种问题只能一一对应的使用set方法来设置;后一种问题可以跳过空字段来进行赋值。 ```java /** * 获取需要忽略的属性 * * @param source * @return */ public static String[] getNullPropertyNames (Object source) { final BeanWrapper src = new BeanWrapperImpl(source); PropertyDescriptor[] pds = src.getPropertyDescriptors(); Set emptyNames = new HashSet<>(); for(PropertyDescriptor pd : pds) { Object srcValue = src.getPropertyValue(pd.getName()); // 此处判断可根据需求修改 if (srcValue == null) { emptyNames.add(pd.getName()); } else if (Number.class.isAssignableFrom(pd.getPropertyType()) || Integer.TYPE.isAssignableFrom(pd.getPropertyType()) || srcValue.equals(0)) { emptyNames.add(pd.getName()); } } String[] result = new String[emptyNames.size()]; return emptyNames.toArray(result); } psvm() { // 跳过空字段 BeanUtils.copyProperties(workerEntity, workerdto, BeanUtil.getNullPropertyNames(workerEntity)); } ``` #### MapStruct 接口的形式自定义转换规则 maven ```xml 1.3.0.Final org.mapstruct mapstruct ${mapstruct.version} org.mapstruct mapstruct-processor ${mapstruct.version} org.apache.maven.plugins maven-compiler-plugin ${java.version} ${java.version} UTF-8 org.projectlombok lombok ${lombok.version} org.mapstruct mapstruct-processor ${mapstruct.version} ``` > 这里需要注意的是,如果项目使用了 lombok 的注解,否则 mapstruct 会判断没有 get和set 方法,需要在编译的时候指定使用 lombok 使用 ```java @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) public interface PageVOConverter { PageVOConverter INSTANCE = Mappers.getMapper(PageVOConverter.class); /** * 视图分页转换 * @Author liurui * @Description 视图分页转换 * @Date 10:59 2021/1/23 * * @param vo * @return com.baomidou.mybatisplus.extension.plugins.pagination.Page **/ Page pageParam2Page(PageParamVO vo); /** * 分页结果转视图类 * @Author liurui * @Description 分页结果转视图类 * @Date 11:03 2021/1/23 * * @param * @return com.newgrand.crawler.web.vo.PageParamVO **/ PageParamVO page2PageParam(Page page); } psvm() { PageParamVO vo = PageVOConverter.INSTANCE.page2PageParam(page1); } ``` 使用起来非常方便,如果存在不一样的字段名,可以用 @Mapping() 来进行映射。具体使用可以自行查找或底下留言。 ### 全局 API 响应处理 过去前后端响应内容都是项目封装好的 response 类,响应 code 都一样,调用方法就一直重复写代码,例如: ```java /** * 工人管理controller * * @author liurui */ @Controller @Api(tags = "工人管理") @Slf4j public class WorkerController { @GetMapping(value = "/**") public Result A(){ ... return ResultGenerator.genSuccessResult(result); } @GetMapping("/***") public Result B(){ .... return ResultGenerator.genSuccessResult(result); } @GetMapping(value = "/**") public Result C(){ ... return ResultGenerator.genSuccessResult(result); } @GetMapping("/***") public Result D(){ .... return ResultGenerator.genSuccessResult(result); } } ``` 出现多个 `ResultGenerator.genSuccessResult` 的响应内容,身为一个懒惰的程序员,这样现象不可容忍。 自定义统一响应注解 ```java @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Documented @Controller @ResponseBody public @interface ResponseResultBody { } ``` 实现注解内容 ```java @Slf4j @RestControllerAdvice public class ResponseResultBodyAdvice implements ResponseBodyAdvice { private static final Class extends Annotation> ANNOTATION_TYPE = ResponseResultBody.class; /** * 判断类或者方法是否使用了 @ResponseResultBody */ @Override public boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType) { return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE) || returnType.hasMethodAnnotation(ANNOTATION_TYPE); } /** * 当类或者方法使用了 @ResponseResultBody 就会调用这个方法 */ @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class extends HttpMessageConverter>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 防止重复包裹的问题出现 if (selectedContentType.getType().equals("image")) { return body; } if (body instanceof Result) { return body; } return ResultGenerator.genSuccessResult(body); } } ``` 往后的在 controller 的类名上加 `@ResponseResultBody` 就可以不需要重复写代码,想放回什么格式的内容都行。 ### 第三方接口调用 - forest http client 的一个开源工具,替代传统的 Post/Get 方法调用第三方接口。 --- ### 2021年7月13日 swagger-ui 的替换工具 knife4j,界面比原工具更加友好,还添加了全局参数、离线文档与个性化等功能,感兴趣的可以结合 spring boot 来进行调试使用。 ```xml com.github.xiaoymin knife4j-spring-boot-starter 2.0.4 io.springfox springfox-swagger-ui 2.9.2 ``` > 修改响应内容方法:在Swagger 配置中添加 ```java List responseMessageList = new ArrayList<>(); Arrays.stream(ResultCode.values()).forEach(errorEnums -> { responseMessageList.add( new ResponseMessageBuilder().code(errorEnums.getCode()).message(errorEnums.getMessage()).responseModel( new ModelRef(errorEnums.getMessage())).build() ); }); return new Docket(DocumentationType.SWAGGER_2) .globalResponseMessage(RequestMethod.GET, responseMessageList) .globalResponseMessage(RequestMethod.POST, responseMessageList) .globalResponseMessage(RequestMethod.PUT, responseMessageList) .globalResponseMessage(RequestMethod.DELETE, responseMessageList); ``` 使用 @ApiResponses({@ApiResponse(code=400,message="请求参数没填好"), @ApiResponse(code=404,message="请求路径没有或页面跳转路径不对") }) 来进行响应内容说明 打赏: 微信, 支付宝 标签: java, spring boot, mybatis 本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。