MybatisPlus 蓝鸟魂斗罗

引入MyBatis-Plus不影响Mybatis的使用

快速入门
'''
定义Mapper
自定义的Mapper继承MybatisPlus提供的BaseMapper接口:

public interface UserMapper extends BaseMapper<User>{

}

~~选择性浏览↓ ~~
:SpringBoot3.2 与 MyBatisPlus 踩坑小记

一、现象

  • 启动测试即报:
Failed to load ApplicationContext
Caused by: IllegalArgumentException: 
Invalid value type for attribute 'factoryBeanObjectType': >java.lang.String

二、根因
SpringBoot 3.2 对 FactoryBean 类型校验变严,而 MyBatis-Plus 早期版本把 >factoryBeanObjectType 设成字符串,导致不兼容。

三、解决步骤

  1. 升级 MyBatis-Plus3.5.8+(或最新 3.5.9
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.9</version>
</dependency>
  1. 覆盖旧版 mybatis-spring(关键!)
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>3.0.3</version>
</dependency>

刷新 Maven
IDEA 右上角 ↻ → 重新下载依赖 → 运行测试 → 通过。

四、心得

  • 报错信息看似吓人,实际是依赖版本不匹配。
  • 升级时别只看starter,传递依赖也要一起升。
  • 控制台 WARNING(agent、sharing)与业务无关,可忽略或加 JVM 参数隐藏。

升级两坐标,问题全解决;SpringBoot3 与 MP 继续愉快玩耍!


快速入门

1.在IDEA中配置好 Spring项目
包括

  • java JDK版本
  • Maven 构建

依赖:

  • lombok
  • Spring web
  • mysql driver
    新建项目时,注意
    image
    软件包的位置就是默认 application 类生成的位置

2.配置好配置文件
pom.xml:手动在小毛驴中添加Mybatis-plus的依赖

引l入MybatisPlus的起步依赖
MyBatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配效果。
因此我们可以用MybatisPlus的starter代替Mybatis的starter:

<!--mybatisPlus-->
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

(可能要改其org.mybatis:mybatis-spring:2.1.2 (omitted for conflict with 3.0.3),因测试时遇到Spring和Mybatis-plus版本不和谐的问题)

application.yml:配置mysql

在源文件下
创建 实体,Mapper这两个关键包,相当于jvav实体与mysql中user表的映射
在application类中导入
@MapperScan("com.lantu.mapper") // 显式扫描 Mapper 接口,解决自动配置问题
(或者在UserMapper上加入@Mapper注解)

4.HelloWorld

package com.lantu;


import com.lantu.entity.User;
import com.lantu.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class UserMapperTest {
    @Resource
    //@Autowired也可以
    private UserMapper userMapper;

    @Test
    //查(east版)
    public void testGetAll(){
        //userMapper.insert() 传入一个userMapper对象
        //userMapper.selectById
        //userMapper.update()
        //userMapper.deleteByod()
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);//遍历
		或者
		    public void testSelect() {
        System.out.println(("----- selectAll method test ------"));
        List<User> userList = userMapper.selectList(null);
        Assert.isTrue(5 == userList.size(), "");
        userList.forEach(System.out::println);
    }
	//控制台输出:

//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)
		
    }
    //增
    @Test
    public void testInsert(){
        User user = new User(null,"xxx",18,"xxx@qq.com");
//        user.setId();//这样就不用设置值了
        userMapper.insert(user);//插入语句通过Mapper加入到mysql中

    }
   //改(这里功能是错的)
   public void testUpdate(){
        userMapper.updateById(user);
   }

}

注意:
UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装器 Wrapper,所以不填写就是无任何条件

注解

1.@TableName
当表名字改为 user_info
实体类 还是 User就没有对上
开发过程中确实会出现类似情况
这时候给一个注解(user.java实体类文件下)显示指明

@TableName("user_info)")
public class User implements Serializable{
...
}

2.@TbaleField()
a. 当表中列的名字想区别于(user.java实体类文件下)显示指明时
b. 当代码中想要新增一个属性同时不需要数据库表中有时
c. 当你不想查询某一个字段时

3.@TableId()
主键注解

@TableName("user_info")
public class User implements Serializable {
	@TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
	//添加在这里a. c.
    @TableField(value = "email",select = false)
    private String mail;
	
	//假设这里还有一个数据库表里没有的状态(也不需要有)
	//b.
	@TableField(exist = false)
	private String status;
	
}
  • 也就是说这前两个注解都可以用来指定映射关系(括号内必须为数据库的内容)

对于注解3.@TableId
如果出现
1.
Caused by: java.sql.SQLException: Field 'id' doesn't have a default value
则说明数据库本身 id 字段没有设置 auto
解决方法:
ALTER TABLE user MODIFY id BIGINT (PRIMARY KEY) AUTO_INCREMENT;
(如果id字段已经是主键则去掉括号的内容)

describe user;
则有
![image]>(https://img2024.cnblogs.com/blog/3725840/202511/3725840->20251129130027630-19408117.png)

Navicat中设置主键自动递增初始值时,保存自动回弹到默认值的问题
其自身的bug,其实已经生效,不过要设置正确的数字才生效

4.@TableLogic
表字段逻辑处理注解(逻辑删除)

适合在数据量小时使用,情景:用户把购物消费记录删除,真的删除了吗?
在后续查询操作时可以降低代码工作量

属性| 类型 |描述
value|String|逻辑未删除值
delval|String|逻辑删除值

全局
1.yml

mybatis-plus:
  global-config:
    db-config:
	  logic-delete-field: deleted #全局逻辑删除的实体字段名,配置后可不用@TableLogic注解
	  logic-delete-value: 1 #逻辑已删除
	  logic-not-delete-value: 0 #逻辑未删除
	  #以上all配置,本质也都是一个映射关系->

2.entity(局部,可能会导致整个项目不统一)
字段一致时使用

@TableLogic
private String deleted;

本质:
(这里事先采用了yml全局设置)

    @Test
    //查(east版)
    public void testGetAll() {
        //userMapper.insert() 传入一个userMapper对象
        //userMapper.selectById
        //userMapper.update()
        //userMapper.deleteByod()
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);//遍历
    }

    @Test
    //删除
    public void testDelete() {
        userMapper.deleteById(9);
    }

执行测试 查询代码时本质上是:
(这里 WHERE deleted = 0 就是自动加的)

==>  Preparing: SELECT id,name,age,email AS mail,deleted FROM user WHERE deleted=0

执行测试 删除代码时本质上是:
(同理)

==>  Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0

这也是mp一种对逻辑删除的一个简化操作

5.@Version
乐观锁注解,当要更新一条记录的时候, 希望这条记录没有被别人更新

区别于 悲观锁, 查询受不受限这一块
是一种 进行操作时 进行锁表, 让其他后来的人无法获取正在进行的数据(但在并发时效率低,后面的人要排队)

乐观锁实现方式:

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

情景:
账户余额 balance = 100 ,同时两个人存钱
获取账户余额: 100
A: set balance = 100 + 20,version=2 where version = 1
B: set balance = 100 + 30,version=2 where version = 1

MP 则是自动回加上这个 version 版本号

使用步骤:
0. 为表新增字段: version,balance
User 实体字段里相应的加 属性

	@Version
    private Integer version;
    private Integer balance;
  1. config
package com.lantu.config;


import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MpConfig {
    //拦截器/插件可以在官网中查到,不用去记
//直接就配好了
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
	}
}

分页查询

MyBatis-Plus 的分页插件 PaginationInnerInterceptor 提供了强大的分页功能,支持多种数据库,使得分页查询变得简单高效。

提示

于 v3.5.9 起,PaginationInnerInterceptor 已分离出来。如需使用,则需单独引入 mybatis-plus-jsqlparser 依赖 , 具体请查看 MP官网安装 一章。

关键修正:artifactId正确,但版本号必须与MyBatis-Plus主版本一致(如3.5.9


com.baomidou
mybatis-plus-jsqlparser
3.5.9(这里不写也不行)


于是就有了小插曲
发现这个
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
怎么也导入不了
于是只能
同时清理Maven本地仓库中的相关依赖和IDE的缓存

  1. mvn clean dependency:purge-local-repository -U
  2. Invalidate Caches
  3. Reload Project
  • 总结:构思Maven,不如pip一根
  1. 配置分页拦截器
package com.lantu.config;
...
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
  1. 查询前设置分页参数
    public void testGetAll2(){
        Page<User> page = new Page<>(1,5);//page对象
        Page<User> userPage = userMapper.selectPage(page, null);
        System.out.println("总数: "+userPage.getTotal());
        //获得具体明细数据
        page.getRecords().forEach(System.out::println);
    }

结果

==>  Preparing: SELECT id,name,age,email AS mail,deleted,version,balance FROM user WHERE deleted=0 LIMIT ?

条件构造器

当条件语句过于复杂时还是写在 xml 里好


https://baomidou.com/guides/wrapper/
详细指令参见

  1. 常规使用
    public void testSelect() {
       // userMapper.selectList(null);//queryWrapper这个封装器,又叫条件构造器
        // 等到你自定义(设置查询语句)时就 不是写 null了
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.gt("age",20);//大于 20 岁
        wrapper.likeRight("name","赵");//查姓赵的
        wrapper.orderByDesc("id");// id降序(从大到下
        //不建议在任何场景下使用前后模糊,因其效率非常低 (左不用,右用索引)

        //这里传入的wrapper就是类似于 写好打包好的 sql查询语句
        List<User> users = userMapper.selectList(wrapper);//得到一个结果集
        users.forEach(System.out::println);
    }
  1. 推荐使用 LambdaQueryWrapper
    public void testSelect() {
//直接避免万一你打错字没有及时发现的情况,使用 User:: 直接一步到位
//eg. "age"万一写成"agr"时
        LambdaQueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.gt(User::getAge,20);//大于 20 岁
        wrapper.likeRight(User::getName,"赵");//查姓赵的
        wrapper.orderByDesc(User::getId);// id降序(从大到小
        //不建议在任何场景下使用前后模糊,因其效率非常低 (左不用,右用索引)

        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

代码生成器(new)

这个没必要打在main里面,放test里面即可

1.快速生成
在 CodeGenerator 中的 main 方法中直接添加生成器代码,并进行相关配置,然后直接运行即可生成代码。
image
a.先加入必要依赖(Maven BOM管理版本[可选])

<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-generator</artifactId>
		</dependency>

		<dependency>
			<groupId>org.freemarker</groupId>
			<artifactId>freemarker</artifactId>
		</dependency>

b.配置代码生成器并启动,注意设置生成路径,前缀过滤条件等等

package com.lantu;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.sql.Types;
import java.util.Collections;

public class CodeGenerator {
    public static void main(String[] args) {
        // 设置
        FastAutoGenerator.create("jdbc:mysql:///b24120526", "root", "ssw")
                .globalConfig(builder -> {
                    builder.author("Ssw") // 设置作者
//
                            .outputDir("C:\\Users\\ssw\\Desktop\\Computer\\java_learning\\hello-Mybaits-Plus\\src\\main\\java"); // 指定输出目录
                })
                .dataSourceConfig(builder ->
                        builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
                            int typeCode = metaInfo.getJdbcType().TYPE_CODE;
                            if (typeCode == Types.SMALLINT) {
                                // 自定义类型转换
                                return  DbColumnType.INTEGER;
                            }
                            return typeRegistry.getColumnType(metaInfo);
                        })
                )
                .packageConfig(builder ->
                        builder.parent("com.lantu") // 设置父包名
                                .moduleName("user") // 设置父包 模块名
                                .pathInfo(Collections.singletonMap(OutputFile.xml, "C:\\Users\\ssw\\Desktop\\Computer\\java_learning\\hello-Mybaits-Plus\\src\\main\\resources\\mapper\\user")) // 设置mapperXml生成路径
                                //上面这个一般是放resources上的,路径就放到下面的 Mapper 再下面的 模块名称的文件夹下
                )
                .strategyConfig(builder ->
                        builder.addInclude("xxx_user_info","xxx_dept_info") // 设置需要生成的表名,有三种方式写多种
                                .addTablePrefix("xxx_") // 设置过滤表前缀
                        // 之后会生成 实体类, 过滤后就不会有 xxx_
                )
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }


}

c.生成代码后再测试能不能正常使用

//test/java/com/lantu/UserServiceTest.java
package com.lantu;

import com.lantu.user.entity.UserInfo;
import com.lantu.user.service.IUserInfoService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;


@SpringBootTest
public class UserServiceTest {

    @Autowired
    private IUserInfoService userInfoService;

    @Test
    public void testQueryUserInfo() {
        List<UserInfo> list = userInfoService.list();
        list.forEach(System.out::println);
    }

}

记得在
src/main/java/com/lantu/HelloMybaitsPlusApplication.java
配置好MapperScanner

package com.lantu;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.lantu.mapper") // 显式扫描 Mapper 接口,解决自动配置问题
@MapperScan("com.lantu.*.mapper")//新增这一行,对应图中User模块下的mapper
public class HelloMybaitsPlusApplication {

	public static void main(String[] args) {
		SpringApplication.run(HelloMybaitsPlusApplication.class, args);
	}

2.交互式生成(推荐使用第一种)
交互式生成在运行之后,会提示您输入相应的内容,等待配置输入完整之后就自动生成相关代码。
字面意思:

请输入作者名称?
liudehua
请输入包名?
com.lantu
请输入表名,多个英文逗号分隔?所有输入 all
xxx_user_info,xxx_dept_info

package com.lantu;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class CodeGenerator2 {
    public static void main(String[] args) {
        FastAutoGenerator.create("urljdbc:mysql:///b24120526", "root", "ssw ")
                // 全局配置
                .globalConfig((scanner, builder) -> builder.author(scanner.apply("请输入作者名称?")))
                // 包配置
                .packageConfig((scanner, builder) -> builder.parent(scanner.apply("请输入包名?")))
                // 策略配置
                .strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
                        .entityBuilder()
                        .enableLombok()
                        .addTableFills(
                                new Column("create_time", FieldFill.INSERT)
                        )
                        .build())
                // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .templateEngine(new FreemarkerTemplateEngine())
                .execute();
    }

    // 处理 all 情况
    protected static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }
}
posted @ 2025-11-30 14:45  WishWhiter  阅读(2)  评论(0)    收藏  举报