相关插件
Alibaba Java Coding Guidelines
这是一个代码审查工具,能够对Java代码进行实时检测,包括不限于是否符合javadoc注解格式、各类命名规范、方法名驼峰要求、以及静态final字段下划线大写等。
不符合规范的用法及命名将会用高亮或下划线展示。
该插件审查均符合阿里巴巴代码规约中的各项要求,可放心大胆使用。
EasyYapi
这是一个基于javadoc&KDoc&ScalaDoc规范并将编写的controller接口自动化生成接口文档,推送到yapi平台的管理工具。
下载插件后,打开设置,选择其他设置->EasyApi
将我们需要发布的yapi地址填入
转到controller层,右键任意位置,选择EasyApi->Export Yapi
将token输入到输入框中,点击确定即可一键发布到Yapi中。
token可到,yapi设置->token配置中找到
Mevan Helper
分析和解决pom冲突依赖关系的插件。
如图,标记出了mybatis-spring-boot-starter与mybatis的冲突。
好用的代码
Mybatis Code Generator
这是mybatis mapper、po、*mapper.xml的自动生成工具
<!-- MyBatis Generator -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.2</version>
</dependency>
配置MybatisPlugin插件
/**
* Mybatis Generator拓展插件
*
* @author xxxxxx
* @since 2024/7/28
*/
public class MybatisPlugin extends PluginAdapter {
public static final DateTimeFormatter DATE_STANDARD_FORMAT = DateTimeFormatter.ofPattern("yyyy/MM/dd");
public static final String AUTHOR = "mybatis generator";
/**
* 查询结果映射到的ResultMap中需要忽视的列
*/
public static final List<String> RESULT_MAP_IGNORED = Lists.newArrayList("create_time", "update_time", "is_delete");
/**
* 是否设置逻辑删除
*/
private static final Boolean TOMBSTONE = true;
/**
* 逻辑删除字段名,设置了逻辑删除时会调用
*/
private static final String TOMBSTONE_FIELD_STRING = "is_delete";
/**
* 是否启用
*/
@Override
public boolean validate(List<String> list) {
return true;
}
/**
* 生成实体类控制函数
*
* @apiNote 添加头部注释和注解
*/
@Override
public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
// 添加实体类的 import
topLevelClass.addImportedType("lombok.Data");
topLevelClass.addImportedType("lombok.NoArgsConstructor");
topLevelClass.addImportedType("lombok.AllArgsConstructor");
topLevelClass.addJavaDocLine("/**");
topLevelClass.addJavaDocLine(" * " + introspectedTable.getRemarks());
topLevelClass.addJavaDocLine(" *");
topLevelClass.addJavaDocLine(" * @author " + AUTHOR);
topLevelClass.addJavaDocLine(" * @since " + LocalDate.now().format(DATE_STANDARD_FORMAT));
topLevelClass.addJavaDocLine(" */");
// 添加实体类的 lombok 注解
topLevelClass.addAnnotation("@Data");
topLevelClass.addAnnotation("@NoArgsConstructor");
topLevelClass.addAnnotation("@AllArgsConstructor");
return true;
}
/**
* 生成实体成员变量控制函数
*
* @apiNote 添加实体成员变量的注释、将boolean还原为byte、区分datetime/date/time/timestamp转的Date
*/
@Override
public boolean modelFieldGenerated(Field field,
TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn,
IntrospectedTable introspectedTable,
Plugin.ModelClassType modelClassType) {
//将boolean还原为byte
if (!TINYINT_CONVERT_BOOLEAN && field.getType().equals(new FullyQualifiedJavaType("java.lang.Boolean"))) {
field.setType(new FullyQualifiedJavaType("java.lang.Byte"));
} else if (field.getType().equals(new FullyQualifiedJavaType("java.util.Date"))) {
//区分datetime、date、time、timestamp转的Date
switch (introspectedColumn.getActualTypeName()) {
case "DATETIME":
case "TIMESTAMP":
field.setType(new FullyQualifiedJavaType("java.time.LocalDateTime"));
break;
case "TIME":
field.setType(new FullyQualifiedJavaType("java.time.LocalTime"));
break;
case "DATE":
field.setType(new FullyQualifiedJavaType("java.time.LocalDate"));
break;
default:
break;
}
}
//生成注释
field.addJavaDocLine("/**");
field.addJavaDocLine(" * " + introspectedColumn.getRemarks());
field.addJavaDocLine(" */");
return true;
}
/**
* 实体类setter生成控制函数
*/
@Override
public boolean modelSetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
// 不生成 setter
return false;
}
/**
* 实体类getter生成控制函数
*/
@Override
public boolean modelGetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
// 不生成 getter
return false;
}
/**
* xml中的deleteByPrimaryKey控制函数
*
* @apiNote 增加逻辑删除功能
*/
@Override
public boolean sqlMapDeleteByPrimaryKeyElementGenerated(XmlElement element,
IntrospectedTable introspectedTable) {
if (TOMBSTONE) {
element.setName("update");
List<VisitableElement> elements = element.getElements();
//清空XmlElement的节点数据
elements.clear();
//增加update from #{table}行
StringBuilder sb = new StringBuilder();
sb.append("update from ");
sb.append(introspectedTable.getFullyQualifiedTableNameAtRuntime());
element.addElement(new TextElement(sb.toString()));
//增加set行
sb = new StringBuilder();
sb.append("set ");
sb.append(TOMBSTONE_FIELD_STRING);
sb.append(" = 1");
element.addElement(new TextElement(sb.toString()));
//增加where行
sb = new StringBuilder();
sb.append("where ");
List<IntrospectedColumn> primaryKeyColumns = introspectedTable.getPrimaryKeyColumns();
for (IntrospectedColumn primaryKeyColumn : primaryKeyColumns) {
sb.append(primaryKeyColumn.getActualColumnName());
sb.append(" = ");
sb.append(MyBatis3FormattingUtilities
.getParameterClause(primaryKeyColumn));
sb.append(" and ");
}
//去除末尾的and
sb.delete(sb.length() - 5, sb.length());
element.addElement(new TextElement(sb.toString()));
}
//如果为false,则不生成deleteByPrimaryKey
return true;
}
/**
* mapper中deleteByPrimaryKey方法控制函数
*
* @apiNote 增加方法注释
*/
@Override
public boolean clientDeleteByPrimaryKeyMethodGenerated(Method method,
Interface interfaze, IntrospectedTable introspectedTable) {
method.addJavaDocLine("/**");
method.addJavaDocLine(" * 根据主键删除数据");
addJavaDoc4PrimaryKeyMethod(method, introspectedTable);
method.addJavaDocLine(" * @return 是否删除成功");
method.addJavaDocLine(" * @author " + AUTHOR);
method.addJavaDocLine(" * @since " + LocalDate.now().format(DATE_STANDARD_FORMAT));
method.addJavaDocLine(" */");
return true;
}
/**
* xml中的SelectByPrimaryKey控制函数
*
* @apiNote 增加逻辑删除功能
*/
@Override
public boolean sqlMapSelectByPrimaryKeyElementGenerated(XmlElement element,
IntrospectedTable introspectedTable) {
if (TOMBSTONE) {
element.addElement(new TextElement(String.format(" and %s = 0", TOMBSTONE_FIELD_STRING)));
}
return true;
}
/**
* mapper中selectByPrimaryKey方法控制函数
*
* @apiNote 增加方法注释
*/
@Override
public boolean clientSelectByPrimaryKeyMethodGenerated(Method method,
Interface interfaze, IntrospectedTable introspectedTable) {
method.addJavaDocLine("/**");
method.addJavaDocLine(" * 根据主键查找数据");
addJavaDoc4PrimaryKeyMethod(method, introspectedTable);
method.addJavaDocLine(" * @return 实体结果");
method.addJavaDocLine(" * @author " + AUTHOR);
method.addJavaDocLine(" * @since " + LocalDate.now().format(DATE_STANDARD_FORMAT));
method.addJavaDocLine(" */");
return true;
}
/**
* 给相关PrimaryKeyMethod增加javadoc
*/
private void addJavaDoc4PrimaryKeyMethod(Method method, IntrospectedTable introspectedTable) {
method.addJavaDocLine(" *");
for (int i = 0; i < method.getParameters().size(); i++) {
Parameter parameter = method.getParameters().get(i);
IntrospectedColumn column = introspectedTable.getPrimaryKeyColumns().get(i);
method.addJavaDocLine(" * @param " + parameter.getName() + " " + column.getRemarks());
}
}
/**
* mapper中insert方法控制函数
*
* @apiNote 增加方法注释
*/
@Override
public boolean clientInsertMethodGenerated(Method method, Interface interfaze,
IntrospectedTable introspectedTable) {
method.addJavaDocLine("/**");
method.addJavaDocLine(" * 向数据库中插入数据");
addJavaDoc4DaoParamMethod(method, introspectedTable);
method.addJavaDocLine(" */");
return true;
}
/**
* 给入参为Dao的相关方法增加javadoc,如insert、update等
*/
private void addJavaDoc4DaoParamMethod(Method method, IntrospectedTable introspectedTable) {
method.addJavaDocLine(" *");
Parameter parameter = method.getParameters().get(0);
method.addJavaDocLine(" * @param " + parameter.getName() + " " + introspectedTable.getRemarks() + "实体");
method.addJavaDocLine(" * @author " + AUTHOR);
method.addJavaDocLine(" * @since " + LocalDate.now().format(DATE_STANDARD_FORMAT));
}
/**
* mapper中insertSelective方法控制函数
*
* @apiNote 增加方法注释
*/
@Override
public boolean clientInsertSelectiveMethodGenerated(Method method, Interface interfaze,
IntrospectedTable introspectedTable) {
method.addJavaDocLine("/**");
method.addJavaDocLine(" * 可选择地设置实体字段属性,插入到数据库中");
addJavaDoc4DaoParamMethod(method, introspectedTable);
method.addJavaDocLine(" */");
return true;
}
/**
* mapper中updateByPrimaryKeySelective方法控制函数
*
* @apiNote 增加方法注释
*/
@Override
public boolean clientUpdateByPrimaryKeySelectiveMethodGenerated(Method method,
Interface interfaze, IntrospectedTable introspectedTable) {
method.addJavaDocLine("/**");
method.addJavaDocLine(" * 根据主键可选择地更新数据库中的相应字段");
addJavaDoc4DaoParamMethod(method, introspectedTable);
method.addJavaDocLine(" */");
return true;
}
/**
* mapper中updateByPrimaryKey方法控制函数
*
* @apiNote 增加方法注释
*/
@Override
public boolean clientUpdateByPrimaryKeyWithoutBLOBsMethodGenerated(Method method, Interface interfaze,
IntrospectedTable introspectedTable) {
method.addJavaDocLine("/**");
method.addJavaDocLine(" * 根据主键更新数据库中的相应行");
addJavaDoc4DaoParamMethod(method, introspectedTable);
method.addJavaDocLine(" */");
return true;
}
/**
* mapper控制函数
*
* @apiNote 增加头部注释
*/
@Override
public boolean clientGenerated(Interface interfaze,
IntrospectedTable introspectedTable) {
interfaze.addJavaDocLine("/**");
interfaze.addJavaDocLine(" * " + introspectedTable.getRemarks() + "映射接口");
interfaze.addJavaDocLine(" *");
interfaze.addJavaDocLine(" * @author " + AUTHOR);
interfaze.addJavaDocLine(" * @since " + LocalDate.now().format(DATE_STANDARD_FORMAT));
interfaze.addJavaDocLine("*/");
return true;
}
/**
* xml中Base_Column_List控制函数
*
* @apiNote 增加查询时根据RESULT_MAP_IGNORED忽略相应的列
*/
@Override
public boolean sqlMapBaseColumnListElementGenerated(XmlElement element,
IntrospectedTable introspectedTable) {
List<VisitableElement> elements = element.getElements();
List<VisitableElement> newElements = Lists.newArrayList();
for (VisitableElement e : elements) {
String content = ((TextElement) e).getContent();
List<String> list = Splitter.on(',')
.trimResults()
.omitEmptyStrings()
.splitToList(content)
.stream()
.filter(a -> !RESULT_MAP_IGNORED.contains(a))
.collect(Collectors.toList());
newElements.add(new TextElement(Joiner.on(", ").join(list)));
}
elements.clear();
elements.addAll(newElements);
return true;
}
/**
* 是否覆盖原有xml
*/
@Override
public boolean sqlMapGenerated(GeneratedXmlFile sqlMap,
IntrospectedTable introspectedTable) {
sqlMap.setMergeable(false);
return true;
}
}
配置mybatis generator的配置文件generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="MyBatisGenerator" targetRuntime="MyBatis3">
<!-- 整合 Mybatis 插件 -->
<plugin type="com.xxxxxxx.common.MybatisPlugin" />
<!-- 表名前后加上''的分隔符,部分数据库可能需要使用 -->
<!-- <property name="beginningDelimiter" value="`" />-->
<!-- <property name="endingDelimiter" value="`" />-->
<commentGenerator>
<!--不要注释 -->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--这里是配置数据库链接 -->
<jdbcConnection driverClass="你的数据库驱动"
connectionURL="你的jdbcURL"
userId="数据库账号"
password="数据库密码">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--这里是配置po -->
<javaModelGenerator targetPackage="com.xxxxxx.po" targetProject="D:\Java\xxxxxxx\src\main\java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
<!-- 这个是生成构造函数的,false没有构造函数但有setter和getter -->
<!-- 用lombok后就不需要了 -->
<!-- <property name="constructorBased" value="true"/>-->
<!-- 这个是生成构造函数的,true则只有构造函数没有setter和getter -->
<!-- <property name="immutable" value="true"/>-->
</javaModelGenerator>
<!--这里是配置mapper.xml -->
<sqlMapGenerator targetPackage="mybatis.mapper" targetProject="D:\Java\xxxxxx\src\main\resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!--这里是配置mapper -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.xxxxxxx.mapper.base"
targetProject="D:\Java\xxxxxx\src\main\java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!--这里是数据库中的表,domainObjectName是要生成的类名,mapperName是要生成的Mapper以及Mapper.xml的名字,Example全设为false -->
<table tableName="T_USER" domainObjectName="UserPO" mapperName="UserBaseMapper"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
</context>
</generatorConfiguration>
运行generator
public class MybatisGenerator {
public static void main(String[] args) throws Exception {
List<String> warnings = new ArrayList<>();
boolean overwrite = true;
File file = new ClassPathResource("generatorConfig.xml").getFile();
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(file);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}
}
运行效果(展示图仅为样板,图片是旧的,具体见自己生成的代码),建议将生成的mapper放到mapper.base
文件夹下,在mapper文件夹下新建子类Mapper并继承相应的父类BaseMapper,需要添加的接口放在子类Mapper中。子类的接口映射到的xml应为新建的Mapper.xml文件。调用时,使用子类Mapper,这样父类的方法也可以正常调用。
Mybatis分页插件-PageHelper
PageHelper是通过interceptor对mybatis查询进行分页的插件。
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>6.1.0</version>
</dependency>
@Configuration
public class PageHelperConfig {
@Bean
public Interceptor[] plugins() {
return new Interceptor[]{new PageInterceptor()};
}
}
分页使用
int currentPage = 1;
int pageSize = 3;
Page<EmployeeDao> page = new Page<>();
page.setCurrentPage(currentPage);
page.setPageSize(pageSize);
//分页查找员工信息
PageHelper.startPage(currentPage, pageSize);
List<EmployeeDao> list = employeeMapper.selectAllSelective(new EmployeeDao());
page.setTotalSize((int) ((com.github.pagehelper.Page<?>) list).getTotal());
查询结果
Mybatis-plus Code Generator
这是mybatis-plus mapper、repository、po、*mapper.xml的自动生成工具
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
public class MybatisPlusCodeGenerator {
private final static String PARENT_PACKAGE_NAME = "com.你的项目包路径.你的项目包路径";
private final static String OUT_DIR = "/src/main";
public static final String DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver";
private static final String URL = "jdbc:mysql://localhost:3306/你的数据库?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai";
private static final String USERNAME = "你的数据库用户名";
public static final String PASSWORD = "你的数据库密码";
/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8.name());
System.out.println("请输入" + tip + ":");
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + OUT_DIR + "/java");
gc.setAuthor("mybatisplus generator");
gc.setOpen(false);
//实体属性 Swagger2 注解
// gc.setSwagger2(true);
gc.setFileOverride(true);
gc.setEntityName("%sPO");
gc.setServiceName("%sRepository");
gc.setServiceImplName("%sRepositoryImpl");
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl(URL);
dsc.setDriverName(DRIVER_CLASS_NAME);
dsc.setUsername(USERNAME);
dsc.setPassword(PASSWORD);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig("/templates/mapper.xml.vm") {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
String name = projectPath + OUT_DIR + "/resources/mybatis/mapper/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
return name.replace("POMapper", "Mapper");
}
});
cfg.setFileOutConfigList(focList);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
templateConfig.setController(null);
// 策略配置
StrategyConfig strategy = new StrategyConfig()
.setEntityColumnConstant(true)
.setEntityLombokModel(true)
.setRestControllerStyle(false)
//不生成id字段
// .setSuperEntityColumns("id")
.setInclude(scanner("表名,多个英文逗号分割").split(","))
.setControllerMappingHyphenStyle(false)
// .setEntitySerialVersionUID(false)
.setEntityBooleanColumnRemoveIsPrefix(true)
.setEntityTableFieldAnnotationEnable(true)
//表名下划线对应驼峰
.setNaming(NamingStrategy.underline_to_camel)
//设定表前缀,加上该配置后,会在表上自动生成@TableName注解
.setTablePrefix("t_")
.setColumnNaming(NamingStrategy.underline_to_camel);
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent(PARENT_PACKAGE_NAME);
packageConfig.setEntity("po");
packageConfig.setMapper("mapper");
packageConfig.setService("repository");
packageConfig.setServiceImpl("repository.impl");
new AutoGenerator().setGlobalConfig(gc)
.setDataSource(dsc)
.setCfg(cfg)
.setStrategy(strategy)
.setTemplate(templateConfig)
.setTemplateEngine(new VelocityTemplateEngine())
.setPackageInfo(packageConfig)
.execute();
}
}
一般table命名请使用
t
表
名
格式,启动代码后,在控制台中输入
{t_表名}格式,启动代码后,在控制台中输入
t表名格式,启动代码后,在控制台中输入{t_表名}(只是举例,于下面生成的文件不一致,无碍)
代码将会自动解析并生成相应的文件。
mapstruct
MapStruct 简单来说就是一个属性映射工具,主要用于解决数据模型之间不通用的情况。
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
<scope>provided</scope>
</dependency>
MapStruct在映射时,默认将会匹配两个类中相同字段名的属性,并在编译时写入target中,如同lombok,MapStruct也可以在pom里写为<scope>provided</scope>
,即只在编译和测试阶段起作用。
注明接口时,只需要在接口类上注上@Mapper(componentModel = "spring")
注解,标明此类里的所有接口都需要MapStruct生成且需要spring统一管理,然后将需要转化的接口写好即可。
使用时如所有接口类,可使用@Autowired
注解调用。
全局异常捕捉
全局异常捕捉主要为了防止运行异常没有try{……}catch(){……}导致整个APP崩溃、@Valid校验捕捉等。
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理参数异常
*
* @param ex 参数异常
* @return 格式化请求结果
* @author xxxxxxx
* @since 2024/7/23
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult exceptions = ex.getBindingResult();
// 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
if (exceptions.hasErrors()) {
List<ObjectError> errors = exceptions.getAllErrors();
if (!errors.isEmpty()) {
for (ObjectError error : errors) {
log.warn("参数请求异常:{}", error.getDefaultMessage());
}
FieldError fieldError = (FieldError) errors.get(0);
return Result.error(fieldError.getDefaultMessage());
}
}
log.error("运行出错", ex);
return Result.error("运行出错,请联系管理员");
}
/**
* 处理其他异常
*
* @param e 异常
* @return 格式化请求结果
* @author xxxxxxx
* @since 2024/7/23
*/
@ExceptionHandler(Exception.class)
public Result<String> handleException(Exception e) {
log.error("运行出错", e);
return Result.error("运行出错,请联系管理员");
}
}