文章目录
- 前言
- 一、快速入门
- 1.1引入依赖
- 1.2定义Mapper
- 1.3常见注解
- 1.4常见配置
- 二、核心功能
- 2.1条件构造器
- 2.2LambdaQueryWrapper
- 2.3自定义SQL
- 2.4多表关联查询
- 三、Service接口
- 四、扩展功能
- 4.1代码生成
- 4.2静态工具
- 4.3逻辑删除
- 4.4通用枚举
- 4.5JSON类型处理器
- 五、插件功能
前言
本系列笔记内容均来自黑马程序员:2024SpringCloud视频
一、快速入门
1.1引入依赖
MybatisPlus提供了starter,实现了自动Mybatis以及MybatisPlus的自动装配功能,坐标如下:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
1.2定义Mapper
为了简化单表CRUD,MybatisPlus提供了一个基础的BaseMapper接口,其中已经实现了单表的CRUD,
因此我们自定义的Mapper只要实现了这个BaseMapper,就无需自己实现单表CRUD了。
public interface UserMapper extends BaseMapper<User> {
}
MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。
泛型中的User就是与数据库对应的PO.
MybatisPlus就是根据PO实体的信息来推断出表的信息,从而生成SQL的。默认情况下:
- MybatisPlus会把PO实体的类名驼峰转下划线作为表名
- MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型
- MybatisPlus会把名为id的字段作为主键
1.3常见注解
@TableName("tb_user")
public class User {
@TableId(value="id",type=IdType.AUTO)
private Long id;
@TableField("username")
private String name;
@TableField("is_married")
private Boolean isMarried;
@TableField("`order`")
private Integer order;
@TableField(exist = false)
private String address;
}
@TableName
说明:
- 描述:表名注解,标识实体类对应的表
- 使用位置:实体类
TableName注解除了指定表名以外,还可以指定很多其它属性:
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 表名 |
schema | String | 否 | “” | schema |
keepGlobalPrefix | boolean | 否 | false | 是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时) |
resultMap | String | 否 | “” | xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定) |
autoResultMap | boolean | 否 | false | 是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入) |
excludeProperty | String[] | 否 | {} | 需要排除的属性名 @since 3.3.1 |
@TableId
说明:
- 描述:主键注解,标识实体类中的主键字段
- 使用位置:实体类的主键字段
TableId注解支持两个属性:
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 主键的字段名 |
type | Enum | 否 | IdType.NONE | 指定主键类型 |
IdType支持的类型有:
值 | 描述 |
---|---|
AUTO | 数据库 ID 自增 |
NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
INPUT | insert 前自行 set 主键值 |
ASSIGN_ID | 分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法) |
ASSIGN_UUID | 分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法) |
ID_WORKER | 分布式全局唯一 ID 长整型类型(please use ASSIGN_ID) |
UUID | 32 位 UUID 字符串(please use ASSIGN_UUID) |
ID_WORKER_STR | 分布式全局唯一 ID 字符串类型(please use ASSIGN_ID) |
这里比较常见的有三种:
AUTO
:利用数据库的id自增长INPUT
:手动生成idASSIGN_ID
:雪花算法生成Long
类型的全局唯一id,这是默认的ID策略
@TableField
说明:
描述:普通字段注解
一般情况下我们并不需要给字段添加@TableField
注解,一些特殊情况除外:
- 成员变量名与数据库字段名不一致
- 成员变量是以
isXXX
命名,按照JavaBean
的规范,MybatisPlus
识别字段时会把is
去除,这就导致与数据库不符。 - 成员变量名与数据库一致,但是与数据库的关键字冲突。使用
@TableField
注解给字段名添加转义字符:````
支持的其它属性如下:
属性 | 类型 | 必填 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | “” | 数据库字段名 |
exist | boolean | 否 | true | 是否为数据库表字段 |
condition | String | 否 | “” | 字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s},参考(opens new window) |
update | String | 否 | “” | 字段 update set 部分注入,例如:当在version字段上注解update="%s+1" 表示更新时会 set version=version+1 (该属性优先级高于 el 属性) |
insertStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:NOT_NULL insert into table_a(column) values (#{columnProperty}) |
updateStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:IGNORED update table_a set column=#{columnProperty} |
whereStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:NOT_EMPTY where column=#{columnProperty} |
fill | Enum | 否 | FieldFill.DEFAULT | 字段自动填充策略 |
select | boolean | 否 | true | 是否进行 select 查询 |
keepGlobalFormat | boolean | 否 | false | 是否保持使用全局的 format 进行处理 |
jdbcType | JdbcType | 否 | JdbcType.UNDEFINED | JDBC 类型 (该默认值不代表会按照该值生效) |
typeHandler | TypeHander | 否 | 类型处理器 (该默认值不代表会按照该值生效) | |
numericScale | String | 否 | “” | 指定小数点后保留的位数 |
1.4常见配置
大多数的配置都有默认值,我们无需配置。但还有一些是没有默认值的,例如:
- 全局id类型
mybatis-plus:
global-config:
db-config:
id-type: auto # 全局id类型为自增长
需要注意的是,MyBatisPlus也支持手写SQL的,而mapper文件的读取地址可以自己配置:
mybatis-plus:
mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,当前这个是默认值。
MyBatisPlus使用的基本流程是什么?
①引入起步依赖
②自定义Mapper基础BaseMapper
③在实体类上添加注解声明 表信息
④在application.yml中根据需要添加配置
二、核心功能
2.1条件构造器
除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外,还支持更加复杂的where条件。
参数中的Wrapper就是条件构造的抽象类,其下有很多默认实现,
Wrapper的子类AbstractWrapper提供了where中包含的所有条件构造方法:
QueryWrapper在AbstractWrapper的基础上拓展了一个select方法,允许指定查询字段:
@Test
void testQueryWrapper() {
// 1.构建查询条件 where name like "%o%" AND balance >= 1000
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id", "username", "info", "balance")
.like("username", "o")
.ge("balance", 1000);
// 2.查询数据
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法,允许指定SQL中的SET部分:
UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4)
@Test
void testUpdateWrapper() {
List<Long> ids = List.of(1L, 2L, 4L);
// 1.生成SQL
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200") // SET balance = balance - 200
.in("id", ids); // WHERE id in (1, 2, 4)
// 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据,
// 而是基于UpdateWrapper中的setSQL来更新
userMapper.update(null, wrapper);
}
- QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分
- UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用
- 尽量使用LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码
2.2LambdaQueryWrapper
@Test
void testLambdaQueryWrapper() {
// 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
// 2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
2.3自定义SQL
①基于Wrapper构建where条件
List<Long> ids = List.of(1L, 2L, 4L);
int amount = 200;
// 1.构建条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getId, ids);
// 2.自定义SQL方法调用
userMapper.updateBalanceByIds(wrapper, amount);
②在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew
void updateBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper, @Param("amount") int amount);
③自定义SQL,并使用Wrapper条件
<update id="updateBalanceByIds">
UPDATE tb_user SET balance = balance - #{amount} ${ew.customSqlSegment}
</update>
2.4多表关联查询
<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
SELECT *
FROM user u
INNER JOIN address a ON u.id = a.user_id
WHERE u.id
<foreach collection="ids" separator="," item="id" open="IN (" close=")">
#{id}
</foreach>
AND a.city = #{city}
</select>
@Test
void testCustomJoinWrapper() {
// 1.准备自定义查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("u.id", List.of(1L, 2L, 4L))
.eq("a.city", "北京");
// 2.调用mapper的自定义方法
List<User> users = userMapper.queryUserByWrapper(wrapper);
users.forEach(System.out::println);
}
Mapper中自定义方法:
@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);
三、Service接口
MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。
通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:
save:新增,remove:删除,update:更新,get:查询单个结果,list:查询集合结果,count:计数, page:分页查询
service接口
MP的Service接口使用流程
- 自定义Service接口继承IService接口
public interface IUserService extends IService<User> {}
- 自定义Service实现类,实现自定义接口并继承ServiceImpl类
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
Iservice的注入
@RequiredArgsConstructor
public class UserController {
private final IUserService userService;
}
@RequiredArgsConstructor 为类中所有被标记为 final 的字段自动生成一个构造函数。
四、扩展功能
4.1代码生成
MybatisPlus官方提供了代码生成器根据数据库表结构生成PO、Mapper、Service等相关代码。
在Idea的plugins市场中搜索并安装MyBatisPlus插件,重启Idea即可使用。
Config Database的配置
Code Generator的配置
4.2静态工具
Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现CRUD功能
4.3逻辑删除
对于一些比较重要的数据,我们往往会采用逻辑删除的方案,即:
- 在表中添加一个字段标记数据是否被删除
- 当删除数据时把标记置为true
- 查询时过滤掉标记为true的数据
注意,只有MybatisPlus生成的SQL语句才支持自动的逻辑删除,自定义SQL需要自己手动处理逻辑删除。
给address表添加一个逻辑删除字段:
alter table address add deleted bit default b'0' null comment '逻辑删除';
然后给Address实体添加deleted字段
@Data
public class Address{
// 逻辑删除
private Boolean deleted;
}
在application.yml中配置逻辑删除字段:
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
执行一个删除操作:
@Test
void testDeleteByLogic() {
// 删除方法与以前没有区别
addressService.removeById(59L);
}
方法与普通删除一模一样,但是底层的SQL逻辑变了
4.4通用枚举
User类中有一个用户状态字段:
// 使用状态(1正常 2冻结)
private Integer status;
MybatisPlus提供了一个处理枚举的类型转换器,可以把枚举类型与数据库类型自动转换。
@Getter
public enum UserStatus {
NORMAL(1, "正常"),
FREEZE(2, "冻结")
;
@EnumValue //MybatisPlus提供了@EnumValue注解来标记枚举属性
private final int value;
@JsonValue //@JsonValue注解标记JSON序列化时展示的字段
private final String desc;
UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}
User类中的status字段改为UserStatus 类型:
private UserStatus status;
在application.yaml文件中添加配置:
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
4.5JSON类型处理器
MybatisPlus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理JSON就可以使用JacksonTypeHandler处理器。
public class UserInfo {
private Integer age;
private String intro;
private String gender;
}
将User类的info字段修改为UserInfo类型,并声明类型处理器:
public class User {
private Long id;
private String username;
@TableField(typeHandler = JacksonTypeHandler.class)
private UserInfo info;
}
五、插件功能
MyBatisPlus提供的内置拦截器有下面这些:
序号 | 拦截器 | 描述 |
---|---|---|
1 | TenantLineInnerInterceptor | 多租户插件 |
2 | DynamicTableNameInnerInterceptor | 动态表名插件 |
3 | PaginationInnerInterceptor | 分页插件 |
4 | OptimisticLockerInnerInterceptor | 乐观锁插件 |
5 | IllegalSQLInnerInterceptor | SQL性能规范插件,检测并拦截垃圾SQL |
6 | BlockAttackInnerInterceptor | 防止全表更新和删除的插件 |
通用分页实体
@EqualsAndHashCode(callSuper = true)
public class UserQuery extends PageQuery {
@ApiModelProperty("用户名关键字")
private String name;
@ApiModelProperty("用户状态:1-正常,2-冻结")
private Integer status;
@ApiModelProperty("余额最小值")
private Integer minBalance;
@ApiModelProperty("余额最大值")
private Integer maxBalance;
}
PageQuery是前端提交的查询参数,一般包含四个属性:
public class PageQuery {
@ApiModelProperty("页码")
private Long pageNo;
@ApiModelProperty("页码")
private Long pageSize;
@ApiModelProperty("排序字段")
private String sortBy;
@ApiModelProperty("是否升序")
private Boolean isAsc;
}