一、简介
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
二、特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
三、注解
1、@TableName
表名注解,用于entity类,注明该类所对应的表。
@Data
@TableName("user")
public class User implements Serializable {
...
}
2、@TableId
主键注解
@Data
@TableName("user")
public class User implements Serializable {
/**
* 用户id
*/
@TableId(value = "uid", type = IdType.AUTO)
private Integer uid;
}
3、@TableField
字段注解,与数据库表字段名对应
@Data
@TableName("user")
public class User implements Serializable {
/**
* 用户id
*/
@TableId(value = "uid", type = IdType.AUTO)
private Integer uid;
/**
* 用户昵称
*/
@TableField(value = "name")
private String name;
}
3.1、FieldStrategy
配置mybatisPlus的项目中,默认进行了不是全量更新的策略:NOT_NULL。
即在利用updateWrapper更新时,会做null判断,如果传参数为null,就不会更新。
在实际项目需求中可能配置全局更新策略为:NOT_NULL,但是某些场景更新时需要插入这个null值进行更新。如何解决这个问题呢?
在@TableField注解中有:whereStrategy
,updateStrategy
,insertStrategy
这三个属性,分别表示当使用where,更新,插入操作时,是否进行空值判断。
值 | 描述 |
---|---|
IGNORED | 忽略判断 |
NOT_NULL | 非NULL判断 |
NOT_EMPTY | 非空判断(只对字符串类型字段,其他类型字段依然为非NULL判断) |
DEFAULT | 追随全局配置 |
示例如下:
//当进行更新操作时,如果name这个属性的值为空,则进行空值判断,所以不更新name这个属性
@TableField(value = "name",updateStrategy = FieldStrategy.NOT_NULL)
private String name;
//当进行更新操作时,虽然name这个属性的值为空,但FieldStrategy为IGNORED,所以不进行空值判断,更新name这个属性为null
@TableField(value = "name",updateStrategy = FieldStrategy.IGNORED)
private String name;
3.2、FieldFill
在进行插入和更新操作时,可以自动对字段进行赋值,不需要手动,仅需进行相关配置
值 | 描述 |
---|---|
DEFAULT | 默认不处理 |
INSERT | 插入填充字段 |
UPDATE | 更新填充字段 |
INSERT_UPDATE | 插入和更新填充字段 |
@TableName("user")
public class UserBean {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@TableField(value = "tenant_id")
private String tenantId;
@TableField(value = "created_at",fill = FieldFill.INSERT)
private Date createdAt;
@TableField(value = "updated_by",fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
}
自动赋值配置:
在项目的config包下新建自动填充处理类使其实现接口MetaObjectHandler,接下来我们来写自动赋值的配置类,并重写其方法。
package com.ws.api.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 自动填充处理类
* @author badao
* @version 1.0
* @see
**/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
//在执行mybatisPlus的insert()时,为我们自动给某些字段填充值,这样的话,我们就不需要手动给insert()里的实体类赋值了
public void insertFill(MetaObject metaObject) {
//其中方法参数中第一个是前面自动填充所对应的字段,第二个是要自动填充的值。第三个是指定实体类的对象
this.setFieldValByName("modifierId", new Long(111), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
this.setFieldValByName("creatorId", new Long(111), metaObject);
this.setFieldValByName("gmtCreat",new Date(), metaObject);
this.setFieldValByName("availableFlag",true, metaObject);
}
@Override//在执行mybatisPlus的update()时,为我们自动给某些字段填充值,这样的话,我们就不需要手动给update()里的实体类赋值了
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("modifierId", new Long(111), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
}
4、@Version
乐观锁注解、标记 @Verison
在字段上。
在数据库并发操作时,为了保证数据的正确性,经常要对数据加锁,加锁有两种方式:悲观锁、乐观锁。
悲观锁:把所需要的数据全部加锁,不允许其他事务对数据做修改。
update xxx where xxxx for update
乐观锁:对数据进行版本校验,如果版本不一致,则操作数据失败。
update xxx,version+1 where xxxx and version=x
示例:
@Data
@TableName("user")
public class User implements Serializable {
/**
* 用户id
*/
@TableId(value = "uid", type = IdType.AUTO)
private Integer uid;
/**
* 用户昵称
*/
@TableField(value = "name",updateStrategy = FieldStrategy.NOT_NULL)
private String name;
/**
* 用户性别
*/
@TableField(value = "gender")
private String gender;
/**
* 版本
*/
@Version
@TableField(value = "version")
private String version;
}
5、@EnumValue
通枚举类注解(注解在枚举字段上)
案例:男,女,后台存储的是1,2,前端展示的是男女。
一、创建枚举类,在需要存储数据库的属性上添加@EnumValue注解,在需要前端展示的属性上添加@JsonValue注解;
@JsonValue
可以用在get方法或者属性字段上,一个类只能用一个,当加上@JsonValue注解时,该类的json化结果,只有这个get方法的返回值,而不是这个类的属性键值对.
示例:
1.创建枚举
package com.demo.mybatisplus.constant;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
public enum SexEnum {
MAN(1, "男"),
WOMAN(2, "女");
@EnumValue
private Integer key;
@JsonValue
private String display;
SexEnum(Integer key, String display) {
this.key = key;
this.display = display;
}
public Integer getKey() {
return key;
}
public String getDisplay() {
return display;
}
}
2.pojo中的sex属性设置为枚举
@ApiModelProperty(value = "性别")
private SexEnum sex;
6、@TableLogic
表字段逻辑处理注解(逻辑删除)
效果:在字段上加上这个注解再执行BaseMapper的删除方法时,删除方法会变成修改。
实体类中属性加上@TableLogic:
@TableLogic
@TableField(value = "deleted")
private Integer deleted;
说明:
只对自动注入的sql起效:
- 插入: 不作限制
- 查找: 追加where条件过滤掉已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
- 更新: 追加where条件防止更新到已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
- 删除: 转变为更新
例如:
- 删除:
update user set deleted=1 where id = 1 and deleted=0
- 查找:
select id,name,deleted from user where deleted=0
字段类型支持说明:
- 支持所有数据类型(推荐使用
Integer
,Boolean
,LocalDateTime
)- 如果数据库字段使用
datetime
,逻辑未删除值和已删除值支持配置为字符串null
,另一个值支持配置为函数来获取值如now()
附录:
- 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
- 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示
7、@OrderBy
内置 SQL 默认指定排序,优先级低于 wrapper 条件查询。
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
isDesc | boolean | 否 | 是 | 是否倒序查询 |
sort | short | 否 | Short.MAX_VALUE | 数字越小越靠前 |
四、条件构造器
一、AbstractWrapper
1.eq
相当于=
eq(R column, Object val)
eq(boolean condition, R column, Object val)
2.allEq
全部=
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
null2IsNull
: 默认为true,为true
则在map
的value
为null
时调用 isNull方法,为false
时则忽略value
为null
的。
- 例1:
allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})
—>name = '老王' and age is null
- 例2:
allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null}, false)
—>name = '老王'
不等于<>
ne(R column, Object val)
ne(boolean condition, R column, Object val)
大于>
gt(R column, Object val)
gt(boolean condition, R column, Object val)
大于等于>=
ge(R column, Object val)
ge(boolean condition, R column, Object val)
小于<
lt(R column, Object val)
lt(boolean condition, R column, Object val)
7.le
小于等于<=
le(R column, Object val)
le(boolean condition, R column, Object val)
8.between
between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
9.notBetween
notBetween(R column, Object val1, Object val2)
notBetween(boolean condition, R column, Object val1, Object val2)
10.like
LIKE ‘%值%’
like(R column, Object val)
like(boolean condition, R column, Object val)
11.notLike
NOT LIKE ‘%值%’
notLike(R column, Object val)
notLike(boolean condition, R column, Object val)
12.likeLeft
LIKE ‘%值’
likeLeft(R column, Object val)
likeLeft(boolean condition, R column, Object val)
13.likeRight
LIKE ‘值%’
likeRight(R column, Object val)
likeRight(boolean condition, R column, Object val)
14.isNull
isNull(R column)
isNull(boolean condition, R column)
15.isNotNull
isNotNull(R column)
isNotNull(boolean condition, R column)
in(R column, Collection<?> value)
in(boolean condition, R column, Collection<?> value)
- 字段 IN (value.get(0), value.get(1), …)
- 例:
in("age",{1,2,3})
—>age in (1,2,3)
in(R column, Object... values)
in(boolean condition, R column, Object... values)
- 字段 IN (v0, v1, …)
- 例:
in("age", 1, 2, 3)
—>age in (1,2,3)
17.notIn
notIn(R column, Collection<?> value)
notIn(boolean condition, R column, Collection<?> value)
- 字段 NOT IN (value.get(0), value.get(1), …)
- 例:
notIn("age",{1,2,3})
—>age not in (1,2,3)
notIn(R column, Object... values)
notIn(boolean condition, R column, Object... values)
- 字段 NOT IN (v0, v1, …)
- 例:
notIn("age", 1, 2, 3)
—>age not in (1,2,3)
18.inSql
inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)
- 字段 IN ( sql语句 )
- 例:
inSql("age", "1,2,3,4,5,6")
—>age in (1,2,3,4,5,6)
- 例:
inSql("id", "select id from table where id < 3")
—>id in (select id from table where id < 3)
19.notInSql
notInSql(R column, String inValue)
notInSql(boolean condition, R column, String inValue)
- 字段 NOT IN ( sql语句 )
- 例:
notInSql("age", "1,2,3,4,5,6")
—>age not in (1,2,3,4,5,6)
- 例:
notInSql("id", "select id from table where id < 3")
—>id not in (select id from table where id < 3)
20.groupBy
groupBy(R... columns)
groupBy(boolean condition, R... columns)
- 分组:GROUP BY 字段, …
- 例:
groupBy("id", "name")
—>group by id,name
21.orderByAsc
orderByAsc(R... columns)
orderByAsc(boolean condition, R... columns)
- 排序:ORDER BY 字段, … ASC
- 例:
orderByAsc("id", "name")
—>order by id ASC,name ASC
22.orderByDesc
orderByDesc(R... columns)
orderByDesc(boolean condition, R... columns)
- 排序:ORDER BY 字段, … DESC
- 例:
orderByDesc("id", "name")
—>order by id DESC,name DESC
23.orderBy
orderBy(boolean condition, boolean isAsc, R... columns)
- 排序:ORDER BY 字段, …
- 例:
orderBy(true, true, "id", "name")
—>order by id ASC,name ASC
- 例:
orderBy(true, false, "id", :"name")
—>order by id DESC,name DESC
24.having
having(String sqlHaving, Object... params)
having(boolean condition, String sqlHaving, Object... params)
- HAVING ( sql语句 )
- 例:
having("sum(age) > 10")
—>having sum(age) > 10
- 例:
having("sum(age) > {0}", 11)
—>having sum(age) > 11
25.or
注意:主动调用or
表示紧接着下一个方法不是用and
连接!(不调用or
则默认为使用and
连接)
or()
or(boolean condition)
- 例:
eq("id",1).or().eq("name","老王")
—>id = 1 or name = '老王'
or(Consumer<Param> consumer)
or(boolean condition, Consumer<Param> consumer)
- OR 嵌套
- 例:
or(i -> i.eq("name", "李白").ne("status", "活着"))
—>or (name = '李白' and status <> '活着')
26.and
and(Consumer<Param> consumer)
and(boolean condition, Consumer<Param> consumer)
- AND 嵌套
- 例:
and(i -> i.eq("name", "李白").ne("status", "活着"))
—>and (name = '李白' and status <> '活着')
27.nested
nested(Consumer<Param> consumer)
nested(boolean condition, Consumer<Param> consumer)
- 正常嵌套 不带 AND 或者 OR
- 例:
nested(i -> i.eq("name", "李白").ne("status", "活着"))
—>(name = '李白' and status <> '活着')
28.apply
注意:该方法可用于数据库函数 动态入参的params
对应前面applySql
内部的{index}
部分.这样是不会有sql注入风险的,反之会有!
apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)
- 例:
apply("id = 1")
—>id = 1
- 例:
apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08")
—>date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
- 例:
apply("price = {0} and pid > {1}", 3, 5)
—>price = 3 and pid > 5
29.last
注意:只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用
last(String lastSql)
last(boolean condition, String lastSql)
- 例:
last("limit 1")
30.exists
exists(String existsSql)
exists(boolean condition, String existsSql)
- 拼接 EXISTS ( sql语句 )
- 例:
exists("select id from table where age = 1")
—>exists (select id from table where age = 1)
31.notExists
notExists(String notExistsSql)
notExists(boolean condition, String notExistsSql)
- 拼接 NOT EXISTS ( sql语句 )
- 例:
notExists("select id from table where age = 1")
—>not exists (select id from table where age = 1)
二、QueryWrapper
1.select
select(String... sqlSelect)
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
- 例:
select("id", "name", "age")
- 例:
select(i -> i.getProperty().startsWith("test"))
三、UpdateWrapper
1.set
set(String column, Object val)
set(boolean condition, String column, Object val)
- SQL SET 字段
- 例:
set("name", "老李头")
- 例:
set("name", "")
—>数据库字段值变为空字符串 - 例:
set("name", null)
—>数据库字段值变为null
2.setSql
setSql(String sql)
- 设置 SET 部分 SQL
- 例:
setSql("name = '老李头'")
四、自定义SQL
注意事项:
需要
mybatis-plus
版本 >=3.0.7
param 参数名要么叫ew
,要么加上注解@Param(Constants.WRAPPER)
使用${ew.customSqlSegment}
不支持Wrapper
内的entity生成where语句
service层:
QueryWrapper<Email> query = new QueryWrapper<>();
List<String> ids = userService.getUserIds();
if(ids != null && ids.isEmpty()){
query.lambda().in(Email::getUid, ids);
}else{
query.apply("1!=1");
}
List<Email> list = emailService.getEmails(query);
maper层:
//用注解
@Select("select * from email ${ew.customSqlSegment}")
List<Email> getEmails(@Param(Constants.WRAPPER) Wrapper wrapper);
//用XML
List<Email> getEmails(Wrapper ew);
<select id="getEmails" resultType="Email">
SELECT * FROM email ${ew.customSqlSegment}
</select>
五、插件
InnerInterceptor
所有的插件都基于此接口实现。
目前已有的插件:
- 自动分页: PaginationInnerInterceptor
- 多租户: TenantLineInnerInterceptor
- 动态表名: DynamicTableNameInnerInterceptor
- 乐观锁: OptimisticLockerInnerInterceptor
- sql性能规范: IllegalSQLInnerInterceptor
- 防止全表更新与删除: BlockAttackInnerInterceptor
注意:
使用多个功能需要注意顺序关系,建议使用如下顺序
- 多租户,动态表名
- 分页,乐观锁
- sql性能规范,防止全表更新与删除
总结: 对sql进行单次改造的优先放入,不对sql进行改造的最后放入
1、分页
PaginationInnerInterceptor
属性:
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
overflow | boolean | false | 溢出总页数后是否进行处理(默认不处理,参见 插件#continuePage 方法) |
maxLimit | Long | 单页分页条数限制(默认无限制,参见 插件#handlerLimit 方法) |
|
dbType | DbType | 数据库类型(根据类型获取应使用的分页方言,参见 插件#findIDialect 方法) |
|
dialect | IDialect | 方言实现类(参见 插件#findIDialect 方法) |
1.增加分页插件配置类
@Configuration
@MapperScan("com.lunz.demo4.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
2.编写Dao层接口
@Repository
public interface MailMapper extends BaseMapper<Mail> {
IPage<Mail> selectPageVo(Page<?> page);
}
Mapper.xml等同于编写一个普通 list 查询,mybatis-plus 自动替你分页
<select id="selectPageVo" resultType="mail">
select * from mail
</select>
注意:加不加where取决于你是否携带查询条件
3.编写service层
public interface MailService {
IPage<Mail> mailPage(Page<Mail> page);
}
@Service
public class MailServiceImpl implements MailService {
@Autowired
private MailMapper mapper;
@Override
public IPage<Mail> mailPage(Page<Mail> page) {
return mapper.selectPageVo(page);
}
}
4.编写controller
@RequestMapping("mail")
@CrossOrigin
@RestController
public class HelloController {
@Autowired
private MailService mailService;
//pageindex为当前页,size为每页显示数目
@GetMapping("page/{pageindex}/{size}")
public IPage<Mail> page(@PathVariable(value = "pageindex") Integer pageindex,@PathVariable(value = "size") Integer size){
return mailService.mailPage(new Page<Mail>(pageindex,size));
}
}
2、乐观锁
OptimisticLockerInnerInterceptor
说明:
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
配置:
1.在MybatisPlusConfig类中加入
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
2.在实体类上加上@Version注解
@Version
private Integer version;
说明:
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整数类型下
newVersion = oldVersion + 1
newVersion
会回写到entity
中- 仅支持
updateById(id)
与update(entity, wrapper)
方法 - 在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
六、通用service
- 接口类继承
IService<T>
public interface UserService extends IService<User> {
}
- 接口实现类中继承
ServiceImpl<M,T>
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
//调用mapper层可直接使用baseMapper
}