今天先试试水,先创建一个大概能看的笔记 好,出于事态紧急,我们直接空降mybatis-plus吧
然后就是这里跟的是https://cyborg2077.github.io/2022/09/20/MyBatisPlus/
这篇笔记做的
额,就是不对底层逻辑进行过多死磕,大概了解一下用法,咱不做理论大师,但咱得向这些大师看齐,多多学习
他这边的技术栈大概就是 SpringBoot+Mybatis+MySQL
步骤一
首先是建表
首先之前我只用过SQLserver,但似乎MySQL的语法类似,所以我就写类似的吧,顺便帮我回忆一点基本语法吧,等这篇笔记写完我让Chat帮我查查错误
//
create table user(
id bigint(20) primary key auto_increment,
name varchar(32) not null,
password varchar(32) not null,
age int(3) not null,
tel varchar(32) not null
);
insert into user values(1,'用户1','666','12','1234567);
insert into user values(2,'用户2','995995','21','120');
insert into user values(3,'用户3','4869','82','119')
步骤二
然后就是配依赖,用的是mybatis-plus 3.4.1的版本,然后看到他这边用的好像是druid这个连接池,我们还是默认用Hikari作为连接池吧,不用特地配置他的以来了
<!-- Web 项目 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JDBC 基础(可选,很多时候 web starter 里已经带了) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot3-starter</artifactId>
<version>3.5.14</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok(可选,简化实体类) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
然后就是这边application我们把后缀从properties改成yaml,这样更规范
大概就是这么个结构
spring:
datasource:
driver-class-name:com.mysql.cj.jdbc.Driver
url:/*那就是自己的数据库链接了*/
username:
password:
mybatis-plus:
configuration;
log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
然后这些造完就把对应数据库的entity建好就行了,我记得
lombok是可以帮我创建好他基本的set和get方法
然后就是mapper
@Mapper
public inetrface UserMapper extends BaseMapper<User>{
}
就差不多这样,其实很方便的都帮我们创建完了,我比较喜欢叫这玩意叫mapper而非dao,因为dao有种提刀砍人的谐音感观感不好,这就类似spring里面的bean给我带来很差的观感一样,但我还找不到替换词
然后就是这个extends的这个basemapper<User>这个玩意,其实也类似预制菜一样的东西,帮我们直接把他的
insert(T entity) deleteById(Serializable id) updateById(T entity) T selectById(Serializable id) List<T>selectList(Wrapper<T>queryWrapper)
这些基础功能给创建完了,其实就直接用就行了,如果还有其他功能实现的需求就需要去自己写了
然后这里这个注解,也就是这个@Mapper,是告诉你mybatis说“这接口是一个mapper,对其生成类并注册bean”
和sql关系最近的就是这个接口了,就类似仓库和仓库管理员的关系了
然后你就建议的搭一个controller去测试一下,也是需要用到一个注解好像叫@RestController,可以让我们到postman里面去测试我们写的方法,比如我们需要获取我们数据库里面用户的信息就可以用@GetMapping这个注解,这里就不多说了,目前在读的文章的作者应该会在后面讲到,我就不瞎说了
步骤三
额,这边讲了MP(也就是mybatis-plus)的基本性质
那这边直接贴作者的写的吧(因为我大概也不会一个字一个字敲了,懒是这样的)
MP的特性:
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
然后就是挺神奇的就是mapper会在和模型名同名的表里面查找,就比如我写BaseMapper<User>,他就自己跑去找我数据库里面和User同名的表
那如果你就是不想写同名,那你也可以用@TableName注解,比如数据表叫tb_user但数据类叫User,那就再User类上加上注解,也就是@TableName(“tb_user”)
接下来就是这个Lombok,这个玩意也挺方便的说,这个是一个java类库,版本由SpringBoot管理版本号的
基本注解如下
- –
@Setter:为模型类的属性提供setter方法@Getter:为模型类的属性提供getter方法@ToString:为模型类的属性提供toString方法@EqualsAndHashCode:为模型类的属性提供equals和hashcode方法@Data:是个组合注解,包含上面的注解的功能@NoArgsConstructor:提供一个无参构造函数@AllArgsConstructor:提供一个包含所有参数的构造函数
我靠,好像我这样写不光别人看不懂,我自己也P都看不懂,我明天高低得列个表格来列出每个东西的注释或者方法(包括重载和实际用法),今天先睡了,我看看现在几点,2025.11.27 0:53 看到3.3.3,当然我还没写笔记写到这里来
OK现在是2025 11.27 22.23,比较晚了明天还有早八就随便写点吧
然后就是这个查询,当初我刚用的时候就有很多疑问,
那总之我这里先列出来吧
步骤四
MyBatis-Plus 几种常见查询方式对比
| 查询方式 | 写法示例 | 是否写字符串列名 | 主要用途 / 场景 | 优点 | 缺点 / 注意点 |
|---|---|---|---|---|---|
| 直接查全部(无条件) | userMapper.selectList(null); | 否 | 快速查询整张表所有数据 | 写法最简单,看库里现在到底有什么数据 | 只能查全部,不能加 where 条件 |
QueryWrapper | java\nQueryWrapper<User> qw = new QueryWrapper<>();\nqw.eq(\"age\", 18);\nqw.like(\"name\", \"张\");\nuserMapper.selectList(qw);\n | ✅ 要 | 早期/简单 demo,条件不多时 | API 简单直白,和 SQL 很像 | 列名是字符串:拼错编译器不报错,重构易踩坑 |
LambdaQueryWrapper | java\nLambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();\nlqw.eq(User::getAge, 18)\n .like(User::getName, \"张\");\nuserMapper.selectList(lqw);\n | ❌ 不要 | 日常开发推荐用;需要保证重构安全、IDE 友好 | 用 User::getXxx 写列名,编译期能检查,字段改名不易炸 | 写法比字符串略长一点,但基本没啥副作用 |
带条件参数的 LambdaQueryWrapper | java\nLambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();\nlqw.eq(age != null, User::getAge, age)\n .like(StringUtils.hasText(name), User::getName, name);\nuserMapper.selectList(lqw);\n | ❌ 不要 | 可选条件查询:有值才拼条件,没有就略过 | 用 boolean condition 控制是否拼接某个 where 条件 | 初看有点绕,但一旦写过一次就离不开 |
lambdaQuery() 链式查询(Service) | java\nList<User> list = userService.lambdaQuery()\n .eq(User::getAge, 18)\n .like(User::getName, \"张\")\n .list();\n | ❌ 不要 | 使用 MP 自带的 ServiceImpl 时,写查询更顺手 | 语法更链式,看起来像“从 Service 直接开口要数据” | 依赖 ServiceImpl,不是所有项目都用这套 Service 结构 |
怎么说呢,就是讲一下这个LambdaQueryWrapper和这个QueryWrapper的区别吧,在进行查询的时候QueryWrapper是根据字符串进行查询的,但这就会导致说你要是把字符输错了他也不会报错,但是LambdaQueryWrapper是引用方法的,所以你要是输入的方法错了他就会报错,来个简单的例子
比如QueryWrapper
QueryWrapper <User>qw=new QueryWrapper<>();
qw.lt("age",18);//这里是直接输入age这个字符的
然后就是LambdQueryWrapper
LambdaQueryWrapper <User>lqw= new LambdaQueryWrapper<>(); lqw.lt(User::getAge(),18);//而这里是引用方法的
所以为了开发方便一般都会用lambdaQueryWrapper
然后就是我经常混淆的一点就是我以为这些Wrapper是先查询我们数据库,然后再通过条件筛选将我们需要的那几行结果存储到它本身,但其实并不是,他其实就是个类似说明书的作用,真正链接并查询的是类似selectList()这种方法
就类似
List<User> result=userMapper.selectList(lqw);//这里面的参数就是lqw,也就是他会告诉这个selectList这个方法说,我需要你按照lqw里面的条件去我数据库对应的表里找符合条件的数据,这样其实就省去了我们很多麻烦,因为按照正常的spring写法,我们得额外写那个xml创建方法进行查询,但现在我们只需要利用MP封装完得方法直接使用就行了,当然这些只是基础需求可以让我们偷懒的,如果有其他业务需求当然还是得我们自己手搓xml
好了明天有早八就先到这里了吧,今天就是写了一点昨天晚上没写完的笔记内容,但是读文章的进度还没推进,现在是2025 11.27 23:33
步骤五
好的今天是12.1然后现在是23:21,对已经过去好几天了我才更新(懒鬼是这样的)
然后就是这几个比较有意思的注解啊,就是类似我们之前讲过的那个TableName,我直接在这边列个表吧看的比较清楚一点(这个编辑模式下看这个表格是真的乱)
| 注解名 | 类型(所在包) | 作用位置 | 主要作用说明 |
|---|---|---|---|
| @TableName | com.baomidou.mybatisplus.annotation.TableName | 类上 | 指定实体类对应的数据库表名、schema、resultMap 等 |
| @TableId | com.baomidou.mybatisplus.annotation.TableId | 字段上 | 指定主键字段名,配置主键生成策略(IdType) |
| @TableField | com.baomidou.mybatisplus.annotation.TableField | 字段上(非主键) | 指定普通字段名及各种字段策略、自动填充等 |
怎么说呢,就是,咱先按这个顺序来讲吧,首先就是这个TableName,一般咱用到的也就是那个value这个属性,他指的是你这个已经链接的数据库的指定表名(如果你怕连错的话就可以添加一个这个)
@TableName("tb_user)
@Data
public class User{
}//类似于这样
然后就是这个TableId,一般常用的属性就是value还有这个type,value这玩意默认就是设置数据库表主键名称的比如
@TableName("tb_user")
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Integer online;
}
这样咱的id就被设置为主键了,然后就是这个什么,这个type,他是可以设置我们这个主键规则的
| 枚举值 | 含义说明 |
|---|---|
| AUTO | 数据库自增 |
| NONE | 未指定(跟随全局配置,一般是 INPUT) |
| INPUT | 插入前手动 set 主键 |
| ASSIGN_ID | MP 分配长整型/字符串雪花 ID |
| ASSIGN_UUID | MP 分配 32 位 UUID 字符串 |
就差不多是这样的,Auto是默认的的自增(即按照你表的规则自增,比如一次增加3或者5之类的)
然后这个none和input基本算是同流合污,input就是让你自己输入主键的值,none的默认好像也是Input
接下来就是这个ASSIGN_ID和这个ASSIGN_UUID
这俩个是跟分布式有关系的,一旦你数据量太大,一台服务器装不下就需要多台数据库服务器,那么一个数据类的内容可能会被存在不同的数据库服务器上吗,这时候就需要这俩了
①首先是这个ASSIGN_ID,他是通过一个叫雪花算法的东西生成的ID,数据类型为Long,一般都是由64位构成的整数
首位用来表示正负(也就是我们计算机组成原理里面那玩意)
随着他后面的41位表示时间戳,毫秒级别的记录时间
在接下来的10位用来记录工作机器的ID,高五位用来记录数据中心ID,低五位用来记录工作节点ID
最后的序列号ID占12位从0毫秒开始叠加最多到4095,共产生4096个ID
//其实我很想把图放在这里面来详细解释可惜笔记没办法放图好像
②然后就是这个UUID了
数据类型为varchar,长度需要大于32位,对就这样,好像也没什么更多好说的了
哦对了然后就是我们可以在application.yaml里面配置MP的数据库配置,比如我多个表都需要将数据类型设置为assgin_id,那可以这么配置
mybatis-plus:
global-config:
db-config:
id-type: assign_id
还有就是如果你的数据库名有固定的前缀也可以加上一个
tablke_prefix:tb_//形如tb_这种的
这样你就不用每次重复写这玩意了
OK了现在是12.2 0:05了,其实这就是在对我白天上课摸鱼时看的笔记的补充,所以现在思路还算是挺清晰的,我打算微服务和redis学完后自己部署一个网站,明天把MP这章完结了就开始推下一章吧,到时候看能不能把笔记的格式再美化一下
好的现在是 12.4 20:12,偷懒了一天,打算把MP这章完结一下子
其实就是多记录操作
@Test
void testDeleteByIds(){
ArrayList<Long> list = new ArrayList<>();
list.add(1572543345085964289L);
list.add(1572554951983460354L);
list.add(1572555035978534913L);
userDao.deleteBatchIds(list);
}
形如上述这种写法,就是将list这个数组作为参数传入userMapper里面的这个方法(似乎也是BaseMapper里面提供的方法),当然有delete也会有select,用法也差不多,也就是selectBatchIds(list)
还有就是MP里面也可以执行对数据库的操作,比如
//表名前缀和id生成策略在yml配置文件写了
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
//新增delete属性
//value为正常数据的值(在职),delval为删除数据的值(离职)
@TableLogic(value = "0",delval = "1")
private Integer deleted;
}
这样,我们就添加了一个属性,叫deleted
这样我们执行userMapper.deleteById(1)时,就会在数据库里执行
UPDTAE user set deleted=1 where userId=1
也就是他不会真的把你数据库里的数据删掉,只是打个标记罢了,这样就不会在实际业务里面出现无主信息
那么,当你执行
userMapper.selectList(null)
时,数据库就会执行
select*from user where deleted=0;
一般这种在业务中就是类似“冻结/禁用/停职”这种,我们不真的把数据删掉而是做标记保留
那如果我们要全局应用的话,就可以在配置文件里面直接做类似添加
mybatis-plus:
global-config:
db-config:
## 逻辑删除字段名
logic-delete-field: deleted
## 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
## 逻辑删除字面值:删除为1
logic-delete-value: 1
那么就对TableLogic做个总结吧
| 名称 | TableLogic |
|---|---|
| 类型 | 属性注解 |
| 位置 | 模型类中用于表示删除字段的属性定义上方 |
| 作用 | 标识该字段为进行逻辑删除的字段 |
| 相关属性 | value:逻辑未删除值 delval:逻辑删除值 |
接下来就是跟线程有点关系的东西了,这就是学科之间的联动性啊,不对应该是一致性
来看看乐观锁,这就是我之前在操作系统实验里面有学到过的锁
一般我们会遇到一个问题,就是同一个活动可能会在不同服务器里同时执行,也就是并发,比如动车售票
那我们需要添加一个version的属性,首先在数据库里面给version设置默认值为1,长度为11
为了防止线程冲突,就会有类似逻辑
set version =newVersion where version=oldVersion newVersion =version +1 oldVersion=newVersion
人话来说就是我要对version实现自增加1
那么我俩个线程在执行时,假如线程1执行,version会变成2,那么线程2在执行时就会发现version=2无法执行,从而保证了线程不会并发,唯一动作被执行
那么我们添加一下
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
@TableLogic(value = "0", delval = "1")
private Integer deleted;
@Version
private Integer version;
}
并在配置文件里面添加一下乐观锁拦截器
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
做一个简单的案例,也就是俩个user对象对内容进行修改
@Test
void testUpdate() {
User userA = userDao.selectById(1L); //version=1
User userB = userDao.selectById(1L); //version=1
userB.setName("Jackson");
userDao.updateById(userB); //B修改完了之后,version=2
userA.setName("Person");
//A拿到的version是1,但现在的version已经是2了,那么A在执行 UPDATE ... WHERE version = 1时,就必然会失败
userDao.updateById(userA);
}
就是这个,可以理解为userA和userB都拿到了1L这个对象,那么我userB执行setName后,这里version就自增等于2了,那么userA就没办法对version已经为2的1L进行修改了,这就保证了我们在同一时间内数据不会被两个用户更改而产生冲突
当然这里的updateById方法是能正常执行的
OKOKOK接下来就是最后一part了,就是这个代码生成器了(其实用起来很难受,但咱还是多少要学一下)
首先就是需要在maven里面把这个依赖配置一下,
<!--代码生成器-->
<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>
这里需要根据我们的版本进行灵活的变更
然后再编写一个引导类
@SpringBootApplication
public class Mybatisplus04GeneratorApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus04GeneratorApplication.class, args);
}
}
没错这个就是负责我们生成文件的启动器
然后就是
public class CodeGenerator {
public static void main(String[] args) {
//1.获取代码生成器的对象
AutoGenerator autoGenerator = new AutoGenerator();
//设置数据库相关配置
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("YOURPASSWORD");
autoGenerator.setDataSource(dataSource);
//设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/项目名/src/main/java"); //设置代码生成位置
globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录
globalConfig.setAuthor("Kyle"); //设置作者
globalConfig.setFileOverride(true); //设置是否覆盖原始生成的文件
globalConfig.setMapperName("%sDao"); //设置数据层接口名,%s为占位符,指代模块名称
globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略
autoGenerator.setGlobalConfig(globalConfig);
//设置包名相关配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.aaa"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
packageInfo.setEntity("domain"); //设置实体类包名
packageInfo.setMapper("dao"); //设置数据层包名
autoGenerator.setPackageInfo(packageInfo);
//策略设置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tb_user"); //设置当前参与生成的表名,参数为可变参数
strategyConfig.setTablePrefix("tb_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tb_user - tb_
strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格
strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); //设置是否启用lombok
autoGenerator.setStrategy(strategyConfig);
//2.执行生成操作
autoGenerator.execute();
}
}
这个就类似专门为代码生成器写的配置文件,当然这里面的乐死globalConfig可以用类似
global.setOutPutDir()
.setOpen(false)
.setAuthorf("me")
......
这么写,节省一点时间,下面那些同理
然后就需要我们自己去编写ftl文件对我们对于文件进行详细配置
运行那个负责生成的文件后,就可以生成我们需要的controller,service ,mapper,entity等文件
嗯,这就是全部了,从27到今天大概一周左右,MP的内容没有想象中的那么多,但是具体我掌 握得怎么样还得看我之后得使用,路漫漫其修远兮啊。 如果后续我有什么地方写错,我会再进行更新额,我也会常来复习这些内容的 还有就是,特别感谢我同学分享的资源和心得,这是他网站的链接 http://artsail.top/


