1. 简介
-
MybatisPlus(简称MP)是基于MyBatisPlus框架基础上开发的增强型工具,旨在简化开发、提高效率
-
官网:https://mybatis.plus/ https://mp.baomidou.com/
-
开发方式:
- 基于MyBatis使用MybatisPlus
- 基于Spring使用MybatisPlus
- 基于SpringBoot使用MybatisPlus
-
SpringBoot整合MyBatis开发过程:
- 创建SpringBoot工程
- 勾选配置使用的技术
- 设置dataSource相关属性(JDBC参数)
- 定义数据层接口映射配置
-
由于MyBatisPlus并未收录到idea的系统内置配置,无法直接选择加入,需要手动添加MyBatisPlus起步依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency>
MP的特性:
- 无侵入:只做增强不做改变,不会对现有工程产生影响
- 强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表CRUD 操作
- 支持 Lambda:编写查询条件无需担心字段写错
- 支持主键自动生成
- 内置分页插件
- …
2. 标准数据层开发
2.1 标准数据层CRUD功能
功能 | 自定义接口 | MP接口 |
---|---|---|
新增 | boolean save(T t) | int insert(T t) |
删除 | boolean delete(int id) | int deleteById(Serializable id) |
修改 | boolean update(T t) | int updateById(T t) |
根据id查询 | T getById(int id) | T selectById(Serializable id) |
查询全部 | List getAll() | List selectList() |
分页查询 | PageInfo getAll(int page,int size) | IPage selectPage(IPage page) |
按条件查询 | List getAll(Condition condition) | IPage selectPage(Wrapper queryWrapper) |
2.2 lombok
-
lombok,一个Java类库,提供了一组注解,简化了POJO实体类开发
-
Lombok可以为当前的实体类在编译期设置get/set方法,无参/有参构造方法,toString方法,hashCode方法,equals方法等
-
可以在pom.xml中导入依赖:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <!--<version>1.18.12</version>--> </dependency>
也可以直接在创建SpringBoot项目时,选择lombok:
-
常用注解:
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
2.3 分页查询
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
- IPage:用来构建分页查询条件
- Wrapper:用来构建条件查询的条件
- IPage:返回值,你会发现构建分页条件和方法的返回值都是IPage
具体步骤:
- 调用方法传入参数获取返回值
@Test
void testGetByPage(){
IPage page = new Page(1,2);
userDao.selectPage(page,null);
System.out.println("当前页码值:"+page.getCurrent());
System.out.println("一共多少页:"+page.getPages());
System.out.println("数据:"+page.getRecords());
System.out.println("每页显示数:"+page.getSize());
System.out.println("一共多少条数据:"+page.getTotal());
}
- 设置分页拦截器
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor(){
//定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//添加具体的拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
- 运行测试程序
- 如果想要查看Sql语句,可以在配置文件中添加:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台
2.4 关闭日志
在配置文件中添加用来关闭mybatisplus的日志:
mybatis-plus:
global-config:
banner: false
在配置文件中添加如下设置用来关闭springboot的日志:
同时还要在resource文件夹下添加logback.xml,其中仅存放:
<?xml version="1.0" encoding="UTF-8"?>
<configuration >
</configuration>
3. DQL编程控制
3.1 条件查询
- MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。
- QueryWrapper
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper qw = new QueryWrapper();
qw.lt("age",18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
}
-
lt: 小于(<) ,最终的sql语句为
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
- 第二种:QueryWrapper的基础上使用lambda
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.lambda().lt(User::getAge, 10);//添加条件
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
}
- User::getAget,为lambda表达式中的,类名::方法名,最终的sql语句为:
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
注意: 构建LambdaQueryWrapper的时候泛型不能省。
- 第三种:LambdaQueryWrapper
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
3.2 null值处理
- if条件语句判断
- 条件参数控制
- 条件参数控制(链式编程)
可以使用两个简单数据类型,也可以使用一个模型类,但是User类中目前只有一个age属性,如:
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
使用一个age属性,如何去接收页面上的两个值呢,有两个解决方案:
方案一:添加属性age2,这种做法可以但是会影响到原模型类的属性内容
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
private Integer age2;
}
方案二:新建一个模型类,让其继承User类,并在其中添加age2属性,UserQuery在拥有User属性后同时添加了age2属性。
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
@Data
public class UserQuery extends User {
private Integer age2;
}
环境准备好后,实现刚才的需求:
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
//模拟页面传递过来的查询数据
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
if(null != uq.getAge2()){
lqw.lt(User::getAge, uq.getAge2());
}
if( null != uq.getAge()) {
lqw.gt(User::getAge, uq.getAge());
}
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
}
上面的写法可以完成条件为非空的判断,但是问题很明显,如果条件多的话,每个条件都需要判断,代码量就比较大,MP提供了简化方式:
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
//模拟页面传递过来的查询数据
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
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);
}
}
-
lt()方法
condition为boolean类型,返回true,则添加条件,返回false则不添加条件
3.3 查询投影
查询投影即不查询所有字段,只查询出指定内容的数据
- 查询指定的字段:
@Test
void testQuarry(){
LambdaQueryWrapper<Dept> lqw = new LambdaQueryWrapper<Dept>();
lqw.select(Dept::getId,Dept::getName);
List<Dept> userlist = userDao.selectList(lqw);
System.out.println(userlist);
}
select(…)方法用来设置查询的字段列,可以设置多个
- 如果使用的不是lambda,就需要手动指定字段
@Test
void testQuarry(){
QueryWrapper<Dept> lqw = new QueryWrapper<Dept>();
lqw.select("id","name");
List<Dept> userlist = userDao.selectList(lqw);
System.out.println(userlist);
}
3.4 查询条件
- 查询条件:
- 范围匹配(>、=、between)
- 模糊匹配(like)
- 空判断(null)
- 包含性匹配(in)
- 分组(group)
- 排序(order)
- ......
- 查询范围:
gt():大于(>)
ge():大于等于(>=)
lt():小于(<)
lte():小于等于(<=)
between():between ? and ?
- 等值查询
eq(): 相当于 `=`,对应的sql语句为
selectList:查询结果为多个或者单个
selectOne:查询结果为单个
3.5 字段映射与表名映射
MP提供了一个注解@TableField
,使用该注解可以实现模型类属性名和表的列名之间的映射关系
-
@TableFiled
-
属性注解
-
它有一个属性叫
exist
,设置该字段是否在数据库表中存在,如果设置为false则不存在,生成sql语句查询的时候,就不会再查询该字段了 -
@TableField
注解的一个属性叫select
,该属性设置默认是否需要查询该字段的值,true(默认值)表示默认查询该字段,false表示默认不查询该字段
3.5.1 @TableField
名称 | @TableField |
---|---|
类型 | 属性注解 |
位置 | 模型类属性定义上方 |
作用 | 设置当前属性对应的数据库表中的字段关系 |
相关属性 | value(默认):设置数据库表字段名称 exist:设置属性在数据库表字段中是否存在,默认为true,此属性不能与value合并使用 select:设置属性是否参与查询,此属性与select()映射配置不冲突 |
3.5.2 @TableName
名称 | @TableName |
---|---|
类型 | 类注解 |
位置 | 模型类定义上方 |
作用 | 设置当前类对应于数据库表关系 |
相关属性 | value(默认):设置数据库表名称 |
4. DML编程控制
4.1 id生成策略
- AUTO(0):使用数据库id自增策略控制id生成
- NONE(1):不设置id生成策略
- INPUT(2):用户必须自己输入id
- ASSIGN_ID(3):雪花算法生成id(可兼容数值型与字符串型)
- ASSIGN_UUID(4):以UUID生成算法作为id生成策略
- 其他的几个策略均已过时,都将被ASSIGN_ID和ASSIGN_UUID代替掉。
对比:
- NONE: 不设置id生成策略,MP不自动生成,约等于INPUT,所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的ID造成主键冲突,为了保证主键不冲突就需要做很多判定,实现起来比较复杂
- AUTO:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用
- ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢
- ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键
4.1.1 @TableId
名称 | @TableId |
---|---|
类型 | 属性注解 |
位置 | 模型类中用于表示主键的属性定义上方 |
作用 | 设置当前类中主键属性的生成策略 |
相关属性 | value(默认):设置数据库表主键名称 type:设置主键属性的生成策略,值查照IdType的枚举值 |
分布式ID是什么?
- 当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储
- 比如订单表就有可能被存储在不同的服务器上
- 如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突
- 这个时候就需要一个全局唯一ID,这个ID就是分布式ID。
4.2 雪花算法
雪花算法(SnowFlake),是Twitter官方给出的算法实现 是用Scala写的。其生成的结果是一个64bit大小整数,它的结构如下图:
- 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
- 41bit-时间戳,用来记录时间戳,毫秒级
- 10bit-工作机器id,用来记录工作机器id,其中高位5bit是数据中心ID其取值范围0-31,低位5bit是工作节点ID其取值范围0-31,两个组合起来最多可以容纳1024个节点
- 序列号占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID
4.3 简化配置
在配置文件中进行如下配置:
mybatis-plus:
global-config:
db-config:
id-type: assign_id
4.4 逻辑删除
- 逻辑删除:为数据设置是否可用状态字段,删除时设置为不可用状态,但数据仍然保留
@TableLogic
名称 | @TableLogic |
---|---|
类型 | 属性注解 |
位置 | 模型类中用于表示删除字段的属性定义上方 |
作用 | 标识该字段为进行逻辑删除的字段 |
相关属性 | value:逻辑未删除值 delval:逻辑删除值 |
4.5 乐观锁
当要更新一条记录的时候,希望这条记录没有被别人更新
4.5.1 并发控制
当程序中可能出现并发的情况时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。这就叫做并发控制。并发控制的目的是保证一个用户的工作不会对另一个用户的工作产生不合理的影响。
没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题
乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做,乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。
4.5.2 乐观锁的实现方式
- 数据库表中添加version列,比如默认值给1
- 第一个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
- 第二个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
- 第一个线程执行更新时,set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 第二个线程执行更新时,set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 假如这两个线程都来更新数据,第一个和第二个线程都可能先执行
- 假如第一个线程先执行更新,会把version改为2,
- 第二个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第二个线程会修改失败
- 假如第二个线程先执行更新,会把version改为2,
- 第一个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第一个线程会修改失败
- 不管谁先执行都会确保只能有一个线程更新数据,这就是MP提供的乐观锁的实现原理分析。
步骤:
-
数据库表添加列
列名可以任意,比如使用
version
,给列设置默认值为1
-
在模型类中添加对应的属性
根据添加的字段列名,在实体类中添加对应的属性值
-
添加乐观锁的拦截器
@Configuration public class MpConfig { @Bean public MybatisPlusInterceptor mpInterceptor(){ //定义Mp拦截器 MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); //添加乐观锁的拦截器 mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mpInterceptor; } }
-
当没有携带
version
数据时,并不会进行更新version
字段 -
要想实现乐观锁,需要拿到
version
,把version
当作条件进行查询,并将更新后的version1
重新放回数据库列表中
5. 代码生成器
- 模板:MybatisPlus提供
- 数据库相关配置:读取数据库获取信息
- 开发者自定义配置:需要手动设置
5.1 相关依赖
<!--代码生成器-->
<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>
5.2 创建代码生成类
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("123456");
autoGenerator.setDataSource(dataSource);
//设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java"); //设置代码生成位置
globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录
globalConfig.setAuthor("liang"); //设置作者
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("tbl_user"); //设置当前参与生成的表名,参数为可变参数
strategyConfig.setTablePrefix("tbl_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tbl_user - tbl_
strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格
strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); //设置是否启用lombok
autoGenerator.setStrategy(strategyConfig);
//2.执行生成操作
autoGenerator.execute();
}
}
官网:代码生成器(新) | MyBatis-Plus (baomidou.com)
5.3 官网模板
FastAutoGenerator.create("url", "username", "password")
.globalConfig(builder -> {
builder.author("baomidou") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://"); // 指定输出目录
})
.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.baomidou.mybatisplus.samples.generator") // 设置父包名
.moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, "D://")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_simple") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();