Mybatis-Plus:Mybatis增强工具

一、介绍

1 概述

官网:https://baomidou.com/

MyBatis-Plus(简称 MP,是由baomidou(苞米豆)组织开源的)是一个基于 MyBatis 的增强工具,它对 Mybatis 的基础功能进行了增强,但未做任何改变。使得我们可以在 Mybatis 开发的项目上直接进行升级为 Mybatis-plus,正如它对自己的定位,它能够帮助我们进一步简化开发过程,提高开发效率。

Mybatis-Plus 其实可以看作是对 Mybatis 的再一次封装,升级之后,对于单表的 CRUD 操作,调用 Mybatis-Plus 所提供的 API 就能够轻松实现,此外还提供了各种查询方式、分页等行为。最最重要的,开发人员还不用去编写 XML,这就大大降低了开发难度。

 2 特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响。

  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作。

  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求。

  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错。

  • 支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多种数据库。

  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题。

  • 支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动。

  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作。

  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )。

  • 支持关键词自动转义:支持数据库关键词(order、key......)自动转义,还可自定义关键词。

  • 内置代码生成器:采用代码或者Maven 插件可快速生成 Mapper 、Model 、Service 、Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用。

  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询。

  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询。

  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作。

  • 内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击。

 二、快速入门

1. 引入依赖

<dependencies>
    <!-- mybatis-plus框架 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.2</version>
    </dependency>
    <!--mysql数据库库-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.26</version>
    </dependency>
</dependencies>

2. 配置数据源

# 数据源配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
# 日志级别配置
logging:
  level:
    com.xxx.mapper: trace

 3. 创建实体类

@Data
@TableName
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

 4. 创建映射接口

  • 继承BaseMapper

  • BaseMapper中提供了很多的方法,继承后可以直接使用

public interface UserMapper extends BaseMapper<User> {
}

 5. 启动类扫描映射接口

@SpringBootApplication
@MapperScan("com.xxx.mapper")
public class MybatisplusApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisplusApplication.class, args);
    }
}

三、通用CRUD

1. MybatisPlus日志配置 

mybatis-plus:
  configuration:
    #mybatis-plus日志控制台输出
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    #关闭banner
    banner: false

 2. 插入操作

 @TableId 
名称@TableId
类型属性注解
位置模型类中用于表示主键的属性定义上方
作用设置当前类中主键属性的生成策略
相关属性value(默认):设置数据库表主键名称,字段名和属性名相同可以省略 type:设置主键属性的生成策略,值查照IdType的枚举值

 使用数据库表主键自增一般设置为AUTO;

@Getter
public enum IdType {
    /**
     * 数据库ID自增
     * <p>该类型请确保数据库设置了 ID自增 否则无效</p>
     */
    AUTO(0),
    /**
     * 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
     * 注解中指定为NONE使用全局的生成策略,默认使用雪花算法
     */
    NONE(1),
    /**
     * 用户输入ID
     * <p>该类型可以通过自己注册自动填充插件进行填充</p>
     */
    INPUT(2),

    /* 以下2种类型、只有当插入对象ID 为空,才自动填充。 */
    /**
     * 分配ID (主键类型为number或string)
     * 雪花算法生成id
     */
    ASSIGN_ID(3),
    /**
     * 分配UUID (主键类型为 string)
     */
    ASSIGN_UUID(4);
}
  1. AUTO:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用。

  2. NONE: 跟随全局的设置。

  3. INPUT:手动输入或使用插件生成id。

  4. ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键。

  5. ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢。

 修改id生成策略 

  1. 局部修改

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

   2.全局修改

mybatis-plus:
  global-config:
    db-config:
    	id-type: auto
映射匹配设置
 @TableField
名称@TableField
类型属性注解
位置模型类属性定义上方
作用设置当前属性对应的数据库表中的字段关系
相关属性value(默认):设置数据库表字段名称 exist:设置属性在数据库表字段中是否存在,默认为true,此属性不能与value合并使用 select:设置属性是否参与查询,此属性与select()映射配置不冲突
 @TableName
名称@TableName
类型类注解
位置模型类定义上方
作用设置当前类对应于数据库表关系
相关属性value(默认):设置数据库表名称

3. 更新操作

  根据id更新

int updateById(@Param(Constants.ENTITY) T entity); 

根据条件更新 
/**
  * 根据 whereEntity 条件,更新记录
  *
  * @param entity        实体对象 (set 条件值,可以为 null)
  * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
  */
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper)

 4.删除操作

1 根据id删除 
/**
  * 根据 ID 删除
  *
  * @param id 主键ID
  */
int deleteById(Serializable id);

/**
  * 根据实体(ID)删除
  *
  * @param entity 实体对象
  * @since 3.4.4
  */
int deleteById(T entity);
 2 根据指定字段删除
/**
  * 根据 columnMap 条件,删除记录
  *
  * @param columnMap 表字段 map 对象
  */
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);


//例如
@Test
void contextLoads() {
    Map<String, Object> columnMap = new HashMap<>();
    columnMap.put("uname","Jack");
    columnMap.put("age",20);

    //将columnMap中的键值对设置为删除的条件,多个之间为and关系
    int i = this.userMapper.deleteByMap(columnMap);
}
 3 根据Wrapper删除
/**
  * 根据 entity 条件,删除记录
  *
  * @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
  */
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

//例如
@Test
void contextLoads() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("uname", "Tom");
    queryWrapper.eq("age", "28");
    int i = userMapper.delete(queryWrapper);

}

5 查询操作

1 根据id查询 

T selectById(Serializable id); 

2 根据id批量查询 
/**
  * 查询(根据ID 批量查询)
  *
  * @param idList 主键ID列表(不能为 null 以及 empty)
  */
List<T> selectBatchIds(@Param(Constants.COLL) Collection<? extends Serializable> idList);

//例如
@Test
void contextLoads() {
    List<User> list = userMapper.selectBatchIds(Arrays.asList(1L, 2L));
}
 3 查询一条数据
/**
 * 根据 entity 条件,查询一条记录
 * <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常</p>
 *
 * @param queryWrapper 实体对象封装操作类(可以为 null)
 */
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
    //...
}

@Test
void contextLoads() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("uname", "Jack");
    queryWrapper.eq("age", 20);
    User user = userMapper.selectOne(queryWrapper);
}
 4 查询记录数
/**
 * 根据 Wrapper 条件,查询总记录数
 *
 * @param queryWrapper 实体对象封装操作类(可以为 null)
 */
Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

@Test
void contextLoads() {
    Long aLong = userMapper.selectCount(null);
}
 5 查询多条数据
/**
 * 根据 entity 条件,查询全部记录
 *
 * @param queryWrapper 实体对象封装操作类(可以为 null)
 */
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);


@Test
void contextLoads() {
    List<User> list = userMapper.selectList(null);
}
6 分页查询
/**
 * 根据 entity 条件,查询全部记录(并翻页)
 *
 * @param page         分页查询条件(可以为 RowBounds.DEFAULT)
 * @param queryWrapper 实体对象封装操作类(可以为 null)
 */
<P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);


//实例
@Test
void contextLoads() {
    //1 创建IPage分页对象,设置分页参数,1为当前页码,2为每页显示的记录数
    IPage<User> page=new Page<>(1,3);
    //2 执行分页查询
    userMapper.selectPage(page,null);
    //3 获取分页结果,不配置拦截器就只能获取到current和size的数据,pages,total都是0,records是全部数据
    System.out.println("当前页码值:"+page.getCurrent());
    System.out.println("每页显示数:"+page.getSize());
    System.out.println("一共多少页:"+page.getPages());
    System.out.println("一共多少条数据:"+page.getTotal());
    System.out.println("数据:"+page.getRecords());
}

 注意:必须配置mybatis拦截器才能生效。

//注解为配置类@Configuration,也可以在引导类@Import({MybatisPlusConfig.class})
@Configuration
public class MybatisPlusConfig {
    //被Spring容器管理
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //1 创建mp拦截器对象MybatisPlusInterceptor
        MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
        //2 添加内置拦截器,参数为分页内置拦截器对象PaginationInnerInterceptor
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }
}

四、配置文件

在MP中有大量的配置,其中有一部分是Mybatis原生的配置,另一部分是MP的配置。

详情:使用配置 | MyBatis-Plus

configLocation

MyBatis 配置文件位置,如果有单独的 MyBatis 配置,请将其路径配置到 configLocation 中。 

Spring Boot:

mybatis-plus:
  config-location: classpath:mybatis-config.xml

mapperLocations

MyBatis Mapper 所对应的 XML 文件位置,如果您在 Mapper 中有自定义方法(XML 中有自定义实 现),需要进行该配置,告诉 Mapper 所对应的 XML 文件位置。 

mybatis-plus:
  #Maven 多模块项目的扫描路径需以 classpath*: 开头 (即可以加载jar包下的 XML 文件)
  mapper-locations: classpath*:mybatis/*.xml

 typeAliasesPackage

MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 文件 中可以直接使用类名,而不用使用全限定的类名(即 XML 中调用的时候不用包含包名)。 

mybatis-plus:
  type-aliases-package: com.xxx.pojo

  cacheEnabled

 开启Mybatis二级缓存,默认为 true。

mybatis-plus:
  configuration:
    cache-enabled: false

 五、DQL高级篇

增删改查四个操作中,查询是非常重要的也是非常复杂的操作。 

1. 条件查询 

1 条件查询的Wrapper包装器类
  • MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。

  • 查询数据的时候,看到过一个Wrapper,这个类就是用来构建查询条件

包装器Wrapper<T>是接口,实际开发中主要使用它的两个实现类:

  1. QueryWrapper

  2. LambdaQueryWrapper

两种方式各有优劣:

  • QueryWrapper存在属性名写错的危险,但是支持聚合、分组查询;

  • LambdaQueryWrapper没有属性名写错的危险,但不支持聚合、分组查询;

2 基本查询条件
  • alleq

    • 全部eq或个别isNull

    • allEq({id:1,name:"Arvin",age:null})--->id = 1 and name = 'Arvin' and age is null

  • eq

    • 等于 =

  • ne

    • 不等于 <>

  • gt

    • 大于 >

  • ge

    • 大于等于 >=

  • lt

    • 小于 <

  • le

    • 小于等于 <=

  • between

    • BETWEEN 值1 AND 值2

  • notBetween

    • NOT BETWEEN 值1 AND 值2

  • in

    • 字段 IN (...)

  • notIn

    • 字段 NOT IN (...)

  • like

    • like():前后加百分号,如 %J%

    • likeLeft():左边加百分号,如 %J

    • likeRight():后面加百分号,如 J%

3 构建条件查询

QueryWrapper  

@Test
void contextLoads() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("uname", "Tom").gt("age", 20);
    List<User> list = userMapper.selectList(queryWrapper);
}

LambdaQueryWrapper

@Test
void contextLoads() {
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(User::getName, "Tom").gt(User::getAge, 20);
    List<User> list = userMapper.selectList(wrapper);
}

 注意:默认使用and连接,使用or需要自己指定

@Test
void contextLoads() {
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(User::getName, "Tom").or().gt(User::getAge, 20);
    List<User> list = userMapper.selectList(wrapper);
}

 2. 查询投影

  • 在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容。

  • 查询投影:不查询所有字段,只查询出指定字段的数据

//使用Lambda
@Test
void contextLoads() {
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(User::getName, "Tom").gt(User::getAge, 20);
    wrapper.select(User::getId, User::getName);
    List<User> list = userMapper.selectList(wrapper);
}
//不用lambda
@Test
void contextLoads() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("uname", "Tom").gt("age", 20);
    queryWrapper.select("uid", "uname");
    List<User> list = userMapper.selectList(queryWrapper);
}

3. 逻辑删除

  • 逻辑删除的本质其实是修改操作。

@TableLogic

名称@TableLogic
类型属性注解
位置模型类中用于表示删除字段的属性定义上方
作用标识该字段为进行逻辑删除的字段
相关属性value:逻辑未删除值 delval:逻辑删除值
@TableName("tb_user")
public class User {
    @TableId(value = "uid", type = IdType.AUTO)
    private Long id;
    @TableField("uname")
    private String name;
    private Integer age;
    @TableField("uemail")
    @TableLogic(value = "test1@baomidou.com", delval = "逻辑删除")
    private String email;
}


@Test
void contextLoads() {
    int i = userMapper.deleteById(1L);
}



==>  Preparing: UPDATE tb_user SET uemail='逻辑删除' WHERE uid=? AND uemail='test1@baomidou.com'
==>  Parameters: 1(Long)
<==    Updates: 1

4. 悲观锁与乐观锁

悲观锁

 悲观锁在操作数据时比较悲观,每次更新数据的时候认为别的线程也会同时更新数据,所以每次更新数据是都会上锁,这样别的线程就会阻塞等待获取锁。

乐观锁

乐观锁在更新数据时非常乐观,认为别的线程不会同时更新数据,所以不会上锁,但是在更新之前会判断在此期间别的线程是否有更新过该数据。 

乐观锁实现思路

  1. 数据库表中添加version列,比如默认值给1。

  2. 第一个线程要修改数据之前,取出记录时,获取当前数据库中的version=1。

  3. 第二个线程要修改数据之前,取出记录时,获取当前数据库中的version=1。

  4. 第一个线程执行更新时,set version = newVersion where version = oldVersion

    1. newVersion = version+1 [2]

    2. oldVersion = version [1]

  5. 第二个线程执行更新时,set version = newVersion where version = oldVersion

    1. newVersion = version+1 [2]

    2. oldVersion = version [1]

乐观锁实现
1.添加version属性,添加@Version注解
@TableName("tb_user")
public class User {
    @TableId(value = "uid", type = IdType.AUTO)
    private Long id;
    @TableField("uname")
    private String name;
    private Integer age;
    @TableField("uemail")
    private String email;
    @Version
    private Integer version;
}
2.配置mybatis拦截器
@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mpInterceptor() {
        //1.定义Mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        //2.添加乐观锁拦截器
        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mpInterceptor;
    }
}
 3.先查询version再更新操作
@Test
void contextLoads() {
    User user = userMapper.selectById(2L);
    user.setAge(30);
    int i = userMapper.updateById(user);
}

六、Service的CRUD

  1. IService<User>

  2. ServiceImpl<UserMapper, User>

查询 

public interface UserService extends IService<User> {
}

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
Get
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录,有多个 result 是否抛出异常
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录,转换函数
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
List
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
Page
// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
Count
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
Chain
// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery();
 
// 示例:
query().eq("column", value).one();
lambdaQuery().eq(Entity::getId, value).list();

 

添加

Save
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);

删除

// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);

更新

// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);

 

  • 21
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值