MyBatis-plus 是一款 Mybatis 增强工具,用于简化开发,提高效率。下文使用缩写 mp来简化表示 MyBatis-plus,本文主要介绍 mp 整合 Spring Boot 的使用。
(5条消息) mybatis-plus用法(二)_渣娃工程师的博客-CSDN博客
1.创建一个Spring Boot项目。
2.导入依赖
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.4.RELEASE
com.example
mybatis-plus
0.0.1-SNAPSHOT
mybatis-plus
1.8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-configuration-processor
com.baomidou
mybatis-plus-boot-starter
3.4.2
mysql
mysql-connector-java
runtime
org.projectlombok
lombok
org.springframework.boot
spring-boot-maven-plugin
3.配置数据库
# application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/yogurt?serverTimezone=Asia/Shanghai
username: root
password: root
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启SQL语句打印
4.创建一个实体类
package com.example.mp.po;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
private Long managerId;
private LocalDateTime createTime;
}
5.创建一个mapper接口
package com.example.mp.mappers;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp.po.User;
public interface UserMapper extends BaseMapper { }
6.在SpringBoot启动类上配置mapper接口的扫描路径
package com.example.mp;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconp.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.mp.mappers")
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
7.在数据库中创建表
DROP TABLE IF EXISTS user;
CREATE TABLE user (
id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键',
name VARCHAR(30) DEFAULT NULL COMMENT '姓名',
age INT(11) DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
manager_id BIGINT(20) DEFAULT NULL COMMENT '直属上级id',
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
CONSTRAINT manager_fk FOREIGN KEY(manager_id) REFERENCES user (id)
) ENGINE=INNODB CHARSET=UTF8;
INSERT INTO user (id, name, age ,email, manager_id, create_time) VALUES
(1, '大BOSS', 40, '[email protected]', NULL, '2021-03-22 09:48:00'),
(2, '李经理', 40, '[email protected]', 1, '2021-01-22 09:48:00'),
(3, '黄主管', 40, '[email protected]', 2, '2021-01-22 09:48:00'),
(4, '吴组长', 40, '[email protected]', 2, '2021-02-22 09:48:00'),
(5, '小菜', 40, '[email protected]', 2, '2021-02-22 09:48:00')
8.编写一个SpringBoot测试类
package com.example.mp;
import com.example.mp.mappers.UserMapper;
import com.example.mp.po.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import static org.junit.Assert.*;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleTest {
@Autowired
private UserMapper mapper;
@Test
public void testSelect() {
List list = mapper.selectList(null);
assertEquals(5, list.size());
list.forEach(System.out::println);
}
}
准备工作完成,数据库情况如下:
运行测试类
可以看到,针对单表的基本CRUD操作,只需要创建好实体类,并创建一个继承自BaseMapper
的接口即可,可谓非常简洁。并且,我们注意到,User
类中的managerId
,createTime
属性,自动和数据库表中的manager_id
,create_time
对应了起来,这是因为mp自动做了数据库下划线命名,到Java类的驼峰命名之间的转化。
mp一共提供了8个注解,这些注解是用在Java的实体类上面的。
@TableName
注解在类上,指定类和数据库表的映射关系。实体类的类名(转成小写后)和数据库表名相同时,可以不指定该注解。
@TableId
注解在实体类的某一字段上,表示这个字段对应数据库表的主键。当主键名为id时(表中列名为id,实体类中字段名为id),无需使用该注解显式指定主键,mp会自动关联。若类的字段名和表的列名不一致,可用value
属性指定表的列名。另,这个注解有个重要的属性type
,用于指定主键策略。
@TableField
注解在某一字段上,指定Java实体类的字段和数据库表的列的映射关系。这个注解有如下几个应用场景。
排除非表字段
若Java实体类中某个字段,不对应表中的任何列,它只是用于保存一些额外的,或组装后的数据,则可以设置exist
属性为false
,这样在对实体对象进行插入时,会忽略这个字段。排除非表字段也可以通过其他方式完成,如使用static
或transient
关键字,但个人觉得不是很合理,不做赘述
字段验证策略
通过insertStrategy
,updateStrategy
,whereStrategy
属性进行配置,可以控制在实体对象进行插入,更新,或作为WHERE条件时,对象中的字段要如何组装到SQL语句中。
字段填充策略
通过fill
属性指定,字段为空时会进行自动填充
@Version
乐观锁注解
@EnumValue
注解在枚举字段上
@TableLogic
逻辑删除
KeySequence
序列主键策略(oracle
)
InterceptorIgnore
插件过滤规则
mp封装了一些最基础的CRUD方法,只需要直接继承mp提供的接口,无需编写任何SQL,即可食用。mp提供了两套接口,分别是Mapper CRUD接口和Service CRUD接口。并且mp还提供了条件构造器Wrapper
,可以方便地组装SQL语句中的WHERE条件。
Mapper CRUD接口
只需定义好实体类,然后创建一个接口,继承mp提供的BaseMapper
,即可食用。mp会在mybatis启动时,自动解析实体类和表的映射关系,并注入带有通用CRUD方法的mapper。BaseMapper
里提供的方法,部分列举如下:
insert(T entity)
插入一条记录
deleteById(Serializable id)
根据主键id删除一条记录
delete(Wrapper
根据条件构造器wrapper进行删除
selectById(Serializable id)
根据主键id进行查找
selectBatchIds(Collection idList)
根据主键id进行批量查找
selectByMap(Map
根据map中指定的列名和列值进行等值匹配查找
selectMaps(Wrapper
根据 wrapper 条件,查询记录,将查询结果封装为一个Map,Map的key为结果的列,value为值
selectList(Wrapper
根据条件构造器wrapper
进行查询
update(T entity, Wrapper
根据条件构造器wrapper
进行更新
updateById(T entity)
...
下面讲解几个比较特别的方法
selectMaps
BaseMapper
接口还提供了一个selectMaps
方法,这个方法会将查询结果封装为一个Map,Map的key为结果的列,value为值
该方法的使用场景如下:
只查部分列
当某个表的列特别多,而SELECT的时候只需要选取个别列,查询出的结果也没必要封装成Java实体类对象时(只查部分列时,封装成实体后,实体对象中的很多属性会是null),则可以用selectMaps
,获取到指定的列后,再自行进行处理即可
比如
@Test
public void test3() {
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.select("id","name","email").likeRight("name","黄");
List
进行数据统计
比如
// 按照直属上级进行分组,查询每组的平均年龄,最大年龄,最小年龄
/**
select avg(age) avg_age ,min(age) min_age, max(age) max_age from user group by manager_id having sum(age) < 500;
**/
@Test
public void test3() {
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.select("manager_id", "avg(age) avg_age", "min(age) min_age", "max(age) max_age")
.groupBy("manager_id").having("sum(age) < {0}", 500);
List
selectObjs
只会返回第一个字段(第一列)的值,其他字段会被舍弃
比如
@Test
public void test3() {
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.select("id", "name").like("name", "黄");
List
得到的结果,只封装了第一列的id
selectCount
查询满足条件的总数,注意,使用这个方法,不能调用QueryWrapper
的select
方法设置要查询的列了。这个方法会自动添加select count(1)
比如
@Test
public void test3() {
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.like("name", "黄");
Integer count = userMapper.selectCount(wrapper);
System.out.println(count);
}
Service CRUD 接口
另外一套CRUD是Service层的,只需要编写一个接口,继承IService
,并创建一个接口实现类,即可食用。(这个接口提供的CRUD方法,和Mapper接口提供的功能大同小异,比较明显的区别在于IService
支持了更多的批量化操作,如saveBatch
,saveOrUpdateBatch
等方法。
食用示例如下
1.首先,新建一个接口,继承IService
package com.example.mp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mp.po.User;
public interface UserService extends IService {
}
2.创建这个接口的实现类,并继承ServiceImpl
,最后打上@Service
注解,注册到Spring容器中,即可食用
package com.example.mp.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mp.mappers.UserMapper;
import com.example.mp.po.User;
import com.example.mp.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl implements UserService { }
3.测试代码
package com.example.mp;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.example.mp.po.User;
import com.example.mp.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest {
@Autowired
private UserService userService;
@Test
public void testGetOne() {
LambdaQueryWrapper wrapper = Wrappers.lambdaQuery();
wrapper.gt(User::getAge, 28);
User one = userService.getOne(wrapper, false); // 第二参数指定为false,使得在查到了多行记录时,不抛出异常,而返回第一条记录
System.out.println(one);
}
}
4.结果
另,IService
也支持链式调用,代码写起来非常简洁,查询示例如下
@Test
public void testChain() {
List list = userService.lambdaQuery()
.gt(User::getAge, 39)
.likeRight(User::getName, "王")
.list();
list.forEach(System.out::println);
}
更新示例如下
@Test
public void testChain() {
userService.lambdaUpdate()
.gt(User::getAge, 39)
.likeRight(User::getName, "王")
.set(User::getEmail, "[email protected]")
.update();
}
删除示例如下
@Test
public void testChain() {
userService.lambdaUpdate()
.like(User::getName, "青蛙")
.remove();
}
mp让我觉得极其方便的一点在于其提供了强大的条件构造器Wrapper
,可以非常方便的构造WHERE条件。条件构造器主要涉及到3个类,AbstractWrapper
。QueryWrapper
,UpdateWrapper
,它们的类关系如下
在AbstractWrapper
中提供了非常多的方法用于构建WHERE条件,而QueryWrapper
针对SELECT
语句,提供了select()
方法,可自定义需要查询的列,而UpdateWrapper
针对UPDATE
语句,提供了set()
方法,用于构造set
语句。条件构造器也支持lambda表达式,写起来非常舒爽。
下面对AbstractWrapper
中用于构建SQL语句中的WHERE条件的方法进行部分列举
eq
:equals,等于
allEq
:all equals,全等于
ne
:not equals,不等于
gt
:greater than ,大于 >
ge
:greater than or equals,大于等于≥
lt
:less than,小于<
le
:less than or equals,小于等于≤
between
:相当于SQL中的BETWEEN
notBetween
like
:模糊匹配。like("name","黄")
,相当于SQL的name like '%黄%'
likeRight
:模糊匹配右半边。likeRight("name","黄")
,相当于SQL的name like '黄%'
likeLeft
:模糊匹配左半边。likeLeft("name","黄")
,相当于SQL的name like '%黄'
notLike
:notLike("name","黄")
,相当于SQL的name not like '%黄%'
isNull
isNotNull
in
and
:SQL连接符AND
or
:SQL连接符OR
apply
:用于拼接SQL,该方法可用于数据库函数,并可以动态传参
.......
使用示例
下面通过一些具体的案例来练习条件构造器的使用。(使用前文创建的user
表)
// 案例先展示需要完成的SQL语句,后展示Wrapper的写法
// 1. 名字中包含佳,且年龄小于25
// SELECT * FROM user WHERE name like '%佳%' AND age < 25
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.like("name", "佳").lt("age", 25);
List users = userMapper.selectList(wrapper);
// 下面展示SQL时,仅展示WHERE条件;展示代码时, 仅展示Wrapper构建部分
// 2. 姓名为黄姓,且年龄大于等于20,小于等于40,且email字段不为空
// name like '黄%' AND age BETWEEN 20 AND 40 AND email is not null
wrapper.likeRight("name","黄").between("age", 20, 40).isNotNull("email");
// 3. 姓名为黄姓,或者年龄大于等于40,按照年龄降序排列,年龄相同则按照id升序排列
// name like '黄%' OR age >= 40 order by age desc, id asc
wrapper.likeRight("name","黄").or().ge("age",40).orderByDesc("age").orderByAsc("id");
// 4.创建日期为2021年3月22日,并且直属上级的名字为李姓
// date_format(create_time,'%Y-%m-%d') = '2021-03-22' AND manager_id IN (SELECT id FROM user WHERE name like '李%')
wrapper.apply("date_format(create_time, '%Y-%m-%d') = {0}", "2021-03-22") // 建议采用{index}这种方式动态传参, 可防止SQL注入
.inSql("manager_id", "SELECT id FROM user WHERE name like '李%'");
// 上面的apply, 也可以直接使用下面这种方式做字符串拼接,但当这个日期是一个外部参数时,这种方式有SQL注入的风险
wrapper.apply("date_format(create_time, '%Y-%m-%d') = '2021-03-22'");
// 5. 名字为王姓,并且(年龄小于40,或者邮箱不为空)
// name like '王%' AND (age < 40 OR email is not null)
wrapper.likeRight("name", "王").and(q -> q.lt("age", 40).or().isNotNull("email"));
// 6. 名字为王姓,或者(年龄小于40并且年龄大于20并且邮箱不为空)
// name like '王%' OR (age < 40 AND age > 20 AND email is not null)
wrapper.likeRight("name", "王").or(
q -> q.lt("age",40)
.gt("age",20)
.isNotNull("email")
);
// 7. (年龄小于40或者邮箱不为空) 并且名字为王姓
// (age < 40 OR email is not null) AND name like '王%'
wrapper.nested(q -> q.lt("age", 40).or().isNotNull("email"))
.likeRight("name", "王");
// 8. 年龄为30,31,34,35
// age IN (30,31,34,35)
wrapper.in("age", Arrays.asList(30,31,34,35));
// 或
wrapper.inSql("age","30,31,34,35");
// 9. 年龄为30,31,34,35, 返回满足条件的第一条记录
// age IN (30,31,34,35) LIMIT 1
wrapper.in("age", Arrays.asList(30,31,34,35)).last("LIMIT 1");
// 10. 只选出id, name 列 (QueryWrapper 特有)
// SELECT id, name FROM user;
wrapper.select("id", "name");
// 11. 选出id, name, age, email, 等同于排除 manager_id 和 create_time
// 当列特别多, 而只需要排除个别列时, 采用上面的方式可能需要写很多个列, 可以采用重载的select方法,指定需要排除的列
wrapper.select(User.class, info -> {
String columnName = info.getColumn();
return !"create_time".equals(columnName) && !"manager_id".equals(columnName);
});
Condition
条件构造器的诸多方法中,均可以指定一个boolean
类型的参数condition
,用来决定该条件是否加入最后生成的WHERE语句中,比如
String name = "黄"; // 假设name变量是一个外部传入的参数
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.like(StringUtils.hasText(name), "name", name);
// 仅当 StringUtils.hasText(name) 为 true 时, 会拼接这个like语句到WHERE中
// 其实就是对下面代码的简化
if (StringUtils.hasText(name)) {
wrapper.like("name", name);
}
实体对象作为条件
调用构造函数创建一个Wrapper
对象时,可以传入一个实体对象。后续使用这个Wrapper
时,会以实体对象中的非空属性,构建WHERE条件(默认构建等值匹配的WHERE条件,这个行为可以通过实体类里各个字段上的@TableField
注解中的condition
属性进行改变)
示例如下
@Test
public void test3() {
User user = new User();
user.setName("黄主管");
user.setAge(28);
QueryWrapper wrapper = new QueryWrapper<>(user);
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
执行结果如下。可以看到,是根据实体对象中的非空属性,进行了等值匹配查询。
若希望针对某些属性,改变等值匹配的行为,则可以在实体类中用@TableField
注解进行配置,示例如下
package com.example.mp.po;
import com.baomidou.mybatisplus.annotation.SqlCondition;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long id;
@TableField(condition = SqlCondition.LIKE) // 配置该字段使用like进行拼接
private String name;
private Integer age;
private String email;
private Long managerId;
private LocalDateTime createTime;
}
运行下面的测试代码
@Test
public void test3() {
User user = new User();
user.setName("黄");
QueryWrapper wrapper = new QueryWrapper<>(user);
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
从下图得到的结果来看,对于实体对象中的name
字段,采用了like
进行拼接
@TableField
中配置的condition
属性实则是一个字符串,SqlCondition
类中预定义了一些字符串以供选择
package com.baomidou.mybatisplus.annotation;
public class SqlCondition {
//下面的字符串中, %s 是占位符, 第一个 %s 是列名, 第二个 %s 是列的值
public static final String EQUAL = "%s=#{%s}";
public static final String NOT_EQUAL = "%s<>#{%s}";
public static final String LIKE = "%s LIKE CONCAT('%%',#{%s},'%%')";
public static final String LIKE_LEFT = "%s LIKE CONCAT('%%',#{%s})";
public static final String LIKE_RIGHT = "%s LIKE CONCAT(#{%s},'%%')";
}
SqlCondition
中提供的配置比较有限,当我们需要<
或>
等拼接方式,则需要自己定义。比如
package com.example.mp.po;
import com.baomidou.mybatisplus.annotation.SqlCondition;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long id;
@TableField(condition = SqlCondition.LIKE)
private String name;
@TableField(condition = "%s > #{%s}") // 这里相当于大于, 其中 > 是字符实体
private Integer age;
private String email;
private Long managerId;
private LocalDateTime createTime;
}
测试如下
@Test
public void test3() {
User user = new User();
user.setName("黄");
user.setAge(30);
QueryWrapper wrapper = new QueryWrapper<>(user);
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
从下图得到的结果,可以看出,name
属性是用like
拼接的,而age
属性是用>
拼接的
allEq方法
allEq方法传入一个map
,用来做等值匹配
@Test
public void test3() {
QueryWrapper wrapper = new QueryWrapper<>();
Map param = new HashMap<>();
param.put("age", 40);
param.put("name", "黄飞飞");
wrapper.allEq(param);
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
当allEq方法传入的Map中有value为null
的元素时,默认会设置为is null
@Test
public void test3() {
QueryWrapper wrapper = new QueryWrapper<>();
Map param = new HashMap<>();
param.put("age", 40);
param.put("name", null);
wrapper.allEq(param);
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
若想忽略map中value为null
的元素,可以在调用allEq时,设置参数boolean null2IsNull
为false
@Test
public void test3() {
QueryWrapper wrapper = new QueryWrapper<>();
Map param = new HashMap<>();
param.put("age", 40);
param.put("name", null);
wrapper.allEq(param, false);
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
若想要在执行allEq时,过滤掉Map中的某些元素,可以调用allEq的重载方法allEq(BiPredicate
@Test
public void test3() {
QueryWrapper wrapper = new QueryWrapper<>();
Map param = new HashMap<>();
param.put("age", 40);
param.put("name", "黄飞飞");
wrapper.allEq((k,v) -> !"name".equals(k), param); // 过滤掉map中key为name的元素
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
lambda条件构造器
lambda条件构造器,支持lambda表达式,可以不必像普通条件构造器一样,以字符串形式指定列名,它可以直接以实体类的方法引用来指定列。示例如下
@Test
public void testLambda() {
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, "黄").lt(User::getAge, 30);
List users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
像普通的条件构造器,列名是用字符串的形式指定,无法在编译期进行列名合法性的检查,这就不如lambda条件构造器来的优雅。
另外,还有个链式lambda条件构造器,使用示例如下
@Test
public void testLambda() {
LambdaQueryChainWrapper chainWrapper = new LambdaQueryChainWrapper<>(userMapper);
List users = chainWrapper.like(User::getName, "黄").gt(User::getAge, 30).list();
users.forEach(System.out::println);
}
上面介绍的都是查询操作,现在来讲更新和删除操作。
BaseMapper
中提供了2个更新方法
updateById(T entity)
根据入参entity
的id
(主键)进行更新,对于entity
中非空的属性,会出现在UPDATE语句的SET后面,即entity
中非空的属性,会被更新到数据库,示例如下
@RunWith(SpringRunner.class)
@SpringBootTest
public class UpdateTest {
@Autowired
private UserMapper userMapper;
@Test
public void testUpdate() {
User user = new User();
user.setId(2L);
user.setAge(18);
userMapper.updateById(user);
}
}
update(T entity, Wrapper
根据实体entity
和条件构造器wrapper
进行更新,示例如下
@Test
public void testUpdate2() {
User user = new User();
user.setName("王三蛋");
LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>();
wrapper.between(User::getAge, 26,31).likeRight(User::getName,"吴");
userMapper.update(user, wrapper);
}
额外演示一下,把实体对象传入Wrapper
,即用实体对象构造WHERE条件的案例
@Test
public void testUpdate3() {
User whereUser = new User();
whereUser.setAge(40);
whereUser.setName("王");
LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(whereUser);
User user = new User();
user.setEmail("[email protected]");
user.setManagerId(10L);
userMapper.update(user, wrapper);
}
注意到我们的User类中,对name
属性和age
属性进行了如下的设置
@Data
public class User {
private Long id;
@TableField(condition = SqlCondition.LIKE)
private String name;
@TableField(condition = "%s > #{%s}")
private Integer age;
private String email;
private Long managerId;
private LocalDateTime createTime;
}
执行结果
再额外演示一下,链式lambda条件构造器的使用
@Test
public void testUpdate5() {
LambdaUpdateChainWrapper wrapper = new LambdaUpdateChainWrapper<>(userMapper);
wrapper.likeRight(User::getEmail, "share")
.like(User::getName, "飞飞")
.set(User::getEmail, "[email protected]")
.update();
}
反思
由于BaseMapper
提供的2个更新方法都是传入一个实体对象去执行更新,这在需要更新的列比较多时还好,若想要更新的只有那么一列,或者两列,则创建一个实体对象就显得有点麻烦。针对这种情况,UpdateWrapper
提供有set
方法,可以手动拼接SQL中的SET语句,此时可以不必传入实体对象,示例如下
@Test
public void testUpdate4() {
LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>();
wrapper.likeRight(User::getEmail, "share").set(User::getManagerId, 9L);
userMapper.update(null, wrapper);
}
BaseMapper
一共提供了如下几个用于删除的方法
deleteById
根据主键id进行删除
deleteBatchIds
根据主键id进行批量删除
deleteByMap
根据Map进行删除(Map中的key为列名,value为值,根据列和值进行等值匹配)
delete(Wrapper
根据条件构造器Wrapper
进行删除
与前面查询和更新的操作大同小异,不做赘述
当mp提供的方法还不能满足需求时,则可以自定义SQL。
原生mybatis
示例如下
注解方式
package com.example.mp.mappers;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp.po.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @Author yogurtzzz
* @Date 2021/3/18 11:21
**/
public interface UserMapper extends BaseMapper {
@Select("select * from user")
List selectRaw();
}
xml方式
package com.example.mp.mappers;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp.po.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UserMapper extends BaseMapper {
List selectRaw();
}
使用xml时,若xml文件与mapper接口文件不在同一目录下,则需要在application.yml
中配置mapper.xml的存放路径
mybatis-plus:
mapper-locations: /mappers/*
若有多个地方存放mapper,则用数组形式进行配置
mybatis-plus:
mapper-locations:
- /mappers/*
- /com/example/mp/*
测试代码如下
@Test
public void testCustomRawSql() {
List users = userMapper.selectRaw();
users.forEach(System.out::println);
}
结果
mybatis-plus
也可以使用mp提供的Wrapper条件构造器,来自定义SQL
示例如下
注解方式
package com.example.mp.mappers;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.example.mp.po.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UserMapper extends BaseMapper {
// SQL中不写WHERE关键字,且固定使用${ew.customSqlSegment}
@Select("select * from user ${ew.customSqlSegment}")
List findAll(@Param(Constants.WRAPPER)Wrapper wrapper);
}
xml方式
package com.example.mp.mappers;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp.po.User;
import java.util.List;
public interface UserMapper extends BaseMapper {
List findAll(Wrapper wrapper);
}
BaseMapper
中提供了2个方法进行分页查询,分别是selectPage
和selectMapsPage
,前者会将查询的结果封装成Java实体对象,后者会封装成Map
。分页查询的食用示例如下
1. 创建mp的分页拦截器,注册到Spring容器中
package com.example.mp.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
/** 新版mp **/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
/** 旧版mp 用 PaginationInterceptor **/
}
2. 执行分页查询
@Test
public void testPage() {
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.ge(User::getAge, 28);
// 设置分页信息, 查第3页, 每页2条数据
Page page = new Page<>(3, 2);
// 执行分页查询
Page userPage = userMapper.selectPage(page, wrapper);
System.out.println("总记录数 = " + userPage.getTotal());
System.out.println("总页数 = " + userPage.getPages());
System.out.println("当前页码 = " + userPage.getCurrent());
// 获取分页查询结果
List records = userPage.getRecords();
records.forEach(System.out::println);
}
3. 结果
4. 其他
注意到,分页查询总共发出了2次SQL,一次查总记录数,一次查具体数据。若希望不查总记录数,仅查分页结果。可以通过Page
的重载构造函数,指定isSearchCount
为false
即可
public Page(long current, long size, boolean isSearchCount)
在实际开发中,可能遇到多表联查的场景,此时BaseMapper
中提供的单表分页查询的方法无法满足需求,需要自定义SQL,示例如下(使用单表查询的SQL进行演示,实际进行多表联查时,修改SQL语句即可)
1. 在mapper接口中定义一个函数,接收一个Page对象为参数,并编写自定义SQL
// 这里采用纯注解方式。当然,若SQL比较复杂,建议还是采用XML的方式
@Select("SELECT * FROM user ${ew.customSqlSegment}")
Page selectUserPage(Page page, @Param(Constants.WRAPPER) Wrapper wrapper);
2. 执行查询
@Test
public void testPage2() {
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.ge(User::getAge, 28).likeRight(User::getName, "王");
Page page = new Page<>(3,2);
Page userPage = userMapper.selectUserPage(page, wrapper);
System.out.println("总记录数 = " + userPage.getTotal());
System.out.println("总页数 = " + userPage.getPages());
userPage.getRecords().forEach(System.out::println);
}
3. 结果