MyBatis-Plus(3.x)的使用
简介
Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。现在MP最新的版本已经升级到3.X.
参考官方文档
MybatisPlus的特性
- 无侵入:Mybatis-Plus 在 Mybatis 的基础上进行扩展,只做增强不做改变,引入 Mybatis-Plus 不会对您现有的Mybatis 构架产生任何影响,而且 MP 支持所有 Mybatis 原生的特性
- 依赖少:仅仅依赖 Mybatis 以及 Mybatis-Spring
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 预防Sql注入:内置 Sql 注入剥离器,有效预防Sql注入攻击
- 通用CRUD操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD操作,更有强大的条件构造器,满足各类使用需求
- 多种主键策略:支持多达4种主键策略(内含分布式唯一ID生成器),可自由配置,完美解决主键问题
- 支持热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
- 支持ActiveRecord:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可实现基本 CRUD 操作
- 支持代码生成:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 支持关键词自动转义:支持数据库关键词(order、key…)自动转义,还可自定义关键词
- 内置分页插件:基于 Mybatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询
- 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能有效解决慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,预防误操作
名词解释
GlobalConfig -> 指的是mp的全局配置文件, 版本2.X和 3.X的变化还是比较大, 可以直接看源码的注释
BaseMapper -> BaseMapper里面维护了许多常用的方法, 例如根据主键查询记录, 根据根据主键修改记录等等, 普通mapper只需要继承这个BaseMapper即可获得通用的方法
Wrapper -> 条件构造抽象类, 用来生成sql的条件
LogicSqlInjector -> 逻辑sql处理器, (逻辑删除)
PaginationInterceptor -> 分页插件, 底层是物理分页
代码生成器
代码生成器,又被叫做逆向工程,这个功能是非常强大,支持自定义配置,在MyBatis-Plus的官网文档中,有将代码生成器的配置详解,也有项目示例代码,
功能列表:
[✔] 自动生成model类
[✔] 自动生成dao接口
[✔] 自动生成xml文件
[✔] 自动生成service接口
[✔] 自动生成service实现类
[✔] model支持Builder模式
[✔] 支持swagger2
[✔] 支持生成数据库字段常量
[✔] 支持生成Kotlin代码
目录结构
Maven 配置
<dependencies>
<!-- 数据库相关 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.7.1</version>
</dependency>
<!-- 自动生成代码 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.0.7.1</version>
</dependency>
<!-- 生成模版 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
</dependencies>
全局配置
protected void config() {
/**
* 数据库配置
*/
dataSourceConfig.setDbType(DbType.MYSQL);
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUsername(genQo.getUserName());
dataSourceConfig.setPassword(genQo.getPassword());
dataSourceConfig.setUrl(genQo.getUrl());
// 自定义数据库表字段类型转换【可选】
dataSourceConfig.setTypeConvert(new MySqlTypeConvert() {
@Override
public IColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
//System.out.println("转换类型:" + fieldType);
if("datetime".equals(fieldType)){
//datetime转换成java.util.Date类型
globalConfig.setDateType(DateType.ONLY_DATE);
}
return super.processTypeConvert(globalConfig, fieldType);
}
});
/**
* 全局配置
*/
globalConfig.setOutputDir(genQo.getProjectPath() + File.separator + "src" + File.separator + "main" + File.separator + "java");
// 是否覆盖目录
globalConfig.setFileOverride(false);
globalConfig.setActiveRecord(true);
// 是否Swagger2注解
globalConfig.setSwagger2(true);
// 是否开启二级缓存
globalConfig.setEnableCache(false);
globalConfig.setBaseResultMap(true);
globalConfig.setBaseColumnList(true);
// 是否打开输出目录 默认true
globalConfig.setOpen(false);
// 主键策略
globalConfig.setIdType(genQo.getIdType());
// 作者
globalConfig.setAuthor(genQo.getAuthor());
/**
* 生成策略
*/
if (genQo.getIgnoreTabelPrefix() != null) {
strategyConfig.setTablePrefix(new String[]{genQo.getIgnoreTabelPrefix()});
}
if (genQo.getLogicDeleteFieldName() != null) {// 逻辑删除
strategyConfig.setLogicDeleteFieldName(genQo.getLogicDeleteFieldName());
}
if (genQo.getVersionFieldName() != null) {// 乐观锁
strategyConfig.setVersionFieldName(genQo.getVersionFieldName());
}
strategyConfig.setInclude(new String[]{genQo.getTableName()});
// strategy.setExclude(new String[]{"test"}); // 排除生成的表
// 表名生成策略
strategyConfig.setNaming(NamingStrategy.underline_to_camel);
// 字段名生成策略
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
//strategyConfig.setEntityTableFieldAnnotationEnable(true);
// 是否使用Lombok简化代码
strategyConfig.setEntityLombokModel(true);
strategyConfig.setRestControllerStyle(true);
// 自定义实体父类
// strategy.setSuperEntityClass("com.baomidou.demo.TestEntity");
// 自定义实体,公共字段
// strategy.setSuperEntityColumns(new String[] { "test_id", "age" });
// 自定义 mapper 父类
// strategy.setSuperMapperClass("com.baomidou.demo.TestMapper");
// 自定义 service 父类
// strategy.setSuperServiceClass("com.baomidou.demo.TestService");
// 自定义 service 实现类父类
// strategy.setSuperServiceImplClass("com.baomidou.demo.TestServiceImpl");
// 自定义 controller 父类
// strategy.setSuperControllerClass("com.baomidou.demo.TestController");
// 【实体】是否生成字段常量(默认 false)
// strategy.setEntityColumnConstant(true);
// 【实体】是否为构建者模型(默认 false)
// public User setName(String name) {this.name = name; return this;}
// strategy.setEntityBuilderModel(true);
/**
* 自定义参数配置
*/
contextConfig.setProPackage(genQo.getProjectPackage());
contextConfig.setCoreBasePackage(genQo.getCorePackage());
contextConfig.setBizChName(genQo.getBizName());
contextConfig.setModuleName(genQo.getModuleName());
contextConfig.setProjectPath(genQo.getProjectPath());
if(ToolUtil.isEmpty(genQo.getIgnoreTabelPrefix())){
String entityName = StrKit.toCamelCase(genQo.getTableName());
contextConfig.setEntityName(StrKit.firstCharToUpperCase(entityName));
contextConfig.setBizEnName(StrKit.firstCharToLowerCase(entityName));
}else{
String entiyName = StrKit.toCamelCase(StrKit.removePrefix(genQo.getTableName(), genQo.getIgnoreTabelPrefix()));
contextConfig.setEntityName(StrKit.firstCharToUpperCase(entiyName));
contextConfig.setBizEnName(StrKit.firstCharToLowerCase(entiyName));
}
contextConfig.init();
/**
* 自定义包目录
*/
packageConfig.setParent(null);
packageConfig.setEntity(contextConfig.getModelPackageName());
packageConfig.setMapper(contextConfig.getDaoMapperPackageName());
packageConfig.setXml(contextConfig.getDaoMapperPackageName() +".mapping");
packageConfig.setService(contextConfig.getServicePackageName());
packageConfig.setServiceImpl(contextConfig.getServicePackageName() + ".impl");
packageConfig.setController(contextConfig.getControllerPackageName());
/**
* mybatis-plus 生成器开关
*/
if (!genQo.getEntitySwitch()) {
packageConfig.setEntity("TTT");
}
if (!genQo.getDaoSwitch()) {
packageConfig.setMapper("TTT");
packageConfig.setXml("TTT");
}
if (!genQo.getServiceSwitch()) {
packageConfig.setService("TTT");
packageConfig.setServiceImpl("TTT");
}
if (!genQo.getControllerSwitch()) {
packageConfig.setController("TTT");
}
}
MP操作配置
/**
* MP生成代码
*/
public void doMpGeneration() {
config();
AutoGenerator autoGenerator = new AutoGenerator();
autoGenerator.setGlobalConfig(globalConfig);
autoGenerator.setDataSource(dataSourceConfig);
autoGenerator.setStrategy(strategyConfig);
autoGenerator.setPackageInfo(packageConfig);
// 注入自定义配置,可以在 VM 中使用 cfg.abc 设置的值
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
Map<String, Object> map = new HashMap<>();
map = ToolUtil.bean2Map(contextConfig, map);
this.setMap(map);
}
};
//自定义文件输出位置(非必须)
/*List<FileOutConfig> fileOutList = new ArrayList<>();
fileOutList.add(new FileOutConfig("/templates/list.html.vm") {
@Override
public String outputFile(TableInfo tableInfo) {
return contextConfig.getProjectPath() + "/src/main/resources/templates/" + tableInfo.getEntityName() + "List.html";
}
});
cfg.setFileOutConfigList(fileOutList)*/;
autoGenerator.setCfg(cfg);
autoGenerator.execute();
destory();
}
生成操作
/**
* 数据库信息
*/
private static final String database = "数据库名称";
private static final String url = "数据库地址" + database
+ "?useUnicode=true&useSSL=false&characterEncoding=utf8";
private static final String userName = "数据库用户名";
private static final String password = "数据库密码";
/**
* 生成代码主函数
*/
public static void main(String[] args) {
GenQo genQo = new GenQo();
genQo.setProjectPath("/E:/workspace/edu-parent/edu-homework/");//生成文件的本地路径
// AUTO:数据库ID自增、 NONE:未设置主键类型、 INPUT:用户输入id、 ID_WORKER:全局唯一id(IdWorker Long类型)、 UUID:全局唯一ID(uuid)、 ID_WORKER_STR:全局唯一id(IdWorker String类型)
genQo.setIdType(IdType.UUID);
genQo.setAuthor("winsun");//作者
genQo.setProjectPackage("com.winsun.affiliation");//核心包,不用修改
genQo.setCorePackage("com.winsun.affiliation.modular");//生成代码的路径
genQo.setModuleName("test");//生成代码的包名
genQo.setIgnoreTabelPrefix("sys_");//去掉的表前缀
genQo.setTableName("sys_user_test");//表名
genQo.setBizName("用户信息");//表注释
genQo.setLogicDeleteFieldName("delFlag");//逻辑删除字段
/** dao的开关 */
genQo.setDaoSwitch(true);
/** service */
genQo.setServiceSwitch(true);
/** 生成实体的开关 */
genQo.setEntitySwitch(true);
/** 是否生成控制器代码开关 */
genQo.setControllerSwitch(true);
genQo.setUrl(url);
genQo.setUserName(userName);
genQo.setPassword(password);
/**
* Mybatis-Plus的代码生成器:
* mp的代码生成器可以生成实体,mapper,mapper对应的xml,service
*/
WebGeneratorConfig gunsGeneratorConfig = new WebGeneratorConfig(genQo);
gunsGeneratorConfig.doMpGeneration();
}
自定义模版
可以修改为你想要的模版格式,不修改则使用默认的模版格式。
新建文件controller.java.vm,内容如下:
package ${package.Controller};
import java.util.Arrays;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.winsun.affiliation.core.base.controller.BaseController;
import com.winsun.affiliation.core.common.BizExceptionEnum;
import com.winsun.affiliation.core.common.constant.factory.PageFactory;
import com.winsun.affiliation.core.exception.GunsException;
import com.winsun.affiliation.core.log.LogObjectHolder;
import com.winsun.affiliation.core.util.ResponseEntity;
import com.winsun.affiliation.core.util.ToolUtil;
import ${package.Entity}.${entity};
import ${package.Service}.${table.serviceName};
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
/**
* ${cfg.bizChName}控制器
*
* @author ${author}
* @Date ${cfg.datetime}
*/
@Api(value="${cfg.bizChName}", tags="${cfg.bizChName}接口")
@RestController
@RequestMapping("/${cfg.bizEnName}")
public class ${cfg.bizEnBigName}Controller extends BaseController {
@Autowired
private I${cfg.entityName}Service ${cfg.bizEnName}Service;
/**
* 获取${cfg.bizChName}列表
*/
@ApiOperation("${cfg.bizChName}列表")
@RequestMapping(value = "/list", method = RequestMethod.POST)
public Object list(${cfg.entityName} ${cfg.bizEnName}) {
IPage<${cfg.entityName}> page = new PageFactory<${cfg.entityName}>().defaultPage();
QueryWrapper<${cfg.entityName}> queryWrapper = new QueryWrapper<>();
page = ${cfg.bizEnName}Service.page(page, queryWrapper);
return super.packForBT(page);
}
/**
* 新增${cfg.bizChName}
*/
@ApiOperation("新增${cfg.bizChName}")
@RequestMapping(value = "/add", method = RequestMethod.POST)
public Object add(@Valid ${cfg.entityName} ${cfg.bizEnName}, BindingResult bindingResult) {
//判断参数是否通过校验
if (bindingResult.hasErrors()) {
//错误信息
String message = bindingResult.getFieldError().getDefaultMessage();
return ResponseEntity.newJSON("code",400, "message", message);
}
boolean result = ${cfg.bizEnName}Service.save(${cfg.bizEnName});
return result ? SUCCESS_TIP : ERROR_TIP;
}
/**
* 删除${cfg.bizChName}
*/
@ApiOperation("删除${cfg.bizChName}")
@ApiImplicitParam(name="id", required=true, value="${cfg.bizChName}Id", paramType="query")
@RequestMapping(value = "/delete", method = RequestMethod.GET)
public Object delete(@RequestParam String id) {
if (ToolUtil.isEmpty(id)) {
throw new GunsException(BizExceptionEnum.REQUEST_NULL);
}
boolean result = ${cfg.bizEnName}Service.removeById(id);
return result ? SUCCESS_TIP : ERROR_TIP;
}
/**
* 修改${cfg.bizChName}
*/
@ApiOperation("修改${cfg.bizChName}")
@RequestMapping(value = "/edit", method = RequestMethod.POST)
public Object edit(@Valid ${cfg.entityName} ${cfg.bizEnName}, BindingResult bindingResult) {
//判断参数是否通过校验
if (bindingResult.hasErrors()) {
//错误信息
String message = bindingResult.getFieldError().getDefaultMessage();
return ResponseEntity.newJSON("code",400, "message", message);
}
boolean result = ${cfg.bizEnName}Service.updateById(${cfg.bizEnName});
return result ? SUCCESS_TIP : ERROR_TIP;
}
/**
* ${cfg.bizChName}详情
*/
@ApiOperation("${cfg.bizChName}详情")
@ApiImplicitParam(name="id", required=true, value="${cfg.bizChName}Id", paramType="path")
@RequestMapping(value = "/detail/{id}", method = RequestMethod.GET)
public Object detail(@PathVariable("id") String id) {
if (ToolUtil.isEmpty(id)) {
throw new GunsException(BizExceptionEnum.REQUEST_NULL);
}
${cfg.entityName} ${cfg.bizEnName} = ${cfg.bizEnName}Service.getById(id);
LogObjectHolder.me().set(${cfg.bizEnName});
return ResponseEntity
.newJSON("code", 200, "data", ${cfg.bizEnName});
}
/**
* 批量删除${cfg.bizChName}
*/
@ApiOperation("批量删除${cfg.bizChName}")
@ApiImplicitParam(name="item", required=true, value="${cfg.bizChName}Id(,分隔)", paramType="query")
@RequestMapping(value="/batchDelete", method= RequestMethod.POST)
public Object deleteBatchIds(@RequestParam String item){
if (ToolUtil.isEmpty(item)) {
throw new GunsException(BizExceptionEnum.REQUEST_NULL);
}
List<String> ids = Arrays.asList(item.split(","));
boolean result = ${cfg.bizEnName}Service.removeByIds(ids);
return result ? SUCCESS_TIP : ERROR_TIP;
}
}
自定义注入InjectionConfig
上面的controller.java.vm 模版生成代码时可以通过${cfg.key}获取注入的参数值,也可以指定自定义html或jsp模版生成页面
AutoGenerator autoGenerator = new AutoGenerator();
// 注入自定义配置,可以在 VM 中使用 cfg.abc 设置的值
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
Map<String, Object> map = new HashMap<>();
map = ToolUtil.bean2Map(contextConfig, map);
this.setMap(map);
}
};
// 使用自定义html模版生成页面
List<FileOutConfig> fileOutList = new ArrayList<>();
fileOutList.add(new FileOutConfig("/templates/list.html.vm") {
@Override
public String outputFile(TableInfo tableInfo) {
return contextConfig.getProjectPath() + "/src/main/resources/templates/" + tableInfo.getEntityName() + "List.html";
}
});
cfg.setFileOutConfigList(fileOutList)
autoGenerator.setCfg(cfg);
autoGenerator.execute();
MyBatis-Plus的使用
项目初始化
1、引入maven依赖
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.7.1</version>
</dependency>
</dependencies>
2、将生成的代码,拷贝到相应的包下
3、mybatis-plus配置
扫描的目录或文件注意修改相应的包名
# 实体扫描,多个package用逗号或者分号分隔
mybatis-plus.type-aliases-package=com.winsun.affiliation.modular.*.model
# 枚举扫描
mybatis-plus.typeEnumsPackage=com.winsun.affiliation.modular.*.enums
# 0:数据库ID自增 1:用户输入id 2:全局唯一id(IdWorker) 3:全局唯一ID(uuid)
mybatis-plus.global-config.id-type=3
# 字段策略(拼接sql时用于判断属性值是否拼接) 0:忽略判断,1:非NULL判断,2:非空判断
mybatis-plus.global-config.field-strategy=2
# 驼峰下划线转换含查询column及返回column(column下划线命名create_time,返回java实体是驼峰命名createTime,开启后自动转换否则保留原样)
mybatis-plus.global-config.db-column-underline=true
# 是否动态刷新mapper
mybatis-plus.global-config.refresh-mapper=true
mybatis-plus.global-config.logic-delete-value=1
mybatis-plus.global-config.logic-not-delete-value=0
# 自定义SQL注入器
#mybatis-plus.global-config.sql-injector=com.baomidou.mybatisplus.extension.injector.LogicSqlInjector
mybatis-plus.configuration.map-underscore-to-camel-case=true
# 配置的缓存的全局开关
mybatis-plus.configuration.cache-enabled=true
# 延时加载的开关
mybatis-plus.configuration.lazyLoadingEnabled=true
# 开启的话,延时加载一个属性时会加载该对象全部属性,否则按需加载属性
mybatis-plus.configuration.multipleResultSetsEnabled=true
# 打印sql语句,调试用
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# xml文件扫描
mybatis-plus.mapper-locations=classpath*:com/winsun/affiliation/**/dao/mapping/*.xml
/**
* 数据源配置
*/
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = {"com.winsun.affiliation.modular.*.dao"})
public class SingleDataSourceConfig {
/**
* mybatis-plus SQL执行效率插件【生产环境可以关闭】
*/
@Bean
@Profile({"local","dev})// 设置 local dev 环境开启
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
//格式化sql语句
/*Properties properties = new Properties();
properties.setProperty("format", "true");
performanceInterceptor.setProperties(properties);*/
performanceInterceptor.setFormat(true);
performanceInterceptor.setMaxTime(180000);
return performanceInterceptor;
}
/**
* mybatis-plus分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
// 攻击 SQL 阻断解析器、加入解析链
sqlParserList.add(new BlockAttackSqlParser());
paginationInterceptor.setSqlParserList(sqlParserList);
//paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持
return paginationInterceptor;
}
/**
* 乐观锁mybatis插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
/**
* 注入sql注入器
*/
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
/**
* 如果是对全表的删除或更新操作,就会终止该操作
* @return
*/
@Bean
public SqlExplainInterceptor sqlExplainInterceptor() {
SqlExplainInterceptor sqlExplainInterceptor= new SqlExplainInterceptor();
return sqlExplainInterceptor;
}
}
注解说明
主键生成策略:@TableId
值 | 描述 |
---|---|
value | 字段名称,实体类字段名和数据库一致时可以不写 |
type | 主键 ID 策略类型( 默认 INPUT ,全局开启的是 ID_WORKER ) |
type的类型有以下几种:
- IdType.AUTO:数据库ID自增
- IdType.INPUT:用户输入ID
- IdType.ID_WORKER:全局唯一ID,内容为空自动填充(默认配置)
- IdType.UUID:全局唯一ID,内容为空自动填充
实体对应表名注解:@TableName
值 | 描述 |
---|---|
value | 表名( 默认空 ),指定当前实体类对应的数据库表 |
resultMap | xml 字段映射 resultMap ID |
数据库字段映射名称:@TableField
值 | 描述 |
---|---|
value | 字段值(驼峰命名方式,该值可无) |
update | 预处理 set 字段自定义注入 |
condition | 预处理 WHERE 实体条件自定义运算规则 |
el | 详看注释说明 |
exist | 是否为数据库表字段( 默认 true 存在,false 不存在 ) |
strategy | 字段验证 ( 默认 非 null 判断,查看 com.baomidou.mybatisplus.enums.FieldStrategy ) |
fill | 字段填充标记 ( FieldFill, 配合自动填充使用 ) |
-
TableField 注解属性 exist 忽略插入到表的字段
数据库没有这个字段,如果不忽略,那么插入就会报错,找不到这个字段@TableField(exist = false) private Double money;
-
TableField 注解新增属性 update 预处理 set 字段自定义注入
例如:@TableField(.. , update="%s+1") 其中 %s 会填充为字段 输出 SQL 为:update 表 set 字段=字段+1 where ... 例如:@TableField(.. , update="now()") 使用数据库时间 输出 SQL 为:update 表 set 字段=now() where ...
-
TableField 注解新增属性 condition 预处理 WHERE 实体条件自定义运算规则
@TableField(condition = SqlCondition.LIKE) private String name; 输出 SQL 为:select 表 where name LIKE CONCAT('%',值,'%')
-
字段填充策略 FieldFill
值 | 描述 |
---|---|
DEFAULT | 默认不处理 |
INSERT | 插入填充字段 |
UPDATE | 更新填充字段 |
INSERT_UPDATE | 插入和更新填充字段 |
序列主键策略 注解 @KeySequence
值 | 描述 |
---|---|
value | 序列名 |
clazz | id的类型 |
乐观锁标记注解 @Version
通用的CRUD操作
Service
我们一起去看源码 com.baomidou.mybatisplus.extension.service.IService
增加:
/**
* <p>
* 插入一条记录(选择字段,策略插入)
* </p>
*
* @param entity 实体对象
*/
boolean save(T entity);
修改:
/**
* <p>
* 根据 ID 选择修改
* </p>
*
* @param entity 实体对象
*/
boolean updateById(T entity);
/**
* <p>
* 根据 whereEntity 条件,更新记录
* </p>
*
* @param entity 实体对象
* @param updateWrapper 实体对象封装操作类
* {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
*/
boolean update(T entity, Wrapper<T> updateWrapper);
删除:
/**
* <p>
* 根据 ID 删除
* </p>
*
* @param id 主键ID
*/
boolean removeById(Serializable id);
/**
* <p>
* 根据 entity 条件,删除记录
* </p>
*
* @param queryWrapper 实体包装类
* {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
boolean remove(Wrapper<T> queryWrapper);
Mapper
com.baomidou.mybatisplus.core.mapper.BaseMapper
增加:
/**
* <p>
* 插入一条记录
* </p>
*
* @param entity 实体对象
*/
int insert(T entity);
修改:
/**
* <p>
* 根据 whereEntity 条件,更新记录
* </p>
*
* @param entity 实体对象 (set 条件值,不能为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
int update(@Param(Constants.ENTITY) T entity,
@Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
/**
* <p>
* 根据 ID 修改
* </p>
*
* @param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity);
删除:
/**
* <p>
* 根据 entity 条件,删除记录
* </p>
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* <p>
* 根据 ID 删除
* </p>
*
* @param id 主键ID
*/
int deleteById(Serializable id);
查询
MP 3.x,查询接口发生了很大的变化。
/**
* <p>
* 查询列表
* </p>
*
* @param queryWrapper 实体对象封装操作类
* {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
List<T> list(Wrapper<T> queryWrapper);
/**
* <p>
* 根据 ID 查询
* </p>
*
* @param id 主键ID
*/
T getById(Serializable id);
/**
* <p>
* 根据 Wrapper,查询一条记录
* </p>
*
* @param queryWrapper 实体对象封装操作类
* {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
T getOne(Wrapper<T> queryWrapper);
分页查询
/**
* <p>
* 翻页查询
* </p>
*
* @param page 翻页对象
* @param queryWrapper 实体对象封装操作类
* {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
自定义深层次CRUD操作
Service
有时候需要在Service层进行逻辑处理,最好不要重写覆盖原来的方法,防止别人引用同一个方法造成冲突,所以需要重命名新的方法
新增修改
servcie
/**
* 自定义新增
* @param user
* @return
*/
public boolean doSave(User user);
/**
* 自定义修改
* @param user
* @return
*/
public boolean doUpdateById(User user);
servcieimpl
@Override
public boolean doSave(User user) {
// do something
user.setId(IdWorker.get32UUID());
user.setCreateTime(new Date());
return save(user);
}
@Override
public boolean doUpdateById(User user) {
// do something
user.setUpdateTime(new Date());
return updateById(user);
}
查询
servcie
/**
* 分页查询
* @param page 分页对象
* @param user 查询条件
* @return
*/
public IPage<User> findPage(IPage<User> page, User user);
/**
* 查询
* @param user 查询条件
* @return
*/
public List<User> findList(User user);
servcieimpl
@Override
public IPage<User> findPage(IPage<User> page, User user) {
// 查询条件组装
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.eq(User::getName, user.getName())
.eq(User::getPhone, user.getPhone());
page = page(page, queryWrapper);
return page;
}
@Override
public List<User> findList(User user) {
// 查询条件组装
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.eq(User::getName, user.getName())
.eq(User::getPhone, user.getPhone());
List<User> userList = list(queryWrapper);
return userList;
}
逻辑删除
1、代码生成器中配置:
strategyConfig.setLogicDeleteFieldName("delFlag") // 逻辑删除字段
或
genQo.setLogicDeleteFieldName("delFlag");//逻辑删除字段
或者,你可以手写,参考:
@ApiModelProperty(value = "删除标识(1:删除;0:正常(默认))")
@TableLogic
private String delFlag;
2、自定义数据库的值:
yml文件配置
mybatis-plus:
global-config:
logic-delete-value: 1
logic-not-delete-value: 0
properties文件配置
mybatis-plus.global-config.logic-delete-value=1
mybatis-plus.global-config.logic-not-delete-value=0
3、插件配置
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
4、删除:
/**
* <p>
* 根据 ID 删除
* </p>
*
* @param id 主键ID
*/
boolean removeById(Serializable id);
/**
* <p>
* 批量删除 根据 ID 删除
* </p>
*
* @param id 主键ID集合
*/
boolean removeByIds(Collection<? extends Serializable> idList);
枚举类
1、自定义枚举实现 IEnum 接口
public enum AgeEnum implements IEnum {
ONE(1, "一岁"),
TWO(2, "二岁");
private int value;
private String desc;
AgeEnum(final int value, final String desc) {
this.value = value;
this.desc = desc;
}
@Override
public Serializable getValue() {
return this.value;
}
public String getDesc(){
return this.desc;
}
}
2、配置文件中添加扫描:
yml文件配置
mybatis-plus:
# 扫描枚举类 # 支持统配符 * 或者 ; 分割
type-enums-package: com.fengwenyi.mp3demo.enums
properties文件配置
# 枚举扫描
mybatis-plus.typeEnumsPackage=com.winsun.affiliation.modular.*.enums
3、JSON序列化处理
一、Jackson
1.在需要响应描述字段的get方法上添加@JsonValue注解即可
二、Fastjson
1.全局处理方式
FastJsonConfig config = new FastJsonConfig();
//设置WriteEnumUsingToString
config.setSerializerFeatures(SerializerFeature.WriteEnumUsingToString);
converter.setFastJsonConfig(config);
2.局部处理方式
@JSONField(serialzeFeatures= SerializerFeature.WriteEnumUsingToString)
private UserStatus status;
以上两种方式任选其一,然后在枚举中复写toString方法即可.
乐观锁
意图:
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = yourVersion+1 where version = yourVersion
- 如果version不对,就更新失败
1、插件配置
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
2、注解实体字段
@Version
private Integer version;
@TableField完成字段自动填充
字段注解 @TableField
- com.baomidou.mybatisplus.annotations.TableField
值 | 描述 |
---|---|
value | 字段值(驼峰命名方式,该值可无) |
update | 预处理 set 字段自定义注入 |
condition | 预处理 WHERE 实体条件自定义运算规则 |
el | 详看注释说明- |
exist | 是否为数据库表字段( 默认 true 存在,false 不存在 ) |
strategy | 字段验证 ( 默认 非 null 判断,查看com.baomidou.mybatisplus.enums.FieldStrategy ) |
fill | 字段填充标记 ( FieldFill, 配合自动填充使用 ) |
- 字段填充策略 FieldFill
值 | 描述 |
---|---|
DEFAULT | 默认不处理 |
INSERT | 插入填充字段 |
UPDATE | 更新填充字段 |
INSERT_UPDATE | 插入和更新填充字段 |
实现
实体类中有如下属性,通过上面的自动填充属性,我们可以实现
在进行插入操作时对
添加了注解@TableField(fill = FieldFill.INSERT)
的字段进行自动填充。
对添加了注解@TableField(fill = FieldFill.INSERT_UPDATE)
的字段在进行插入和更新时进行自动填充。
/**
* 创建人
*/
@TableField(fill = FieldFill.INSERT)
private StringcreateBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createDate;
/**
* 修改人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateDate;
这样我们在具体业务中对实体类进行赋值就可以不用对这些公共字段进行赋值,在执行插入或者更新时就能自动赋值并插入数据库。
那么要自动赋的值在哪里配置?
在项目的config包下新建自动填充处理类使其实现接口MetaObjectHandler
并重写其方法:
/**
* 自动填充处理类
**/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createBy", ShiroKit.getId(), metaObject);
this.setFieldValByName("createDate",new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateBy", ShiroKit.getId(), metaObject);
this.setFieldValByName("updateDate", new Date(), metaObject);
}
}
其中方法参数中第一个是前面自动填充所对应的字段,第二个是要自动填充的值。
自定义全局配置Sql注入器
全局配置sqlInjector用于注入ISqlInjector接口 子类,实现自定义方法注入
参考逻辑删除注入器
类似com.baomidou.mybatisplus.core.enums.SqlMethod的枚举类型
/**
* 自定义全局删除方法
*/
public enum MySqlMethod {
/**
* 删除全部
*/
DELETE_ALL("deleteAll", "删除全部记录", "<script>\nDELETE FROM %s %s\n</script>"),
LOGIC_DELETE_ALL("deleteAll", "逻辑删除全部记录", "<script>\nUPDATE %s %s %s\n</script>");
private final String method;
private final String desc;
private final String sql;
MySqlMethod(String method, String desc, String sql) {
this.method = method;
this.desc = desc;
this.sql = sql;
}
public String getMethod() {
return method;
}
public String getDesc() {
return desc;
}
public String getSql() {
return sql;
}
}
继承AbstractMethod,实现其方法的改写
public class DeleteAll extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sql;
MySqlMethod mySqlMethod = MySqlMethod.DELETE_ALL;
if (tableInfo.isLogicDelete()) {
mySqlMethod = MySqlMethod.DELETE_ALL;
sql = String.format(mySqlMethod.getSql(), tableInfo.getTableName(), tableInfo,
sqlWhereEntityWrapper(true, tableInfo));
} else {
sql = String.format(mySqlMethod.getSql(), tableInfo.getTableName(),
sqlWhereEntityWrapper(true, tableInfo));
}
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return addUpdateMappedStatement(mapperClass, modelClass, mySqlMethod.getMethod(), sqlSource);
}
}
继承AbstractInjector,实现了其getMethod方法,getMethod方法返回一个Stream.of( ).collect(Collectors.toList()),Stream.of()中封装一个对象,是实际的Sql注入方法的写法
/**
* 自定义全局操作
*/
@Component
public class MySqlInjector extends AbstractSqlInjector {
@Override
public List<AbstractMethod> getMethodList() {
return Stream.of(
new DeleteAll()
).collect(Collectors.toList());
}
}
在接口中定义方法
/**
* 删除全部
* @return
*/
int deleteAll();
Lambda 表达式
MyBatis-Plus 3.x开始支持Lambda 表达式。
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
【1】select字段
// SELECT name,age FROM student
QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
..select(Student::getName,Student::getName);
List<Student> studentList = list(queryWrapper);
【2】多eq
// SELECT * FROM student WHERE name = ? AND age = ?
QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.eq(Student::getName, "冯文议")
.eq(Student::getAge, 26);
List<Student> studentList = list(queryWrapper);
QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.and(obj ->
obj.eq(Student::getName, "冯文议")
.eq(Student::getAge, 26));
List<Student> studentList = list(queryWrapper);
【3】or
// SELECT * FROM student WHERE ( name = ? ) OR ( name = ? )
QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.or(obj1 -> obj1.eq(Student::getName, "冯文议"))
.or(obj2 -> obj2.eq(Student::getName, "1"));
List<Student> studentList = list(queryWrapper);
// SELECT * FROM student WHERE name = ? OR name = ?
QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.and(obj ->
obj.eq(Student::getName, "冯文议")
.eq(Student::getAge, 26));
List<Student> studentList = list(queryWrapper);
条件构造器
QueryWrapper与UpdateWrapper共有方法
方法名 | 说明 |
---|---|
allEq | 基于 map 内容等于= |
eq | 等于 = |
ne | 不等于 <> |
gt | 大于 > |
ge | 大于等于 >= |
lt | 小于 < |
le | 小于等于 <= |
between | BETWEEN 条件语句 |
notBetween | NOT BETWEEN 条件语句 |
like | LIKE ‘%值%’’ |
notLike | NOT LIKE ‘%值%’ |
likeLeft | LIKE ‘%值’ |
likeRight | LIKE ‘值%’ |
isNull | NULL 值查询 |
isNotNull | NOT NULL 值查询 |
in | IN 查询 |
notIn | NOT IN 查询 |
inSql | IN 查询(sql注入式) |
notInSql | NOT IN 查询(sql注入式) |
groupBy | 分组 GROUP BY |
orderByAsc | ASC 排序 ORDER BY |
orderByDesc | DESC 排序 ORDER BY |
orderBy | 排序 ORDER BY |
having | HAVING 关键词(sql注入式) |
or | or 拼接 |
apply | 拼接自定义内容(sql注入式) |
last | 拼接在最后(sql注入式) |
exists | EXISTS 条件语句(sql注入式) |
notExists | NOT EXISTS 条件语句(sql注入式) |
and(Function) | AND (嵌套内容) |
or(Function) | OR (嵌套内容) |
nested(Function) | (嵌套内容) |
QueryWrapper特有方法
方法名 | 说明 |
---|---|
select | SQL 查询字段内容,例如:id,name,age(重复设置以最后一次为准) |
UpdateWrapper特有方法
方法名 | 说明 |
---|---|
set | SQL SET 字段(一个字段使用一次) |
自定义sql (多表查询)
如果官方提供的满足不了你的需求(基本上单表操作能满足),你可以自己写一个数据库操作接口
Roles、Permissions关联查询(一对一)
实体对象
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sys_roles")
@ApiModel(value="Roles对象", description="角色信息")
public class Roles extends Model<Roles> {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.UUID)
@ApiModelProperty(hidden=true)
private String id;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "角色名")
private String roles;
@Override
protected Serializable pkVal() {
return this.id;
}
}
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sys_permissions")
@ApiModel(value="Permissions对象", description="权限信息")
public class Permissions extends Model<Permissions> {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.UUID)
@ApiModelProperty(hidden=true)
private String id;
@ApiModelProperty(value = "权限名")
private String name;
@ApiModelProperty(value = "角色名")
private String roles;
@Override
protected Serializable pkVal() {
return this.id;
}
}
@Data
@ApiModel(value="RolesPermissions对象", description="角色权限信息")
public class RolesPermissions extends Roles {
private Permissions permissions;
@Override
public String toString() {
return "RolesPermissions [" + super.toString() + ", permissions=" + permissions + "]";
}
}
mapper.xml
属性介绍:
association : 一对一类型关联,单条结果集将映射为这种类型
collection : 一个复杂的类型关联,多条结果集将映射为这种类型
property : 这是关联的 JavaBean 中的属性名
javaType : property 属性对应的集合类型
ofType : property 集合中的泛型,即定义时泛型所表示的具体类型
column : 作为参数传入被调用的 Select 语句,当需要传递多个值时,写法如下:
column= “{prop1=col1,prop2=col2},接收的类型为 parameterType=“java.util.Map”,这里的参数使用时名称要保持一致;
resultMap: 一个可以映射联合嵌套结果集到一个适合的对象视图上的ResultMap
方式一: 一个关联查询将所需要的数据映射到该对象的属性上
<resultMap id="rolesPermissions" type="RolesPermissions">
<id column="rolesId" property="id" />
<result column="userName" property="username" />
<result column="rolesName" property="roles" />
<association property="permissions" javaType="Permissions">
<id column="permissionsId" property="id" />
<result column="permissions" property="permissions" />
<result column="rolesName" property="roles" />
</association>
</resultMap>
<select id="getRolesPermissionsList" resultMap="rolesPermissions">
select
r.id as rolesId,
r.roles as rolesName,
r.username as userName,
p.id as permissionsId,
p.permissions as permissions
from sys_roles r, sys_permissions p
where r.roles =p.roles
<if test="id !=null and id !=''">
and r.id = #{id}
</if>
</select>
方式二: 嵌套查询,select 语句在其他dao对应的mapper.xml中一个联合查询将所需要的数据映射到该对象的属性上
<resultMap id="rolesPermissions" type="RolesPermissions">
<id column="id" property="id" />
<result column="username" property="username" />
<result column="roles" property="roles" />
<association property="permissions" column="roles" select="getPermissionsLByRoles">
</association>
</resultMap>
<select id="getRolesPermissionsList" resultMap="rolesPermissions">
select
r.id,
r.roles,
r.username
from sys_roles r
<if test="id !=null and id !=''">
where r.id = #{id}
</if>
</select>
<select id="getPermissionsLByRoles" resultType="Permissions">
select
p.id,
p.roles,
p.permissions
from sys_permissions p
where p.roles = #{roles}
</select>
User、Roles关联查询(一对多)
实体对象
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sys_user")
@ApiModel(value="User对象", description="用户信息")
public class User extends Model<User> {
@ApiModelProperty(value = "主键", required=false)
@TableId(value = "id", type = IdType.UUID)
private String id;
@ApiModelProperty(value = "用户姓名", required=true)
private String name;
@ApiModelProperty(value = "性别:0:男 1:女", required=true)
private String gender;
@ApiModelProperty(value = "用户类型", required=false)
private String usertype;
@ApiModelProperty(value = "状态(0:启用 1:删除)", hidden=true)
@TableField("del_flag")
@TableLogic
private String delFlag;
@ApiModelProperty(value = "创建时间", hidden=true)
@TableField(value="create_date", fill=FieldFill.INSERT)
private Date createDate;
@ApiModelProperty(value = "更新时间", hidden=true)
@TableField(value="update_date", fill=FieldFill.UPDATE)
private Date updateDate;
@ApiModelProperty(value = "创建者", hidden=true)
@TableField(value="create_by", fill=FieldFill.INSERT)
private String createBy;
@ApiModelProperty(value = "更新者", hidden=true)
@TableField(value="update_by", fill=FieldFill.UPDATE)
private String updateBy;
@ApiModelProperty(value = "真实姓名", required=true)
@TableField("real_name")
private String realName;
@Override
protected Serializable pkVal() {
return this.id;
}
}
@Data
@ApiModel(value="UserRoles对象", description="用户角色信息")
public class UserRoles extends User {
private List<Roles> rolesList;
@Override
public String toString() {
return "UserRoles [" + super.toString() + ", rolesList=" + rolesList + "]";
}
}
mapper.xml
方式一: 一个关联查询将所需要的数据映射到该对象的属性上
<resultMap id="userRoles" type="UserRoles">
<id column="userId" property="id" />
<result column="name" property="name" />
<result column="gender" property="gender" />
<result column="usertype" property="usertype" />
<result column="del_flag" property="delFlag" />
<result column="create_date" property="createDate" />
<result column="update_date" property="updateDate" />
<result column="create_by" property="createBy" />
<result column="update_by" property="updateBy" />
<result column="real_name" property="realName" />
<!-- 角色列表 -->
<collection property="rolesList" ofType="Roles">
<id column="rolesId" property="id" />
<result column="name" property="username" />
<result column="roles" property="roles" />
</collection>
</resultMap>
<select id="getUserRolesList" resultMap="userRoles">
select
u.id as userId,
u.name,
u.usertype,
u.gender,
u.del_flag,
u.create_date,
u.create_by,
u.update_date,
u.update_by,
u.real_name,
r.id as rolesId,
r.roles
from sys_user u
left join sys_roles r on u.name = r.username
<if test="id !=null and id !=''">
where u.id = #{id}
</if>
</select>
方式二: 嵌套查询,select 语句在其他dao对应的mapper.xml中一个联合查询将所需要的数据映射到该对象的属性上
<resultMap id="userRoles" type="UserRoles">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="gender" property="gender" />
<result column="usertype" property="usertype" />
<result column="del_flag" property="delFlag" />
<result column="create_date" property="createDate" />
<result column="update_date" property="updateDate" />
<result column="create_by" property="createBy" />
<result column="update_by" property="updateBy" />
<result column="real_name" property="realName" />
<!-- 角色列表 ofType="Roles"-->
<collection property="rolesList" column="name" select="getRolesByName">
</collection>
</resultMap>
<select id="getUserRolesList" resultMap="userRoles">
select
u.id,
u.name,
u.usertype,
u.gender,
u.del_flag,
u.create_date,
u.create_by,
u.update_date,
u.update_by,
u.real_name
from sys_user u
<if test="id !=null and id !=''">
where u.id = #{id}
</if>
</select>
<select id="getRolesByName" resultType="Roles">
select
id,
username,
roles
from sys_roles
where username = #{name}
</select>
mapper接口
public interface UserMapper extends BaseMapper<User> {
/**
* 查询角色权限信息
* @return
*/
List<RolesPermissions> getRolesPermissionsList(@Param("id") String id);
/**
* 查询用户角色信息
* @param id
* @return
*/
List<UserRoles> getUserRolesList(@Param("id") String id);
}
Lombok
Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误。
Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。出现的神奇就是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。
Lombok的使用跟引用jar包一样,可以在官网(https://projectlombok.org/download) 下载jar包,也可以使用maven添加依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
Lombok的优缺点
- 优点:
能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率
让代码变得简洁,不用过多的去关注相应的方法
属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等
- 缺点:
不支持多种参数构造器的重载
虽然省去了手动创建getter/setter方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度
Lombok插件的安装
Eclipse 安装
1、下载 lombok.jar (https://projectlombok.org/download.html)
2、将 lombok.jar 放在eclipse安装目录下,和 eclipse.ini 文件平级的。
3、运行lombok.jar
在lombok.jar 的目录下,运行:
java -jar lombok.jar
a. 运行后会弹框如下框,直接点确定
b. 点specify location 按钮,选择eclipse的安装目录,选择到eclipse层即可。
按如下图步骤点击install即可
c. 成功后如下图:
如果想看看是否真的安装成功,可以在 eclipse.ini 中看看,我的环境是多了一行(-javaagent:D:\Program Files\eclipse\lombok.jar)
最后重启eclipse。
IDEA 安装
打开IntelliJ IDEA后点击菜单栏中的File–>Settings,或者使用快捷键Ctrl+Alt+S进入到设置页面。
点击设置中的Plugins进行插件的安装,在右侧选择Browse repositories…,然后在搜索页面输入lombok变可以查询到下方的Lombok Plugin,鼠标点击Lombok Plugin可在右侧看到Install按钮,点击该按钮便可安装。
安装完成后我们再回到Plugins,此时在右侧可以搜索到lombok
在Settings设置页面,我们点击Build,Execution,Deployment–>选择Compiler–>选中Annotation Processors,然后在右侧勾选Enable annotation processing即可。
最后保存好重启idea。
常用注解
@Data 注解在类上;提供类所有属性的 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 方法
@Setter :注解在属性上;为属性提供 setting 方法
@Getter :注解在属性上;为属性提供 getting 方法
@Log4j :注解在类上;为类提供一个 属性名为log 的 log4j 日志对象
@NoArgsConstructor :注解在类上;为类提供一个无参的构造方法
@AllArgsConstructor :注解在类上;为类提供一个全参的构造方法
@Cleanup : 可以关闭流
@Builder : 被注解的类加个构造者模式
@Synchronized : 加个同步锁
@SneakyThrows : 等同于try/catch 捕获异常
@NonNull : 如果给参数加个这个注解 参数为null会抛出空指针异常
@Value : 注解和@Data类似,区别在于它会把所有成员变量默认定义为private final修饰,并且不会生成set方法。
@toString:注解在类上;为类提供toString方法(可以添加排除和依赖);
官方文档https://projectlombok.org/features/index.html
@Data
@Data注解在类上,会为类的所有属性自动生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
官方实例如下:
import lombok.AccessLevel;
import lombok.Setter;
import lombok.Data;
import lombok.ToString;
@Data public class DataExample {
private final String name;
@Setter(AccessLevel.PACKAGE) private int age;
private double score;
private String[] tags;
@ToString(includeFieldNames=true)
@Data(staticConstructor="of")
public static class Exercise<T> {
private final String name;
private final T value;
}
}
@Getter/@Setter
如果觉得@Data太过残暴(因为@Data集合了@ToString、@EqualsAndHashCode、@Getter/@Setter、@RequiredArgsConstructor的所有特性)不够精细,可以使用@Getter/@Setter注解,此注解在属性上,可以为相应的属性自动生成Getter/Setter方法,示例如下:
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
public class GetterSetterExample {
@Getter @Setter private int age = 10;
@Setter(AccessLevel.PROTECTED) private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
}
@NonNull
该注解用在属性或构造器上,Lombok会生成一个非空的声明,可用于校验参数,能帮助避免空指针。
示例如下:
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(@NonNull Person person) {
super("Hello");
this.name = person.getName();
}
}
@Cleanup
该注解能帮助我们自动调用close()方法,很大的简化了代码。
示例如下:
import lombok.Cleanup;
import java.io.*;
public class CleanupExample {
public static void main(String[] args) throws IOException {
@Cleanup InputStream in = new FileInputStream(args[0]);
@Cleanup OutputStream out = new FileOutputStream(args[1]);
byte[] b = new byte[10000];
while (true) {
int r = in.read(b);
if (r == -1) break;
out.write(b, 0, r);
}
}
}
@EqualsAndHashCode
默认情况下,会使用所有非静态(non-static)和非瞬态(non-transient)属性来生成equals和hasCode,也能通过exclude注解来排除一些属性。
示例如下:
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(exclude={"id", "shape"})
public class EqualsAndHashCodeExample {
private transient int transientVar = 10;
private String name;
private double score;
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public String getName() {
return this.name;
}
@EqualsAndHashCode(callSuper=true)
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
}
@ToString
类使用@ToString注解,Lombok会生成一个toString()方法,默认情况下,会输出类名、所有属性(会按照属性定义顺序),用逗号来分割。
通过将includeFieldNames参数设为true,就能明确的输出toString()属性。这一点是不是有点绕口,通过代码来看会更清晰些。
使用Lombok的示例:
import lombok.ToString;
@ToString(exclude="id")
public class ToStringExample {
private static final int STATIC_VAR = 10;
private String name;
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public String getName() {
return this.getName();
}
@ToString(callSuper=true, includeFieldNames=true)
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
}
@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor
无参构造器、部分参数构造器、全参构造器。Lombok没法实现多种参数构造器的重载。
Lombok示例代码如下:
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.NonNull;
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample<T> {
private int x, y;
@NonNull private T description;
@NoArgsConstructor
public static class NoArgsExample {
@NonNull private String field;
}
}