MyBatisPlus基础知识

参考链接:https://www.bilibili.com/video/BV12T4y1B7C3

1 MyBatisPlus简介

1.1 简介

MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率。

1.2 开发方式

  • 基于MyBatis使用MyBatisPlus
  • 基于Spring使用MyBatisPlus
  • 基于SpringBoot使用MyBatisPlus

1.3 SpringBoot整合MyBatis开发过程

1、创建SpringBoot工程
image
2、勾选配置使用的技术
image
1)删除无用的文件
image
2)手动添加mp依赖
【原因】mp未被收录到idea的系统内置配置,无法直接选择加入

<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
	<version>3.4.1</version>
</dependency>
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
	<version>1.1.16</version>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>

image
3、设置dataSource相关属性(JDBC参数application.yml)

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mybatisplus_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&serverTimezone=UTC
    username: root
    password: qwe321456
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
  main:
    banner-mode: off  # 去掉springboot的图标

server:
  port: 8888
# mp日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  globalConfig:
    banner: false   # 去掉mybatis-plus的banner

4、制作实体类与表结构

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `name` varchar(32) DEFAULT NULL COMMENT '用户名',
  `password` varchar(32) DEFAULT NULL COMMENT '密码',
  `age` int(3) DEFAULT NULL COMMENT '年龄',
  `tel` varchar(32) DEFAULT NULL COMMENT '电话',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

User

package com.fanxl.mybatisplus.domain;/**
*@Author: fanxl
*@CreateTime: 2023-04-30  13:27
*/
@Data
public class User {
    private  Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
}

5、定义数据层接口映射配置,继承BaseMapper<User>

package com.fanxl.mybatisplus.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.fanxl.mybatisplus.domain.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDao extends BaseMapper<User> {

}

6、再测试类中注入dao接口,测试功能

@SpringBootTest
class MybatisPlusApplicationTests {
    @Autowired
    private UserDao userDao;
    @Test
    void  testGetAll(){
        List<User> userList = userDao.selectList(null);
        System.out.println(userList);
    }
}

2 标准数据层开发

image

名称 含义
eq 等于=
ne 不等于!=
gt 大于>
ge 大于等于>=
lt 小于<
le 小于等于<=
between 包含 例between(A,B) A<= x <=B
like 模糊查询 %a%
likeRight a%
likeLeft %a

2.1 测试分页

@Test
void testGetByPage(){
	IPage page = new Page(1,2);
	userDao.selectPage(page,null);
	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());
}

输出结果:

当前页码值1
每页显示数2
一共多少页0
一共多少条数据0
数据[User(id=1, name=Tom11, password=tom, age=3, tel=1111), User(id=2, name=Jerry, password=jerry, age=4, tel=2222), User(id=3, name=Jock, password=jock, age=18, tel=3333), User(id=4, name=Timi, password=tmi, age=25, tel=4444), User(id=1652556289647108097, name=分水岭, password=123456, age=18, tel=8888)]

问题:page.getRecords()执行的查询结果相当于是select all,分页相当于是select * from user ??? limit 1,2
在SQL语句的基础上添加一些功能,拦截并增强
解决办法:
1)配置MyBatisPlus的拦截器,在拦截器中开启分页拦截器。
image
2)在类上添加Configuration注解。

package com.fanxl.mybatisplus.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mpInterceptor(){
        // 1、定义mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        // 2、添加具体的拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }
}

2.2 时间更新

2.2.1 数据库级别

1、在数据库中添加时间和修改时间字段

  • 添加时间设置默认值:CURRENT_TIMESTAMP不勾选根据时间戳更新;
  • 修改时间设置默认值:CURRENT_TIMESTAMP,并勾选根据当前时间戳更新。

image

image

2、在实体类中添加字段
private Date createTime;
private Date updateTime;
3、编写测试类
此时可以看到除了插入的记录,其余数据的创建时间和更新时间都为空。
image
解决办法:在创建表时设置该字段不为空
image
image

2.2.2 代码级别

1、删除数据库中字段的默认值和更新操作。
image
2、实体类字段属性上增加注解@TableField
fill:字段自动填充策略

public enum FieldFill {
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入时填充字段
     */
    INSERT,
    /**
     * 更新时填充字段
     */
    UPDATE,
    /**
     * 插入和更新时填充字段
     */
    INSERT_UPDATE
}
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

3、编写处理器处理注解。在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口。

package com.fanxl.mybatisplus.controller;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Date;
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
        //插入时的填充策略
        @Override
        public void insertFill(MetaObject metaObject) {
            log.info("start insert fill......");
            this.setFieldValByName("createTime",new Date(),metaObject);
            //MetaObject[反射对象类]是Mybatis的工具类,通过MetaObject获取和设置对象的属性值。
            this.setFieldValByName("updateTime",new Date(),metaObject);
        }

        //更新时的填充策略
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("start update fill.....");
            this.setFieldValByName("updateTime",new Date(),metaObject);
        }
    }

插入数据后的结果:
image
更新数据后的结果:
image

3 条件查询

  1. 条件查询——设置查询条件
  • 格式一:常规格式
//查询年龄小于18岁的用户
QueryWrapper qw = new QueryWrapper();
qw.lt("age",18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
  • 格式二:链式编程格式
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//查找10~30岁之间的用户
lqw.lt(User::getAge,30).gt(User::getAge,10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
  • 格式三:lambda格式按条件查询(查询)
QueryWrapper<User> qw = new QueryWrapper();
qw.lambda().lt(User::getAge,18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
  • 格式四:lambda格式(推荐)
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge,18);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
  1. 条件查询——组合查询条件
  • 并且(and)
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//查找10~30岁之间的用户
lqw.lt(User::getAge,30).gt(User::getAge,10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
  • 或者(or)
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//查找小于10岁或者大于30岁的用户
lqw.lt(User::getAge,30).or().gt(User::getAge,10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
  1. 条件查询——null值处理
  • User实体类
@Data
public class User {
    private  Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;

}
  • 定义一个UserQuery继承User
@Data
public class UserQuery extends User {

    private Integer age2;    //描述年龄上限
}
  • 编写测试类
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
// 先判断第一个参数是否为true,如果为true连接当前条件
lqw.lt(null!=uq.getAge2(),User::getAge,uq.getAge2());
lqw.gt(null!=uq.getAge(),User::getAge,uq.getAge());
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);

4 查询投影

设置查询出来显示的字段

  • 查询结果包含模型类中部分属性

【lambda格式select里面只能列当前实体类内部的属性名】
错误写法:

LambdaQueryWrapper<Student> student = new LambdaQueryWrapper<>();
student.select("score");
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.select(User::getId,User::getName);   //只适用于lambda的格式
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
  • 查询结果包含模型类中未定义的属性
    select里面的字段名为数据库表中的字段名
QueryWrapper<User> lqw = new QueryWrapper<User>();
lqw.select("count(*) as count,age");    //统计个数
lqw.groupBy("age");
List<Map<String, Object>> userList = userDao.selectMaps(lqw);
System.out.println(userList);

5 查询条件设置

5.1 等于

//条件查询
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//等于 =
lqw.eq(User::getName,"Jerry").eq(User::getPassword,"jerry");
User loginUser = userDao.selectOne(lqw); //专门查询一个对象
System.out.println(loginUser);

5.2 范围查询

LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//范围查询 lt(<)、le(<=)、gt(>)、ge(>=)、eq(=)、between
lqw.between(User::getAge,10,30);   //第一个字段为小值,第2个字段为大值
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);

5.3 模糊匹配

LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//模糊匹配
// lqw.like(User::getName,"J");
// lqw.likeRight(User::getName,"J");
lqw.likeLeft(User::getName,"J");
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);

6 映射匹配兼容性

6.1 表字段与编码属性设计不同步

问题一:表字段与编码属性设计不同步

image

解决办法:使用@TableField属性注解,value设置数据库表字段名称。

image

6.2 编码中添加了数据库中未定义的属性

问题二:编码中添加了数据库中未定义的属性

image

解决办法:使用@TableField属性注解,设置exist字段属性为false。默认为true,此属性无法与value合并使用。

image

6.3 采用默认查询开放了更多的字段查看权限

问题三:使用默认查询时可查询密码字段不安全。

image

解决办法:使用@TableField属性注解,设置select属性是否参与查询,此属性与select()映射配置不冲突。
image

6.4 表名与编码开发设计不同步

问题四:表名与编码开发设计不同步

image

解决办法:使用TableName注解,value设置数据库表名称。

image

7 id生成策略控制

7.1 AUTO:使用数据库id自增策略控制id生成

1)设置主键类型为auto

image
2)将数据表的类型设为自动增长
image

image

3)编写测试类测试

image

7.2 INPUT:用户手工输入id

1)设置主键生成策略为input
image
2)取消自动递增
image
3)指定ID值
image
image

7.3 ASSIGN_ID:雪花算法生成id(可兼容数值型与字符串型)

雪花算法:生成1个由64位的二进制组成的串,最终是一个Long值。
image

8 全局配置

8.1 全局配置主键生成策略

在applicaiton.yml文件中添加以下属性,同时取消实体类中的配置 @TableId(type = IdType.ASSIGN_ID)

dbConfig:
      idType: assign_id
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  globalConfig:
    banner: false   # 去掉mybatis-plus的banner
    dbConfig:
      idType: assign_id

8.2 全局配置数据库表名

在配置文件中添加tablePrefix: tbl_字段。

# mp日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  globalConfig:
    banner: false   # 去掉mybatis-plus的banner
    dbConfig:
      idType: assign_id
      tablePrefix: tbl_

9 多数据删除与查询

9.1 deleteBatchIds

    @Test
    void testDelete(){
        List<Long> list = new ArrayList<Long>();
        list.add(1652931780614860802L);
        list.add(1652934902670188545L);
        list.add(1652936421721337858L);
        userDao.deleteBatchIds(list);
    }

9.2 selectBatchIds

	List<Long> list = new ArrayList<Long>();
	list.add(1L);
	list.add(2L);
	list.add(3L);
	userDao.selectBatchIds(list);

10 逻辑删除

  • 删除操作业务问题:业务数据从数据库中丢弃
  • 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中。

10.1 操作步骤

1、在数据库中添加数据状态字段,并设置字段默认值为0,代表未删除。
image
2、在实体类中添加字段数据状态字段,并添加@TableLogic注解,0代表未删除,1代表删除了,

@Data
//@TableName("tbl_user")
public class User {
//    @TableId(type = IdType.ASSIGN_ID)
    private  Long id;
    private String name;
    @TableField(value="pwd",select = false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist = false)
    private Integer online;
    //逻辑删除字段,标记当前记录是否被删除
    @TableLogic(value = "0" ,delval = "1")
    private Integer deleted;
}

3、编写测试类测试,执行删除操作,可看到执行的数据库语句为update,数据库字段变为1。
image

image
4、测试全局查询操作,过滤了已经被删除的数据。如果想看全部数据,需要自己写SQL语句。
image

10.2 全局配置逻辑删除

# mp日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  globalConfig:
    banner: false   # 去掉mybatis-plus的banner
    dbConfig:
      idType: assign_id
      tablePrefix: tbl_
      logicDeleteField: deleted  #逻辑删除字段
      logicDeleteValue: 1	     #删除值
      logicNotDeleteValue: 0     #未删除值

11 乐观锁

解决并发问题。
步骤:
1、在数据表中添加字段version,并设置默认值为1
image
2、实体类中添加字段
image
3、添加乐观锁拦截器实现锁机制对应的动态SQL语句拼装
image
测试:

1、将数据更新时,需要指定Version,否则version不会更新。

User user = new User();
user.setId(3L);
user.setVersion(1);
user.setName("Jock66");
userDao.updateById(user);

2、使用乐观锁机制在修改前必须先获取到对应数据的verison访客正常进行。

//注意事项:在进行修改时,version数据必须要收集!!!!!!!!
//先通过要修改数据的id将当前数据查询出来
User user1 = userDao.selectById(3L);
//将要修改的属性逐一设置进去
user1.setName("JocK88");
userDao.updateById(user1);

3、模拟多用户操作
多个用户修改时,

User user1 = userDao.selectById(3L);   //version = 3
User user2 = userDao.selectById(3L);   //verison = 3
//将要修改的属性逐一设置进去
user2.setName("JocK77");
userDao.updateById(user2);
user1.setName("JocK99");             //执行失败
userDao.updateById(user1);
  • 执行修改前先执行查询语句:
    SELECT id,name,age,tel,deleted,version FROM tbl_user WHERE id=? AND deleted=0
  • 执行修改时使用version字段作为乐观锁检查依据:
    SELECT id,name,age,tel,deleted,version FROM tbl_user WHERE id=? AND deleted=0

12 代码生成器

  • 模版:MyBatisPlus提供
  • 数据库相关配置:读取数据库获取信息
  • 开发者自定义配置:手工配置

12.1 在pom文件中添加依赖

<!--代码生成器-->
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-generator</artifactId>
	<version>3.4.1</version>
</dependency>
<!--velocity模板引擎-->
<dependency>
	<groupId>org.apache.velocity</groupId>
	<artifactId>velocity-engine-core</artifactId>
	<version>2.3</version>
</dependency>

12.2 核心代码

  • 创建代码生成器的对象 AutoGenerator
  • 设置数据源 DataSourceConfig
  • 设置全局配置 GlobalConfig
  • 设置包名相关配置 PackageConfig
  • 策略设置 StrategyConfig
  • 执行代码生成器 autoGenerator.execute();
package com.fanxl.generator;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
public class CodeGenerator {
    public static void main(String[] args) {
        //1、创建代码生成器的对象
        AutoGenerator autoGenerator = new AutoGenerator();

        //2、设置数据源
        DataSourceConfig dataSource = new DataSourceConfig();
        dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("qwe321456");
        autoGenerator.setDataSource(dataSource);


        //3、设置全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        // 设置代码生成位置
        globalConfig.setOutputDir("E:/IdeaProjects/练习项目文件夹/generator/src/main/java");
        // 设置生成完毕后是否打开生成代码所在的目录,默认为false
        globalConfig.setOpen(false);
        // 设置作者
        globalConfig.setAuthor("分水岭");
        //设置是否覆盖原始生成的文件
        globalConfig.setFileOverride(true);
        //设置数据层接口名,%s为占位符,指代模块名称,即数据库表名
        globalConfig.setMapperName("%sDao");
        //设置Id生成策略
        globalConfig.setIdType(IdType.ASSIGN_ID);
        autoGenerator.setGlobalConfig(globalConfig);

        //4、设置包名相关配置
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setEntity("domain"); //设置实体类包名
        packageConfig.setMapper("dao");    //设置数据层包名
        packageConfig.setParent("com.fanxl"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径。
        autoGenerator.setPackageInfo(packageConfig);

        //5、策略设置
        StrategyConfig strategyConfig = new StrategyConfig();
        //设置当前参与生成的表名,参数为可变参数
        strategyConfig.setInclude("tbl_user");
        //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名  User = tbl_user - tbl_
        strategyConfig.setTablePrefix("tbl_");
        //设置是否启用rest风格  启用的话,controller层的注解为@RestController,否则为Controller
        strategyConfig.setRestControllerStyle(true);
        //设置乐观锁字段名
        strategyConfig.setVersionFieldName("version");
        //设置逻辑删除字段名
        strategyConfig.setLogicDeleteFieldName("deleted");
        //设置是否启用lombok
        strategyConfig.setEntityLombokModel(true);

        autoGenerator.setStrategy(strategyConfig);

        //6、执行代码生成器
        autoGenerator.execute();
    }
}

posted @ 2023-05-02 14:11  翻山越玲  阅读(163)  评论(0)    收藏  举报