目录
- 一、Mybatis-Plus介绍
- 二、Spring boot 整合Mybatis-plus
- 2.1 pom中引入Mybatis-plus依赖
- 2.2 创建一张User表
- 2.3 Mybatis-plus配置
- 2.4 创建一个实体
- 2.5 创建一个Mapper接口
- 2.6 修改服务接口
- 2.7 在启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹
- 2.8 测试
- 2.9 小结
- 三、Mybatis-plus 部分字段注解和查询
- 3.1 主键
- 3.1.1 主键生成策略
- 3.1.2 使用方式
- 3.2 删除
- 3.3 自动填充
- 3.3.1 增加公共属性对象:
- 3.3.2 自定义实现类 MyMetaObjectHandler
- 3.4 条件查询
- 3.4.1 根据各种条件查询
- 3.5 分页查询
- 3.6 小结
- 四、引入对象转换组件mapstruct
- 4.1 pom中引入依赖包
- 4.2 创建一个Convert接口
- 4.3 在类中引用
- 4.4 对象属性不同怎么转换
- 五、写在最后
一、Mybatis-Plus介绍
Mybatis-plus是Mybatis的增强工具包,其官网的介绍如下:
- 润物细无声:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。
- 效率至上:只需简单配置,即可快速进行单表CRUD操作,从而节省大量时间。
- 丰富功能:代码生成、自动分页、逻辑删除、自动填充等功能一应俱全。
其优点如下:
- 无侵入:Mybatis-Plus 在 Mybatis 的基础上进行扩展,只做增强不做改变,引入 Mybatis-Plus 不会对您现有的 Mybatis 构架产生任何影响,而且 MP 支持所有 Mybatis 原生的特性
- 依赖少:仅仅依赖 Mybatis 以及 Mybatis-Spring
- 损耗小:启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作
- 通用CRUD操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 多种主键策略:支持多达4种主键策略(内含分布式唯一ID生成器),可自由配置,完美解决主键问题
- 支持ActiveRecord:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可实现基本 CRUD 操作
- 支持代码生成:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用(P.S. 比 Mybatis 官方的 Generator 更加强大!)
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置分页插件:基于Mybatis物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于写基本List查询
- 内置性能分析插件:可输出Sql语句以及其执行时间,建议开发测试时启用该功能,能有效解决慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,预防误操作
二、Spring boot 整合Mybatis-plus
2.1 pom中引入Mybatis-plus依赖
| |
| <dependency> |
| <groupId>com.baomidou</groupId> |
| <artifactId>mybatis-plus-boot-starter</artifactId> |
| <version>3.5.3.1</version> |
| </dependency> |
注:如果是gradle,引入的方式如下:
implementation group: 'com.baomidou', name: 'mybatis-plus-boot-starter', version: '3.5.3.1'
2.2 创建一张User表
创建对应的数据表 Schema 的表结构和表数据:
| SET FOREIGN_KEY_CHECKS=0; |
| |
| |
| |
| |
| DROP TABLE IF EXISTS `user_base_info`; |
| CREATE TABLE `user_base_info` ( |
| `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', |
| `u_id` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户ID', |
| `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名', |
| `cn_name` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '中文名', |
| `sex` char(3) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '性别', |
| `alias` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '别名', |
| `web_chat` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '微信号', |
| PRIMARY KEY (`id`) |
| ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; |
表中插入数据
| |
| |
| |
| INSERT INTO `user_base_info` VALUES ('1', '001', 'holmium', '钬', '1', '虎啸山村', 'holmium'); |
| INSERT INTO `user_base_info` VALUES ('2', '001', 'test', '测试', '0', '美女', '虎啸山村'); |
2.3 Mybatis-plus配置
| |
| mybatis-plus: |
| |
| mapper-locations: classpath:/mapper/*Mapper.xml |
| |
| typeAliasesPackage: com.holmium.springboot.repository.*.entity |
| global-config: |
| |
| db-config: |
| |
| id-type: AUTO |
| |
| field-strategy: not_empty |
| |
| column-underline: true |
| |
| |
| |
| logic-delete-value: 0 |
| logic-not-delete-value: 1 |
| db-type: h2 |
| |
| refresh: true |
| |
| configuration: |
| map-underscore-to-camel-case: true |
| cache-enabled: false |
2.4 创建一个实体
| |
| @Data |
| public class BasePo { |
| @TableId(value = "id", type = IdType.AUTO) |
| private Integer id; |
| } |
| |
| package com.holmium.springboot.infra.user.entity; |
| |
| import com.baomidou.mybatisplus.annotation.TableField; |
| import com.baomidou.mybatisplus.annotation.TableName; |
| import lombok.Getter; |
| import lombok.Setter; |
| |
| |
| |
| |
| |
| |
| @TableName(value = "user_base_info", autoResultMap = true) |
| @Getter |
| @Setter |
| public class UserPo extends BasePo { |
| |
| |
| |
| @TableField(value = "u_id") |
| String uId; |
| |
| |
| |
| @TableField(value = "name") |
| String name; |
| |
| |
| |
| @TableField(value = "cn_name") |
| String cnName; |
| |
| |
| |
| @TableField(value = "sex") |
| String sex; |
| |
| |
| |
| @TableField(value = "alias") |
| String alias; |
| |
| |
| |
| @TableField(value = "web_chat") |
| String webChat; |
| } |
2.5 创建一个Mapper接口
| package com.holmium.springboot.infra.user.mapper; |
| |
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| import com.holmium.springboot.infra.user.entity.UserPo; |
| import org.apache.ibatis.annotations.Mapper; |
| |
| @Mapper |
| public interface UserMapper extends BaseMapper<UserPo> { |
| } |
2.6 修改服务接口
| |
| @RestController |
| @RequestMapping("/user/center") |
| public class UserApi { |
| @Resource |
| UserAppImpl userApp; |
| |
| @GetMapping(value = "/userinfo") |
| public UserVo gerUserInfo(@RequestParam("id") String id) throws Exception { |
| return userApp.getUserInfoByUserId(id); |
| } |
| } |
2.7 在启动类中添加 @MapperScan
注解,扫描 Mapper 文件夹
| @SpringBootApplication |
| @MapperScan("com.holmium.springboot.infra.*.mapper") |
| public class HolmiumApplication { |
| |
| public static void main(String[] args) { |
| SpringApplication.run(HolmiumApplication.class, args); |
| } |
| |
| } |
2.8 测试



2.9 小结
通过以上的步骤,我们实现了对User表的查询功能,可以看到对于简单的CRUD操作,Mybatis-Plus
只需要定义一个Mapper
接口即可实现,真正做到如他所说的那样,简单配置、效率至上。
三、Mybatis-plus 部分字段注解和查询
3.1 主键
3.1.1 主键生成策略
主键生成策略一共提供的五种:
| AUTO(0),递增策略,如果使用该策略必须要求数据表的列也是递增。 |
| NONE(1),没有策略,必须人为的输入id值 |
| INPUT(2),没有策略,必须人为的输入id值 |
| ASSIGN_ID(3), 随机生成一个Long类型的值。该值一定是唯一。而且每次生成都不会相同。算法:雪花算法。 适合分布式主键。 |
| ASSIGN_UUID(4); 随机产生一个String类型的值。该值也是唯一的。 |
3.1.2 使用方式
通过配置文件中,配置id-type
属性:
| mybatis-plus: |
| global-config: |
| # 数据库相关配置 |
| db-config: |
| #主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID"; |
| id-type: AUTO |
也可在对象属性上进行注解设置:
| @Data |
| public class BasePo { |
| |
| @TableId(value = "id", type = IdType.AUTO) |
| private Integer id; |
| } |
3.2 删除
实际开发过程中,对数据都不会直接删除,都会采用逻辑删除的方式。所谓的逻辑删除,只是将数据置为一个状态,这个状态代表数据为删除状态,一般自己程序中写的话,就设置一个状态字段,给字段赋值为0
或 D
代表删除。而Mybatis-plus提供了@TableLogic
注解,实现逻辑删除。 只对自动注入的 sql 起效:
- 插入: 不作限制
- 查找: 追加 where 条件过滤掉已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段
- 更新: 追加 where 条件防止更新到已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段
- 删除: 转变为 更新
| @Data |
| public class BasePo { |
| |
| |
| |
| @TableId(value = "id", type = IdType.AUTO) |
| private Integer id; |
| |
| |
| |
| |
| @TableLogic |
| private Boolean deleted; |
| } |
3.3 自动填充
实际开发中,我们会在表中记录数据创建时间、创建人、修改时间、修改人几个字段,但是几个字段如果我们每次都要进行赋值,代码比较冗余,Mybatis-plus
提供的自动填充功能。
3.3.1 增加公共属性对象:
| @Data |
| public class BasePo { |
| |
| |
| |
| @TableId(value = "id", type = IdType.AUTO) |
| private Integer id; |
| |
| |
| |
| |
| @TableField(fill = FieldFill.INSERT) |
| @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| private Date createTime; |
| |
| |
| |
| @TableField(fill = FieldFill.INSERT_UPDATE) |
| @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| private Date updateTime; |
| |
| |
| |
| |
| |
| @TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR) |
| private String creator; |
| |
| |
| |
| |
| |
| @TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR) |
| private String updater; |
| |
| |
| |
| |
| @TableLogic |
| private Boolean deleted; |
| } |
3.3.2 自定义实现类 MyMetaObjectHandler
| public class DefaultDbFieldHandler implements MetaObjectHandler { |
| |
| @Override |
| public void insertFill(MetaObject metaObject) { |
| if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity) { |
| BaseEntity baseDO = (BaseEntity) metaObject.getOriginalObject(); |
| |
| Date current = new Date(); |
| |
| if (Objects.isNull(baseDO.getCreateTime())) { |
| baseDO.setCreateTime(current); |
| } |
| |
| if (Objects.isNull(baseDO.getUpdateTime())) { |
| baseDO.setUpdateTime(current); |
| } |
| |
| Long userId = WebUtils.getLoginUserId(); |
| |
| if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) { |
| baseDO.setCreator(userId.toString()); |
| } |
| |
| if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) { |
| baseDO.setUpdater(userId.toString()); |
| } |
| } |
| } |
| |
| @Override |
| public void updateFill(MetaObject metaObject) { |
| |
| Object modifyTime = getFieldValByName("updateTime", metaObject); |
| if (Objects.isNull(modifyTime)) { |
| setFieldValByName("updateTime", new Date(), metaObject); |
| } |
| |
| |
| Object modifier = getFieldValByName("updater", metaObject); |
| Long userId = WebUtils.getLoginUserId(); |
| if (Objects.nonNull(userId) && Objects.isNull(modifier)) { |
| setFieldValByName("updater", userId.toString(), metaObject); |
| } |
| } |
| } |
3.4 条件查询
3.4.1 根据各种条件查询
Wrapper:封装了关于查询的各种条件方法。
有三个子类最常用: LambdaQueryWrapper
查询条件 LambdaUpdateWrapper
修改条件 LambdaQueryWrapper
查询使用lambda表达式条件
| selectPage(userDo, new LambdaQueryWrapperX<UserPo>() |
| .likeIfPresent(UserPo::getName, userDo.getName()) |
| .likeIfPresent(UserPo::getSex, userDo.getSex()) |
| .betweenIfPresent(UserPo::getCreateTime, userDo.getCreateTime()) |
| .orderByDesc(UserPo::getId)) |
| update(update, new LambdaUpdateWrapper<UserPo>() |
| .eq(UserPo::getId, id).eq(UserPo::getName, name)) |
| new LambdaQueryWrapper<UserDo>() |
| .eq(UserPo::getId, id) |
| .eq(UserPo::getName, name); |
3.5 分页查询
| @Bean |
| public MybatisPlusInterceptor mybatisPlusInterceptor() { |
| MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); |
| |
| mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); |
| return mybatisPlusInterceptor; |
| } |
Page<UserPo> page = userMapper.selectPage(page, wrapper);
3.6 小结
到此简单的查询基本已介绍完毕,可满足他80%的日常开发需求。
四、引入对象转换组件mapstruct
从上述代码中可以看到,我们从数据库查询出来的结果是转换为一个想XXXPo
的对象,而接口返回的是XXXVo
对象。在实际项目中,一般一个project都会分很多层,每层都会定义自己的对象,如:PO、VO、DAO、BO、DTO、POJO等等。
- DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
- DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
- BO(Business Object):业务对象,由 Service 层输出的封装业务逻辑的对象。
- AO(ApplicationObject):应用对象,在Web层与Service层之间抽象的复用对象模型, 极为贴近展示层,复用度不高。
- VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
- POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。
这些对象在传递过程中,需要相互转换,如果手工书写,往往会通过getxxx
,再setxxx
,操作起来非常繁琐,那有没有一种方式比较简单实现能?下面我们介绍一个对象转换的组件mapstruct
。
4.1 pom中引入依赖包
| |
| <dependency> |
| <groupId>org.mapstruct</groupId> |
| <artifactId>mapstruct</artifactId> |
| <version>1.5.4.Final</version> |
| </dependency> |
4.2 创建一个Convert接口
| package com.holmium.springboot.app.user.convert; |
| |
| import com.holmium.springboot.api.user.vo.UserVo; |
| import com.holmium.springboot.common.user.app.dto.UserDto; |
| import org.mapstruct.Mapper; |
| import org.mapstruct.factory.Mappers; |
| |
| |
| |
| |
| |
| @Mapper |
| public interface UserAppConvert { |
| UserAppConvert INSTANCES = Mappers.getMapper(UserAppConvert.class); |
| UserVo voToDto(UserDto userDto); |
| } |
避坑
这里的@Mapper
注解和上述Mybatis-plus
中的@Mapper
注解相同,很容易引入错误,导致会报错。
这里的Mapper
是import org.mapstruct.Mapper;
mybatis-plus
中的@Mapper
, import org.apache.ibatis.annotations.Mapper;
一定要注意看清楚引入的类
4.3 在类中引用
| @Service |
| public class UserAppImpl implements UserApp { |
| @Resource |
| public UserDomain userDomain; |
| @Override |
| public UserVo getUserInfoByUserId(String userId) throws Exception { |
| |
| return UserAppConvert.INSTANCES.voToDto(userDomain.getUserInfoByUid(userId)); |
| } |
| } |
4.4 对象属性不同怎么转换
上述是两个对象中属性相同话,可以不用做其他任何操作,就可以事项对象互转,但实际在开发中,两个对象属性不可能完全一样。而上述操作只能将相同属性做转换,不同的属性没法互相转换,导致对象有部分数据丢失?那怎么解决,可在转换方法上加入@Mappings
注解,如下:
| @Mapper |
| public interface UserAppConvert { |
| |
| UserAppConvert INSTANCES = Mappers.getMapper(UserAppConvert.class); |
| @Mappings({ |
| //属性不一致转换 |
| @Mapping(source = "name", target = "userName"), |
| |
| @Mapping(target = "createTime", expression = "java(com.java.mmzsblog.util.DateTransform.strToDate(source.getCreateTime()))"), |
| }) |
| UserVo voToDto(UserDto userDto); |
| } |
这样就解决了不同对象属性和属性类型不一致转换问题。
五、写在最后
截止本篇文章,我们已经将如何创建一个Spring boot 工程,如何开发一个服务,以及从数据库总获取数据基本已完成。根据这一系列文章,大家做日常的开发联系基本够。
后续章节将把druid和mybatis-plus的整合转换成starter的方式,对其做一定的封装。
同时我们会将系统的应用架构转换成DDD模式架构,后续我将用多个篇幅介绍DDD架构。请持续关注跟踪。