MyBatis-Plus 条件查询器的实现
作者:@WAT
本篇的主要代码依赖于之前的通用Mapper和通用Service篇
一、常用注解
在我们平时的日常开发中,会经常遇到我们的数据小伙伴们在数据库中所创建的 表名要与我们Java开发人员所建的domian层的类名有一个对应关系
,这往往在不同的公司都有着不同的要求。
打个比方:数据库表名 t_user
-------- 后台domian的实体类名User
。这样很明显会操做报错。
所以我们的MyBtais-Plus为我们提供了一系列的注解,下面我们来正式学习他们
1.1 @TableName
在实体类上加上注解@TableName("t_user")
,标识这个类所对应的表名是t_user,这样才可以成功映射到对应的字段。
@TableName("t_user") public class User { private Long id; private String userName; private Integer age; private String email; }
以上是通过注解的方式完成,我们也可以通过配置文件来设置
mybatis-plus: configuration: # 配置MyBatis日志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: # 配置MyBatis-Plus操作表的默认前缀 table-prefix: t_
通过table_prefix设置前缀只适用于数据库表有前缀的,如果表名与实体类名差别甚远,则建议使用注解
1.2 @TableId
MyBatis-Plus在实现CRUD时,会默认将id作为主键列,并在插入数据时,默认是基于雪花算法的策略生成id
ASSIGN_ID
【产生的问题】
- 若实体类和表中表示主键的不是id,而是其他字段 如何匹配
- 怎样实现数据库中的自增策略
【对策】
在实体类中uid属性上通过@TableId将其标识为主键
,即可成功执行SQL语句
@TableName("t_user") public class User { @TableId(value = "id", type = IdType.AUTO) private Long id; private String userName; private Integer age; private String email; }
这里有必要说下,idType,这个IdType
是MyBatis-plus里面所提供的一种主键生成策略的枚举类
【源码】
@Getter public enum IdType { /** * 数据库ID自增 * <p>该类型请确保数据库设置了 ID自增 否则无效</p> */ AUTO(0), /** * 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) */ NONE(1), /** * 用户输入ID * <p>该类型可以通过自己注册自动填充插件进行填充</p> */ INPUT(2), /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */ /** * 分配ID (主键类型为number或string), * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法) * * @since 3.3.0 */ ASSIGN_ID(3), /** * 分配UUID (主键类型为 string) * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-","")) */ ASSIGN_UUID(4); private final int key; IdType(int key) { this.key = key; } }
也就是说MP提供了5种测类,下面简要的介绍其中的某些:
AUTO:
数据库自增策略,注意,该类型请确保数据库设置了id自增, 否则无效
- NONE:MP set主键,雪花算法实现。就是如果传了id用传的,否则默认的雪花算法
- INPUT:需要时开发者手动赋值,没写一样雪花算法自动生成
- ASSIGN_ID: 雪花算法,默认,与数据库id是否设置了自增无关
1.3 @TableField
TableField与TableId的区别就是:
- TableField适用于解决非主键字段不匹配的情况,而且TableField还可以指定那个字段查询的时候不显示
- TableId主要解决主键不匹配的情况
@TableName("t_user") public class User { // 处理主键相关的字段名不一致 以及设置主键自增策略 @TableId(value = "id", type = IdType.AUTO) private Long id; // 在mp种默认的设置可以将数据库中的字段名xxx_xxx转化为相应的驼峰命名 @TableField(value = "user_name") private String userName; @TableField(value = "age") private Integer age; // exist=false 表示查询时候不显示 @TableField(exist = false) private String email; }
当我们使用了@TableField(exist = false)
则在查询的时候就不会显示查询出来的值
1.4 @TableLogic
逻辑删除
在我们的日常开发中,会经常遇到这种逻辑删除的操做,因为这样仍然会在数据库中保存这条数据,防止后期如果还想用的话还可以找到。我们要时刻铭记,数据的CUD操做,一定要谨慎,谨慎,再谨慎!!!
如果我们的业务对数据的要求是可以恢复的,我们就要使用逻辑删除操做,一般我们就在数据库种加一个字段is_deleted
物理删除
:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据逻辑删除
:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录- 使用场景:可以进行数据恢复
@TableName("t_user") public class User { // 处理主键相关的字段名不一致 以及设置主键自增策略 @TableId(value = "id", type = IdType.AUTO) private Long id; // 在mp种默认的设置可以将数据库中的字段名xxx_xxx转化为相应的驼峰命名 @TableField(value = "user_name") private String userName; @TableField(value = "age") private Integer age; @TableField(value = "email") private String email; // 逻辑删除 0 标识未删除 1表示删除了 @TableLogic private Integer isDeleted; }
注意:
当我们使用了逻辑删除则删除操作就变成了修改操作
测试删除功能,真正执行的是修改
UPDATE t_user SET is_deleted=1 WHERE id=? AND is_deleted=0
测试查询功能,被逻辑删除的数据默认不会被查询
SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0
二、条件构造器Wrapper
Wrapper在MyBatis-plus里面主要分为两种:一种适用于查询的 QueryWrapper;一种是用于修改的UpdateWrapper
【继承关系】
Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : 查询条件封装
- UpdateWrapper : Update 条件封装
- AbstractLambdaWrapper : 使用Lambda 语法
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
这里主要就是介绍QueryWrapper和UpdateWrapper
2.1 QueryWrapper
上图是BaseMapper里面的关于参数是Warpper的方法,下面我们一一介绍:
2.1.1 组装查询条件selectCount
/** * 测试wraper封装查询条件1 * * @throws Exception */ @Test public void testQuery5() { QueryWrapper<User> queryWrapper = new QueryWrapper<User>(); // sql: SELECT COUNT( * ) FROM t_user WHERE is_deleted=0 AND (age BETWEEN ? AND // ?) queryWrapper.between("age", 18, 23); Long list = userMapper.selectCount(queryWrapper); System.out.println(list); }
2.1.2 组装查询条件selectList
/** * 测试wraper封装查询条件2 * * @throws Exception */ @Test public void testQuery9() { QueryWrapper<User> queryWrapper = new QueryWrapper<User>(); // SELECT id,user_name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND // (email IS NOT NULL AND email LIKE ?) queryWrapper.isNotNull("email").like("email", "163.com"); List<User> list = userMapper.selectList(queryWrapper); list.forEach(System.out::println); }
2.1.3 组装查询参数selectMaps
/** * 测试wraper封装查询条件3 * * @throws Exception */ @Test public void testQuery10() { QueryWrapper<User> queryWrapper = new QueryWrapper<User>(); // SELECT age,email,user_name FROM t_user WHERE is_deleted=0 queryWrapper.select("age", "email", "user_name"); List<Map<String, Object>> list = userMapper.selectMaps(queryWrapper); list.forEach(System.out::println); }
2.1.4 组装查询selectOne
/** * 测试wraper封装查询条件4 * * @throws Exception */ @Test public void testQuery11() { QueryWrapper<User> queryWrapper = new QueryWrapper<User>(); // SELECT age,email,user_name FROM t_user WHERE is_deleted=0 AND (user_name = ?) queryWrapper.select("age", "email", "user_name"); queryWrapper.eq("user_name", "test"); User list = userMapper.selectOne(queryWrapper); System.out.println(list); }
根据上述的方法,我们可以很轻松地观察到,querywrapper里面封装了许多的方法就是用于设置我们的查询条件,而且比较通俗易懂,比如ge大于等于
,between介于
等等,这里只演示这些,剩下的自己在开发中用到继续摸索。
2.1.5 调整条件优先级
注意:
这里说明下如何调整参数的优先级
,我们都知道在sql中可以通过()完成查询条件的优先级提升,那么在MP中如何操做???
【代码演示】
通过and方法,利用lambda表达式实现
@Test public void testQuery7() throws Exception { User user = new User(); // 将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改 user.setEmail("modifyTest@gogel.com"); QueryWrapper<User> queryWrapper = new QueryWrapper<User>(); // UPDATE t_user SET age=?, email=? WHERE (user_name LIKE ? AND (age > ? OR email IS NULL)) queryWrapper.like("user_name", "a") .and( i -> i.ge("age", 20) .or() .isNull("email")); List<User> list = userMapper.selectList(queryWrapper); list.forEach(System.out::println); }
2.1.6 实现子查询inSql
@GetMapping("/queryZI") public List<User> queryZI() { // SELECT id,user_name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (id IN ( SELECT id FROM t_user WHERE id <=100)) QueryWrapper<User> queryWrapper = new QueryWrapper<User>(); queryWrapper.inSql("id", " SELECT id FROM t_user WHERE id <=100"); List<User> maps = userMapper.selectList(queryWrapper); return maps; }
2.2 UpdateWrapper
2.2.1 组装修改条件update
@Test public void testUpdate2() throws Exception { User user = new User(); user.setEmail("modifyTest@gogel.com"); UpdateWrapper<User> updateWrapper = new UpdateWrapper<User>(); updateWrapper.ge("age", 20).like("user_name", "a").or().isNull("email"); /** * 根据 whereEntity 条件,更新记录 * * @param entity 实体对象 (set 条件值,可以为 null) 修改的参数 * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)条件参数 */ int row = userMapper.update(user, updateWrapper); log.info("删除数据={}条", row); }
三、MyBatis-Plus分页插件
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
3.1 实现步骤
1️⃣添加配置类
/** * MybatisPlus的分页插件配置类 * @author wangruoxian * */ @Configuration @MapperScan("com.wei.mapper") public class MyBatisPlusPageConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
2️⃣ 测试
@GetMapping("/page") public IPage<User> pageList(){ Page<User> page = new Page<User>(1,3); // QueryWrapper<User> queryWrapper = new QueryWrapper<User>(); // // 当前页码 // page.setCurrent(1); // // 每页显示的条数 // page.setSize(5); IPage<User> selectPage = userMapper.selectPage(page, null); log.info("当前页数={}",selectPage.getCurrent()); log.info("总页数={}",selectPage.getPages()); selectPage.getRecords().forEach(System.out::println); log.info("每页大小={}",selectPage.getSize()); log.info("总数据条数={}",selectPage.getTotal()); return selectPage; }
四、通用枚举
表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来实现
4.1 数据库表添加字段sex
4.2 配置扫描通用枚举
# 配置扫描通用枚举 type-enums-package: com.wei.enums
4.3 新建枚举类
@Getter public enum SexEnum { MALE(0,"男"), FEMALE(1,"女"); @EnumValue private Integer sexCode; private String sexValue; private SexEnum(Integer sexCode, String sexValue) { this.sexCode = sexCode; this.sexValue = sexValue; } }
4.3.1 @EnumValue注解
通用的枚举类注解,将数据库字段映射成实体类的枚举类型成员变量
将枚举改成成员变量和数据库的字段映射起来,根据数据库字段的值找到对应枚举的对象
4.4 测试
五、多数据源
适用于多种场景:纯粹多库、 读写分离、 一主多从、 混合模式等
,目前我们就来模拟一个纯粹多库的一个场景,其他场景类似
场景说明:
我们创建两个库,分别为:mybatis_plus(以前的库不动)与mybatis_plus_1(新建),将
mybatis_plus库的product表移动到mybatis_plus_1库,这样每个库一张表,通过一个测试用例
分别获取用户数据与商品数据,如果获取到说明多库模拟成功
5.1 创建数据库及表
CREATE DATABASE `mybatis_plus_slave` use `mybatis_plus_slave`; CREATE TABLE t_product ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称', price INT(11) DEFAULT 0 COMMENT '价格', version INT(11) DEFAULT 0 COMMENT '乐观锁版本号', PRIMARY KEY (id) ); INSERT INTO product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
5.2 引入依赖
<!-- 引入依赖 多数据源 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.3.1</version> </dependency>
5.3 配置多数据源
注意
注释掉之前的配置 ,也可以新建一个项目进行测试
spring: # 配置数据源信息 datasource: dynamic: # 设置默认的数据源或者数据源组,默认值即为master primary: master # 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源 strict: false datasource: master: url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 slave_1: url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&useSSL=false driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456
5.4 创建新库中Product对应的类
此处省略domain,mapper,service,serviceImpl的创建
【controller层】
@RestController @RequestMapping("/product") @Api(value = "测试 ProductController 的接口", tags = "产品管理相关的接口", description = "product产品测试接口") public class ProductController { @Autowired private IProductService IProductService; @GetMapping("/list") // @ApiOperation("查询所有商品的接口") public List<Product> queryAllProduct(){ return IProductService.list(); } }
到此这篇关于MyBatis-Plus 条件查询器的实现的文章就介绍到这了,更多相关MyBatis-Plus 条件查询器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!