目录
- MybatisPlus
- 入门案例
- 步骤
- 代码实现
- 数据库及表
- MybatisPlus的Maven坐标
- 配置数据库
- 创建实体类
- User
- Mapper接口
- UserMapper
- 引导类
- 测试类
- 简介
- 使用MP做标准数据层开发
- 标准CRUD
- 新增
- 删除
- 修改
- 根据id查询
- 查询所有
- 分页查询
- 步骤1:调用方法传入参数获取返回值
- 步骤2:设置分页拦截器
- DQL编程控制
- 准备环境
- application.yml
- 引入依赖
- logback.xml
- 条件查询
- 查询投影
- 查询条件
- 映射匹配兼容性
- DML编程控制
- id生成策略
- 简化配置
- 测试
- 多记录操作
- 逻辑删除
- 测试
- 乐观锁
- 概念
- 实现思路
- 步骤
MybatisPlus
入门案例
步骤
- 创建数据库及表,导入数据
- 创建springboot项目,勾选实用技术,引入依赖
- 添加相关配置信息
- 编写实体类,创建mapper层接口
- 引导类添加mapper层接口扫描注解
- 编写测试类
代码实现
数据库及表
create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
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,'Tom','tom',3,'18866668888');
insert into user values(2,'Jerry','jerry',4,'16688886666');
insert into user values(3,'Jock','123456',41,'18812345678');
insert into user values(4,'传智播客','itcast',15,'4006184000');
MybatisPlus的Maven坐标
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version>
</dependency>
配置数据库
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver
#serverTimezone是用来设置时区,UTC是标准时区,和咱们的时间差8小时,所以可以将其修改为`Asia/Shanghaiurl: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=Asia/Shanghaiusername: rootpassword: root
创建实体类
User
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {private Long id;private String name;private String password;private Integer age;private String tel;private Integer online;
}
Mapper接口
UserMapper
public interface UserMapper extends BaseMapper<User> {
}
引导类
@SpringBootApplication
@MapperScan("com.shifan.mapper")//自动扫描该包下的所有接口
public class MybatisPlusApplication {public static void main(String[] args) {SpringApplication.run(MybatisPlusApplication.class, args);}}
测试类
@SpringBootTest
class MpApplicationTests {@Autowiredprivate UserDao userDao;@Testpublic void testGetAll() {List<User> userList = userDao.selectList(null);System.out.println(userList);}
}
简介
MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率
官网:MybatisPlus
使用MP做标准数据层开发
标准CRUD
新增
int insert(T t)
/*tips:爆红是因为UserMapper为接口,无法实例化实现注入服务器启动IOC容器初始化后,框架会生成代理对象完成注入*/@Autowiredprivate UserMapper userMapper;/*** int insert(T t)*/@Testvoid testSave() {User user = User.builder().name("时帆").age(18).password("111").tel("13511111111").build();userMapper.insert(user);}
删除
int deleteById(Serializable id)
/*** int deleteById(Serializable id)*/@Testvoid testDeleteById(){userMapper.deleteById(2L);}
修改
int updateById(T t)
/*** int updateById(T t)*/@Testvoid testUpdateById(){User user = User.builder().name("voracity").age(20).id(3L).password("333").build();userMapper.updateById(user);}
根据id查询
T selectById(Serializable id)
/*** T selectById(Serializable id)*/@Testvoid testSelectById(){User user = userMapper.selectById(5L);System.out.println("user = " + user);}
查询所有
List selectList(Wrapper queryWrapper)
/*** List<T> selectList(Wrapper<T> queryWrapper)*/@Testvoid testSelectList(){List<User> users = userMapper.selectList(null);System.out.println("users = " + users);}
分页查询
IPage selectPage(IPage page,Wrapper queryWrapper)
步骤1:调用方法传入参数获取返回值
/*** IPage<T> selectPage(IPage<T> page , Wrapper<T> queryWrapper)*/@Testvoid testSelectPage(){//创建分页对象,设置分页参数,1为第一页,3为显示记录数IPage<User> page = new Page<>(1,3);userMapper.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());}
步骤2:设置分页拦截器
这个拦截器MP已经为我们提供好了,我们只需要将其配置成Spring管理的bean对象即可。
@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){//1 创建MybatisPlusInterceptor拦截器对象MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();//2 添加分页拦截器mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mpInterceptor;}
}
DQL编程控制
准备环境
application.yml
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver
#serverTimezone是用来设置时区,UTC是标准时区,和咱们的时间差8小时,所以可以将其修改为`Asia/Shanghaiurl: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=Asia/Shanghaiusername: rootpassword: rootmain:#关闭springboot启动logbanner-mode: offmybatis-plus:configuration:# 开启mp日志输出到控制台,影响性能,调试用log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:#关闭mp启动log打印banner: off
引入依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.16</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version></dependency></dependencies>
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--取消初始化spring日志打印,resources目录下添加logback.xml,名称固定-->
</configuration>
条件查询
- Wrapper
- QueryWrapper
- LambdaQueryWrapper
@Autowiredprivate UserMapper userMapper;/*** 测试构建条件查询*/@Testvoid testGetAll() {/*方式一:lt表示小于,即条件为查询年龄小于30的数据缺点:字段名出错难以察觉*//*QueryWrapper qw = new QueryWrapper();qw.lt("age",30);List<User> users = userMapper.selectList(qw);System.out.println("users = " + users);*//*方式二:必须指定泛型,调用lambda()方法开启lambda表达式的使用缺点:多了一层.lambda()调用*//*QueryWrapper<User> qw = new QueryWrapper();qw.lambda().lt(User::getAge,30);List<User> users = userMapper.selectList(qw);System.out.println("users = " + users);*//*方式三:使用LambdaQueryWrapper类,不在需要调用lambda()*/LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.lt(User::getAge,30);List<User> users = userMapper.selectList(lqw);System.out.println("users = " + users);}
-
多条件构建:or()
-
null值判定
/*** 测试构建多条件查询*/@Testvoid testGetAll1() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();//需求:查询年龄在10岁到30岁之间的用户信息//lqw.lt(User::getAge,30);//lqw.gt(User::getAge,10);//支持链式编程//lqw.lt(User::getAge,30).gt(User::getAge,10);//需求:查询年龄小于10或年龄大于30的数据//lqw.lt(User::getAge,10).or().gt(User::getAge,30);/*null值判定,多条件查询时,可能存在条件传递为null的情况需求:根据输入年龄范围来查询符合条件的记录(年龄上限,下限,可能不传递数据,即为null)传统解决方案:使用if语句判断*//*QueryUser qu = new QueryUser();qu.setAge(10);if (qu.getAge()!=null){lqw.gt(User::getAge,qu.getAge());}if (qu.getAge1()!=null){lqw.lt(User::getAge,qu.getAge1());}*//*MybatisPlus解决方案:*/QueryUser qu = new QueryUser();qu.setAge(10);lqw.gt(null!=qu.getAge(),User::getAge,qu.getAge());lqw.lt(null!=qu.getAge1(),User::getAge,qu.getAge1());List<User> users = userMapper.selectList(lqw);System.out.println("users = " + users);}
查询投影
- 查询指定字段:select()
/*** 查询投影*/@Testvoid testGetAll2() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();//需求:查询指定字段:id,name,age/*lqw.select(User::getId,User::getName,User::getAge);List<User> users = userMapper.selectList(lqw);*///若不使用lambda表达式,则需要手动设置字段QueryWrapper<User> qw = new QueryWrapper<>();qw.select("id","name","age");List<User> users = userMapper.selectList(qw);System.out.println("users = " + users);}
-
聚合查询:eg:select(“count(id) count”)
-
分组查询:eg:groupBy(User::getTel)
/*** 聚合函数查询* 聚合函数不支持lambda表达式,lambda表达式只支持实体类属性字段*/@Testvoid testGetAll3() {QueryWrapper<User> qw = new QueryWrapper<>();//qw.select("count(id) count");//加上分组条件qw.select("count(id) count ,tel").groupBy("tel");List<Map<String, Object>> maps = userMapper.selectMaps(qw);System.out.println("maps = " + maps);}@Testvoid test(){LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.select(User::getTel);lqw.groupBy(User::getTel);List<Map<String, Object>> users = userMapper.selectMaps(lqw);System.out.println("users = " + users);}
查询条件
- 等值查询:eq()
/*** 等值查询* 需求:根据用户名和密码查询用户信息* eq:相当于=*/@Testvoid testGetOne() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.eq(User::getName,"John");lqw.eq(User::getPassword,"shifan");User user = userMapper.selectOne(lqw);System.out.println("user = " + user);}
- 范围查询:lt(),le(),gt(),ge(),between()
/*** 范围查询* 需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询* le:相当于<=* ge:相当于>=*/@Testvoid testGetAll4(){LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.between(User::getAge,10,20);List<User> users = userMapper.selectList(lqw);System.out.println("users = " + users);}
- 模糊查询:like(),likeLeft(),likeRight()
/*** 模糊查询* 需求:查询表中name属性的值以`J`开头的用户信息,使用like进行模糊查询* like():在属性值字段的左右都加%,eg:lqw.like(User::getName,"J") ==> %J%* likeLeft():在属性值字段左边加%,eg:%J* likeRight():在属性值字段的右边加%,eg:J%*/@Testvoid testGetAll5() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.likeRight(User::getName,"J");List<User> users = userMapper.selectList(lqw);System.out.println("users = " + users);}
- 排序查询:orderBy(),orderByAsc(),orderByDesc()
/*** 排序查询* 需求:查询所有数据,然后按照id降序* orderBy(boolean condition, boolean isAsc, R... columns):* 第一个参数:是否排序,第二个参数:是否升序,true升序,false降序,第三个参数:字段名,可设置一个或多个* - orderByAsc/Desc(单个column):按照指定字段进行升序/降序* - orderByAsc/Desc(多个column):按照多个字段进行升序/降序* - orderByAsc/Desc* - condition:条件,true添加排序,false不添加排序* - 多个columns:按照多个字段进行排序* 此外还有isNull,isNotNull,in,notIn等方法*/@Testvoid testGetAll6() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.orderBy(true,false,User::getId);List<User> users = userMapper.selectList(lqw);System.out.println("users = " + users);}
映射匹配兼容性
-
@TableField(value=“表字段名”)
-
@TableField(select = false)
-
@TableField(exist = false)
-
@TableName(“表名”)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_user")//映射数据库表名,用于数据库表名和实体类名不一致情况
public class User {private Long id;private String name;@TableField(value = "pwd",select = false)//字段映射,value属性为数据库表中字段名,selcet属性用于设置该字段是否被查询private String password;private Integer age;private String tel;@TableField(exist = false)//exist属性用于表明该字段在数据库表中是否存在private Integer online;
}
DML编程控制
id生成策略
- @TableId(type = IdType.NONE)
NONE表示不使用id生成策略,与INPUT类似,需要手动注入id
- @TableId(type = IdType.AUTO)
AUTO表示使用数据库的主键id自增,前提是数据库的主键id设置了自增属性
- @TableId(type = IdType.INPUT)
INPUT表示id使用手动注入的方式
- @TableId(type = IdType.ASSIGN_ID)
ASSIGN_ID表示使用MP的雪花算法生成id
- @TableId(type = IdType.ASSIGN_UUID)
ASSIGN_UUID表示使用UUID生成id
实际使用案例:
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
//@TableName("tb_user")//映射数据库表名,用于数据库表名和实体类名不一致情况
public class User {//@TableId(type = IdType.NONE)// 不使用主键生成策略,类似Input//@TableId(type= IdType.AUTO)// 使用数据库主键自增,需要保证数据库主键字段有自增属性//@TableId(type = IdType.Input)// 用户手动输入,需要去除数据库主键自增属性,若不给定id值会报错//@TableId(type = IdType.ASSIGN_UUID)// 使用UUID生成主键id,主键类型应为字符串,数据库表中id长度至少32位,长度过小会插入失败//@TableId(type = IdType.ASSIGN_ID)// 使用雪花算法生成主键id(可兼容数值型与字符串型),若所传递的id为null,默认使用此策略,若传递了值则使用传递的值private Long id;private String name;@TableField(value = "pwd",select = false)//字段映射,value属性为数据库表中字段名,selcet属性用于设置该字段是否被查询private String password;private Integer age;private String tel;@TableField(exist = false)//exist属性用于表明该字段在数据库表中是否存在private Integer online;}
简化配置
在配置文件中添加如下配置即可实现全局使用雪花算法生成id
mybatis-plus:global-config:db-config:id-type: assign_id
以下配置用于配置全局表名前缀
mybatis-plus:global-config:db-config:table-prefix: tbl_
测试
/*** 测试主键自增策略* 拓展:分布式id* 当数据量足够大时就需要将数据存储在多台数据库服务器上* 例如订单表存储在不同服务器上,而此时主键若使用自增策略就可能出现冲突* 这时就需要一个全局唯一的id,称为分布式id** 雪花算法:* 雪花算法(SnowFlake),是Twitter官方给出的算法实现 是用Scala写的。* 其生成的结果是一个64bit大小整数,它的结构分为四部分,如下:* 0-00000000 00000000 00000000 00000000 00000000 0-00000000 00-00000000 0000* 第一部分: 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。* 第二部分: 41bit-时间戳,用来记录时间戳,毫秒级* 第三部分: 10bit-工作机器id,用来记录工作机器id,其中高位5bit是数据中心ID其取值范围0-31,低位5bit是工作节点ID其取值范围0-31,两个组合起来最多可以容纳1024个节点* 第四部分: 序列号占用12bit,每个节点每毫秒从0开始不断累加,最多可以累加到4095,每毫秒内一共可以产生4096个ID* id生成策略对比:* - NONE: 不设置id生成策略,MP不自动生成,约等于INPUT,所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的ID造成主键冲突,为了保证主键不冲突就需要做很多判定,实现起来比较复杂* - AUTO:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用* - ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢* - ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键* 综上:根据实际情况选择*/@Testvoid testInsert(){User user = User.builder().name("xiaoming").age(23).tel("12345678345").password("333").build();userMapper.insert(user);}
多记录操作
需求:批量删除用户数据,批量查询用户数据
测试如下:
/*** 测试批量删除*/@Testvoid testDeleteByIds(){List<Long> list = new ArrayList<>();list.add(1647447562652774401L);list.add(1647452538326233089L);list.add(1647452707453181954L);userMapper.deleteBatchIds(list);}/*** 测试批量查询*/@Testvoid testSelectByIds(){List<Long> list = new ArrayList<>();list.add(1L);list.add(3L);list.add(4L);List<User> users = userMapper.selectBatchIds(list);System.out.println("users = " + users);}
逻辑删除
- @TableLogic
实现步骤:
- 在数据库表中添加标识逻辑删除的字段
- 在实体类中添加对应的标识字段,并在此字段上添加@TableLogic注解
- 设置@TableLogic的value和delval属性值,value属性表示当前数据为正常数据,delval属性表示当前数据为
已被删除的数据
实际应用:
//@TableLogic(value = "0",delval = "1")//逻辑删除字段,标记当前数据是否被删除,value表示正常数据,delval表示该数据已被删除private Integer deleted;
全局配置:
mybatis-plus:global-config:db-config:# 逻辑删除字段名logic-delete-field: deleted# 逻辑删除字面值:未删除为0logic-not-delete-value: 0# 逻辑删除字面值:删除为1logic-delete-value: 1
测试
/*** 测试逻辑删除* 用途:解决物理删除(直接删除表中数据,delete操作)会对数据造成伤害的问题* 步骤:在表中添加一个字段(添加默认值属性),用于标识当前数据是否被删除* 在实体类中添加对应的字段并使用@TableLogic注解标识该字段,给定value和delval属性的值即可* 逻辑删除本质上执行的是更新语句,配置了逻辑删除字段后,查询也会自动带上该字段,只查询未被删除的数据* 若需要查询已被删除的数据则需要自己手写实现*/@Testvoid testLogicDelete(){userMapper.deleteById(1L);List<User> users = userMapper.selectList(null);System.out.println("users = " + users);}
乐观锁
概念
主要用于解决修改数据时,期望当前数据未被其他人修改
实现思路
步骤
/*** 乐观锁测试* 原理:* 给数据库表添加字段,如version默认值为1* 当两个线程同时修该一条数据时,需要先获取表中数据,拿到version的值* eg:线程A和B同时来修改id为1的用户的信息* 线程A查询id为1的用户信息,得到此时用户的version字段值为1* 线程B同样查询到id为1的用户信息,此时用户的version字段值为1* 假设线程B先于线程A修改了id为1的用户的信息,并将version的值加了1* 即:update user set name = ? , age = ? ,version = version + 1 where id = 1 and version = 1* 此时线程A再去修改用户信息时就会修改失败,因其修改语句为:* update user set name = ? , age = ? ,version = version + 1 where id = 1 and version = 1* 而version值已被修改为2,故修改失败* 实现步骤:* 1.在数据库表中添加标识字段,并设置默认值,eg:version字段,默认值1* 2.在实体类中添加对应的属性字段,并加上注解@Version* 3.添加乐观锁拦截器* 4.查询需要修改的数据,获取version值* 5.更改数据值,更新数据*/@Testvoid testOptimisticLock(){//没有设置version值,无法实现乐观锁/*User user = User.builder().id(3L).name("mp").password("399").tel("322").age(10).build();*///先查询需要修改的数据User user = userMapper.selectById(3L);//修改数据user.setName("mp");user.setAge(10);user.setPassword("3");//更新数据userMapper.updateById(user);}/*** 模拟多线程情况修改同一条数据*/@Testvoid testOptimisticLock1(){//先查询需要修改的数据User user = userMapper.selectById(3L);//取得的version值为2User user1 = userMapper.selectById(3L);//取得的version值为2user1.setName("user1");userMapper.updateById(user1);//修改成功,version->3user.setName("user");userMapper.updateById(user);//修改失败,此时version值已被修改为3}