一、简介

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注解中有:whereStrategyupdateStrategyinsertStrategy 这三个属性,分别表示当使用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则在mapvaluenull时调用 isNull方法,为false时则忽略valuenull的。

  • 例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 = '老王'

3.ne

不等于<>

ne(R column, Object val)
ne(boolean condition, R column, Object val)

4.gt

大于>

gt(R column, Object val)
gt(boolean condition, R column, Object val)

5.ge

大于等于>=

ge(R column, Object val)
ge(boolean condition, R column, Object val)

6.lt

小于<

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)

16.in

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

  1. 接口类继承 IService<T>
public interface UserService extends IService<User> {
}
  1. 接口实现类中继承ServiceImpl<M,T>
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
	//调用mapper层可直接使用baseMapper
}