spring boot 应用(二)
文章目录
- spring boot 应用(二)
- 数据库版本管理
- ROM框架 - mybatis plus
- 实际使用
- CURD 接口
- 代码生成
- 对象转换 - mapstruct
- BeanUtil.copyProperties
- MapStruct
- 全局 API 响应处理
- 第三方接口调用 - forest
时过一年,在自己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 依赖
<properties>
<mybatis.plus.version>3.4.0</mybatis.plus.version>
</properties>
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
spring boot 配置
@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 配置
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<>
/**
* <p>
* 参数映射表 Mapper 接口
* </p>
*
* @author liurui
* @since 2021-01-20
*/
public interface ParameterComparisionMapper extends BaseMapper<ParameterComparision> {
}
提供的接口够项目初期简单的 curd 使用,这里主要的还是说说代码生成器这一块,貌似现在 tk-mapper 也继承代码生成器,感兴趣的人可以自行研究。
分页也比较简单,直接使用自带的Page类,传入查找方法就行。还能顺便直接查询总数,是个很方便的实现。
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@Override
public List<ParameterComparision> getParameterComparisionByType(int type, Page page) {
QueryWrapper<ParameterComparision> wrapper = new QueryWrapper<>();
wrapper.eq((type != -1), "parameter_type", type);
baseMapper.selectPage(page, wrapper);
return page.getRecords();
}
代码生成
maven
<!--添加 代码生成器 依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.1.1</version>
</dependency>
<!--模板引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
实现代码
@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方法来设置;后一种问题可以跳过空字段来进行赋值。
/**
* 获取需要忽略的属性
*
* @param source
* @return
*/
public static String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> 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
<properties>
<mapstruct.version>1.3.0.Final</mapstruct.version>
</properties>
<!--mapstruct-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<!-- 指定jdk -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
这里需要注意的是,如果项目使用了 lombok 的注解,否则 mapstruct 会判断没有 get和set 方法,需要在编译的时候指定使用 lombok
使用
@Mapper(componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface PageVOConverter {
PageVOConverter INSTANCE = Mappers.getMapper(PageVOConverter.class);
/**
* <p>视图分页转换</p>
* <p>@Author liurui</p>
* <p>@Description 视图分页转换</p>
* <p>@Date 10:59 2021/1/23</p>
*
* @param vo
* @return com.baomidou.mybatisplus.extension.plugins.pagination.Page
**/
Page pageParam2Page(PageParamVO vo);
/**
* <p>分页结果转视图类</p>
* <p>@Author liurui</p>
* <p>@Description 分页结果转视图类</p>
* <p>@Date 11:03 2021/1/23</p>
*
* @param
* @return com.newgrand.crawler.web.vo.PageParamVO
**/
PageParamVO page2PageParam(Page page);
}
psvm() {
PageParamVO vo = PageVOConverter.INSTANCE.page2PageParam(page1);
}
使用起来非常方便,如果存在不一样的字段名,可以用 @Mapping() 来进行映射。具体使用可以自行查找或底下留言。
全局 API 响应处理
过去前后端响应内容都是项目封装好的 response 类,响应 code 都一样,调用方法就一直重复写代码,例如:
/**
* 工人管理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
的响应内容,身为一个懒惰的程序员,这样现象不可容忍。
自定义统一响应注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Controller
@ResponseBody
public @interface ResponseResultBody {
}
实现注解内容
@Slf4j
@RestControllerAdvice
public class ResponseResultBodyAdvice implements ResponseBodyAdvice<Object> {
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 方法调用第三方接口。