MybatisPlus学习笔记

  • Post author:
  • Post category:其他




提示:当前使用的版本为mybatis-plus3.5.1



一、简介


MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。简化CRUD操作。



二、快速入门

使用第三方组件步骤:

  1. 导入对应依赖
  2. 研究依赖如何配置
  3. 代码如何编写
  4. 提高扩展技术能力

步骤基于 spring boot项目

  1. 创建数据库和表
CREATE TABLE user
(
	id BIGINT(20) NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT(11) NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);
# 添加数据
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
  1. 在spring boot工程中导入Maven依赖
<dependency>
	<groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>
  1. 连接数据库
spring:
    datasource:
      # mysql 8 驱动不同com.mysql.cj.jdbc.Driver 需要增加时区的配置 serverTimezone=GMT%2B8
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/database?useSSL=false&useUnicode=true&characterEncoding=utf-8
      username: root
      password: admin
  1. 创建实体类User.java(必须与表名保持一致)
@Data
@Builder(toBuilder = true)
@AllArgsConstructor
@NoArgsConstructor
public class User {
	private Long id;
    private String name;
    private Integer age;
    private String email;
}
  1. 创建UserMapper接口并继承通用Mapper(BaseMapper)泛型为User型
public interface UserMapper extends BaseMapper<User> {
}
  1. 在springboot启动类上添加@MapperScan注解
@SpringBootApplication
@MapperScan("com.example.mybatisplus.mapper") //mapper接口所在的包路径
public class MybatisPlusApplication {
	public static void main(String[] args) {
		SpringApplication.run(MybatisPlusApplication.class, args);
	}
}
  1. 测试
@SpringBootTest
class MybatisPlusApplicationTests {
	@Autowired
    private UserMapper userMapper;
    @Test
    void contextLoads() {
        //查询全部数据 参数为wrapper条件构造器 暂且为null
        List<User> users = userMapper.selectList(null);
        for (User user : users) {
            System.out.println(user);
        }
    }
}
运行输出
```
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)
```



三、配置日志


配置日志便于查看执行的sql语句

在spring boot配置文件中配置日志实现

# 第一种方式
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 第二种方式
logging:
  level:
  # 指定mapper接口所在的包
    com.example.mybatisplus.mapper: debug

测试输出结果

第一种方式
JDBC Connection [HikariProxyConnection@1079733621 wrapping com.mysql.cj.jdbc.ConnectionImpl@682abca7] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email FROM user //执行的SQL
==> Parameters: //SQL的参数
//查询的结果
<==    Columns: id, name, age, email
<==        Row: 1, Jone, 18, test1@baomidou.com
<==        Row: 2, Jack, 20, test2@baomidou.com
<==        Row: 3, Tom, 28, test3@baomidou.com
<==        Row: 4, Sandy, 21, test4@baomidou.com
<==        Row: 5, Billie, 24, test5@baomidou.com
<==      Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66f28a1f]
第二种方式
==>  Preparing: SELECT id,name,age,email FROM user
==> Parameters: 
<==      Total: 5



四、CRUD和扩展



1.insert

测试单表插入

@Test
public void testInsert() {
    User user = User.builder()
        .name("zhangsan")
        .age(22)
        .email("123@qwe").build();

    //受影响行数
    int result = userMapper.insert(user);
    System.out.println(result);
    //发现id会自动填充
    System.out.println(user);

    List<User> users = userMapper.selectList(null);
    for (User u : users) {
        System.out.println(u);
    }
}

运行结果

==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
==> Parameters: 106717185(Integer), zhangsan(String), 22(Integer), 123@qwe(String)
<==    Updates: 1
1
User(id=106717185, name=zhangsan, age=22, email=123@qwe)
...

这里MP使用了雪花算法,参考:


雪花算法(snowflake):snowflake算法是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。雪花算法支持的TPS可以达到419万左右(2^22*1000)。可以几乎保证全球唯一。


分布式系统唯一ID生成:

分布式系统唯一ID生成方案汇总

主键自增策略,默认是

IdType.NONE

配置主键自增

  1. 在实体类主键属性上添加@TableId(type=IdType.AUTO)注解
public class User {
	@TableId(type = IdType.AUTO)
   	private Integer id;
}
  1. 把数据表注解字段设置为自增(auto_increment)
  2. 测试插入运行结果
User(id=106717186, name=zhangsan, age=22, email=123@qwe)

IdType枚举属性

public enum IdType {
	AUTO(0),	//ID自增
    NONE(1),	//不设置主键自增
    INPUT(2),	//手动输入
    ASSIGN_ID(3),
    ASSIGN_UUID(4);
}



2.update

单表更新

@Test
public void testUpdate(){
    User user = User.builder()
        .id(106717186)
        .name("lisi123")
        //.age(23)
        .email("321@qwe").build();
    //更新操作会自动拼接sql
    int result = userMapper.updateById(user);
    System.out.println(result);
}

运行结果

==>  Preparing: UPDATE user SET name=?, email=? WHERE id=?
==> Parameters: lisi123(String), 321@qwe(String), 106717186(Integer)
<==    Updates: 1
1



3.乐观锁插件

乐观锁:顾名思义十分乐观,它总是认为不会出现问题,无论干什么都不会上锁!如果出现问题就再次更新值测试。

悲观锁:顾名思义十分乐观,它总是认为会出现问题,无论干什么都会上锁!再去操作。

乐观锁实现方式:

  • 取出记录时,获取当前 version
  • 更新时,带上这个 version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果 version 不对,就更新失败

测试MP的乐观锁插件

  1. 在数据表中添加version字段

    在这里插入图片描述
  2. 同步实体类
public class User {
	@TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    private Integer age;
    private String email;
    @Version	//乐观锁Version注解
    private Integer version;
    @TableField(fill = FieldFill.INSERT)
    private Date createDate;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date modifyDate;
}
  1. 注册组件,创建MP配置类,向MybatisPlusInterceptor拦截器中添加OptimisticLockerInnerInterceptor拦截器
@Configuration
@MapperScan("com.example.mybatisplus.mapper")
public class MybatisPlusConfig {
	@Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}
  1. 测试乐观锁
@Test
public void testOptimismLock(){
	//模拟线程A
    User user1 = userMapper.selectById(1);
    user1.setAge(22);
    //模拟线程B 执行了插队操作
    User user2 = userMapper.selectById(1);
    user2.setAge(18);
    //线程B抢先更新
    userMapper.updateById(user2);
    //由于线程B已经更新完成了,线程A更新失败
    userMapper.updateById(user1);
}
  1. 运行结果
==>  Preparing: SELECT id,name,age,email,version,create_date,modify_date FROM user WHERE id=?
==> Parameters: 1(Integer)
<==    Columns: id, name, age, email, version, create_date, modify_date
<==        Row: 1, Jone, 18, test1@baomidou.com, 1, 2022-02-07 17:39:39, 2022-02-07 17:39:40
<==      Total: 1

==>  Preparing: SELECT id,name,age,email,version,create_date,modify_date FROM user WHERE id=?
==> Parameters: 1(Integer)
<==    Columns: id, name, age, email, version, create_date, modify_date
<==        Row: 1, Jone, 18, test1@baomidou.com, 1, 2022-02-07 17:39:39, 2022-02-07 17:39:40
<==      Total: 1

==>  Preparing: UPDATE user SET name=?, age=?, email=?, version=?, create_date=?, modify_date=? WHERE id=? AND version=?
==> Parameters: Jone(String), 23(Integer), test1@baomidou.com(String), 2(Integer), 2022-02-07 17:39:39.0(Timestamp), 2022-02-07 19:21:30.42(Timestamp), 1(Integer), 1(Integer)
<==    Updates: 1

==>  Preparing: UPDATE user SET name=?, age=?, email=?, version=?, create_date=?, modify_date=? WHERE id=? AND version=?
==> Parameters: Jone(String), 22(Integer), test1@baomidou.com(String), 2(Integer), 2022-02-07 17:39:39.0(Timestamp), 2022-02-07 19:21:30.441(Timestamp), 1(Integer), 1(Integer)
<==    Updates: 0



4.select

@Test
public void testSelect() {
    //根据ID查询
    User user = userMapper.selectById(1);
    System.out.println(user);

    //根据多个ID查询
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3, 4, 5));
    System.out.println(users.toString());

    //自定义条件查询之一使用Map操作
    Map<String, Object> map = new HashMap<>();
    map.put("age",18);
    map.put("name","Jone");
    List<User> userList = userMapper.selectByMap(map);
    System.out.println(userList);
}



5.分页查询

配置步骤

  1. 在MP配置类中配置分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
	MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    //添加乐观锁插件
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    //添加分页插件
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    return interceptor;
}
  1. 使用selectPage()测试分页
@Test
public void testPageSelect() {
	//Page构造器参数(currentPage,PageSize)
    Page<User> page = new Page<>(3, 5);

    userMapper.selectPage(page, null);
    //获取分页记录
    List<User> users = page.getRecords();
    for (User user : users) {
        System.out.println(user);
    }
    //获取当前页
    System.out.println(page.getCurrent());
    //获取总页数
    System.out.println(page.getPages());
    //获取当前页记录数
    System.out.println(page.getSize());
    //获取总记录数
    System.out.println(page.getTotal());
}



6.delete

@Test
public void testDelete() {
    int result = 0;
    //根据ID删除
    result = userMapper.deleteById(106717187);
    System.out.println(result);
    //根据多个ID删除
    result = userMapper.deleteBatchIds(Arrays.asList(106717188, 106717189));
    System.out.println(result);
    //自定义删除条件之一使用Map操作
    Map<String,Object> map = new HashMap<>();
    map.put("name","Tue Feb 08 10:25:31 CST 2022");
    result = userMapper.deleteByMap(map);
    System.out.println(result);
}



7.逻辑删除

逻辑删除:逻辑删除本质是修改,数据库中没有被删除,而是使用另一个变量(字段),让它失效

物理删除:从数据库中永久删除


作用:管理员可以查看被删除的记录,防止数据丢失,类似于回收站

实现步骤

  1. 在数据表中添加deleted字段

    在这里插入图片描述
  2. 同步实体类
@TableField(fill = FieldFill.INSERT)
@TableLogic //添加逻辑删除注解
private Integer deleted;
  1. 在springboot配置文件中添加配置
mybatis-plus:
  global-config:
    db-config:
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
  1. 测试
@Test
public void testLogicDelete() {
	//删除
    int result = userMapper.deleteById(106717193);
    System.out.println(result);
   
    //查询
    User user = userMapper.selectById(106717193);
    System.out.println(user);
}
输出日志
```
//更新deleted字段值,并添加deleted=0条件
==>  Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0
==> Parameters: 106717193(Integer)
<==    Updates: 1

//查询语句也添加deleted=0条件
==>  Preparing: SELECT id,name,age,email,deleted,version,create_date,modify_date FROM user WHERE id=? AND deleted=0
==> Parameters: 106717193(Integer)
<==      Total: 0
```



8.执行SQL分析打印

该功能依赖

p6spy

组件

  1. p6spy依赖Maven
<dependency>
	<groupId>p6spy</groupId>
	<artifactId>p6spy</artifactId>
	<version>最新版本</version>
</dependency>
  1. 在springboot配置文件中配置
spring:
  datasource:
    # driver-class-name 改为com.p6spy.engine.spy.P6SpyDriver
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    # url前缀为jdbc:p6spy:
    url: jdbc:p6spy:mysql://localhost:3306/mybatis_plus
    ...
  1. 创建p6spy配置文件spy.properties
#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
   #日志输出到控制台
   appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
   # 使用日志系统记录 sql
   #appender=com.p6spy.engine.spy.appender.Slf4JLogger
   # 设置 p6spy driver 代理
   deregisterdrivers=true
   # 取消JDBC URL前缀
   useprefix=true
   # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
   excludecategories=info,debug,result,commit,resultset
   # 日期格式
   dateformat=yyyy-MM-dd HH:mm:ss
   # 实际驱动可多个
   #driverlist=org.h2.Driver
   # 是否开启慢SQL记录
   outagedetection=true
   # 慢SQL记录标准 2 秒
   outagedetectioninterval=1
  1. 运行测试输出结果

    在这里插入图片描述



五、自动填充字段


例如几乎所有的数据表都要有create_date、modify_date字段,有些时候不能直接在数据表上更改

方式一:数据库级别

  1. 在表中新增字段create_date、modify_date

    在这里插入图片描述

  2. 同步实体类

public class User {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    private Integer age;
    private String email;
    private Date createDate;
    private Date modifyDate;
   }
  1. 更新测试

    在这里插入图片描述

方式二:代码级别

  1. 删除数据表字段的默认值
  2. 在实体类属性上添加注解@TableField注解
@TableField(fill = FieldFill.INSERT)
   private Date createDate;

   @TableField(fill = FieldFill.INSERT_UPDATE)
   private Date modifyDate;
  1. 创建数据填充处理器实现MetaObjectHandler接口
@Slf4j
   @Component
   public class DataFillHandler implements MetaObjectHandler {
	//插入时填充
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill....");
        this.setFieldValByName("createDate",new Date(),metaObject);
        this.setFieldValByName("modifyDate",new Date(),metaObject);
    }

    //更新时填充
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill....");
        this.setFieldValByName("modifyDate",new Date(),metaObject);
    }
   }
  1. 测试插入

  2. 测试更新



六、条件构造器


多用于复杂SQL



AbstractWrapper

有两个子类

1.QueryWrapper

示例:

@Test
public void test1() {
    QueryWrapper<User> wrapper = new QueryWrapper();
    
    wrapper.isNotNull("name") //name字段不等于null
        .isNotNull("email")
        .ge("age", 20);	//age字段大于等于20
    userMapper.selectList(wrapper);
}

@Test
public void test2() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("name", "Tom");	//name字段等于‘Tom’的记录
    User user = userMapper.selectOne(wrapper);
    System.out.println(user);
}
@Test
public void test3() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.between("age", 20, 25);	//age字段在20~25区间的记录
    Long count = userMapper.selectCount(wrapper);
    System.out.println(count);
}

@Test
public void test4() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.notLike("name", "a")	//模糊查询不包含‘a’的记录
        .likeRight("email", "3");	//模糊查询
    userMapper.selectList(wrapper);
}
@Test
public void test5() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.inSql("id", "select id from user where age > 22");	//子查询的id与父查询id匹配
    userMapper.selectList(wrapper);
}

@Test
public void test6() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.orderByDesc("id");	//将ID降序排序
    userMapper.selectList(wrapper);
}

2.UpdateWrapper

示例:

@Test
public void test7(){
    UpdateWrapper<User> wrapper = new UpdateWrapper<>();
    //批量更新 ID为2,3 的记录字段name为‘张三
    wrapper.set("name","张三")
        .in("id", Arrays.asList(2,3));
    userMapper.update(new User(),wrapper);
}



七、代码自动生成器


具体参考:

Mybatis-plus最新代码生成器(3.5.1+)的使用



示例:

public class GeneratorCode {
	private static String url = "jdbc:mysql://localhost:3306/club?useSSL=false&useUnicode=true&characterEncoding=utf-8";
	private static String username = "root";
	private static String password = "admin";
	public static void main(String[] args) {
		List<String> tables = new ArrayList<>();
        tables.add("blog");
        tables.add("commodity");
        tables.add("commodity_type");
        tables.add("working");
        tables.add("user");
        FastAutoGenerator.create(url,username,password)
                .globalConfig(builder -> {
                    builder.author("mr.ck")               //作者
                            .outputDir("E:\\code\\JavaIDEA\\自学\\mybatis-plus\\mybatis-plus-generator\\src\\main\\java")    //输出路径(写到java目录)
                            .enableSwagger()           //开启swagger
                            .commentDate("yyyy-MM-dd")
                            .fileOverride();            //开启覆盖之前生成的文件

                })
                .packageConfig(builder -> {
                    builder.parent("com.cheng.mybatisplusgenerator")
                            .moduleName("generator")
                            .entity("entity")
                            .service("service")
                            .serviceImpl("serviceImpl")
                            .controller("controller")
                            .mapper("mapper")
                            .xml("mapper")
                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml,"E:\\code\\JavaIDEA\\自学\\mybatis-plus\\mybatis-plus-generator\\src\\main\\resources\\mapper"));
                })
                .strategyConfig(builder -> {
                    builder.addInclude(tables)
                            .addTablePrefix("t_")
                            .serviceBuilder()
                            .formatServiceFileName("%sService")
                            .formatServiceImplFileName("%sServiceImpl")
                            .entityBuilder()
                            .enableLombok()
                            .logicDeleteColumnName("deleted")
                            .enableTableFieldAnnotation()
                            .controllerBuilder()
                            .formatFileName("%sController")
                            .enableRestStyle()
                            .mapperBuilder()
                            .enableBaseResultMap()  //生成通用的resultMap
                            .superClass(BaseMapper.class)
                            .formatMapperFileName("%sMapper")
                            .enableMapperAnnotation()
                            .formatXmlFileName("%sMapper");
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
	}
}

在这里插入图片描述



版权声明:本文为weixin_45835256原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。