MyBatis Plus
干掉
SQL
!!!
是一个MyBatis
的增强工具,在MyBatis
基础上只做增强,不做改变,可以在不编写SQL
语句的情况下,对单表完成各种操作
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
mybatis plus
提供了一个通用的增删改查的Mapper
,可以通过自定义Mapper
接口继承BaseMapper<T>
完成基础的增删改查
BaseMapper
使用:
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
没有复杂条件的增删改:
@Test
void insert() {
System.out.println("\n\n\n\n插入前");
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
User user = new User();
user.setName("admin3");
user.setEmail("6666@qqq.qqq");
user.setAge(11);
System.out.println("mapper.insert(user) = " + mapper.insert(user));
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
System.out.println("插入后\n\n\n\n");
}
@Test
void updateByPrimaryKey() {
System.out.println("\n\n\n\n修改前");
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
User user = new User();
user.setId(1580877194695786497L);
user.setName("admin");
user.setEmail("6666@qqq.qqq");
user.setAge(101);
System.out.println("mapper.updateById(user) = " + mapper.updateById(user));
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
System.out.println("修改后\n\n\n\n");
}
@Test
void deleteByPrimaryKey() {
System.out.println("\n\n\n\n删除前");
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
User user = new User();
user.setId(1580878736567980034L);
user.setName("admin");
user.setEmail("6666@qqq.qqq");
user.setAge(101);
Map<String, Object> map = new HashMap<>();
map.put("id", 1580879257869725698L);
map.put("name", "admin3");
// 通过Map进行删除
mapper.deleteByMap(map);
// 通过对象中的主键进行删除
mapper.deleteById(user);
// 直接指定id
System.out.println("mapper.deleteById(1580877194695786497L) = " + mapper.deleteById(1580877194695786497L));
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
System.out.println("删除后\n\n\n\n");
}
@Test
void deleteBatch() {
System.out.println("\n\n\n\n批量删除前");
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
System.out.println("mapper.deleteBatchIds(List.of()) = "
+ mapper.deleteBatchIds(List.of(1580880626341011458L, 1580880653503369218L, 1580880677410893825L,
1580880702023098369L, 1580880729009229825L, 1580880758923018242L)));
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
System.out.println("批量删除后\n\n\n\n");
}
没有复杂条件的查询
@Test
void simpleSelect() {
System.out.println("mapper.selectById(1) = " + mapper.selectById(1));
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
System.out.println("mapper.selectCount(null) = " + mapper.selectCount(null));
System.out.println("mapper.selectMaps(null) = " + mapper.selectMaps(null));
System.out.println("mapper.selectObjs(null) = " + mapper.selectObjs(null));
System.out.println("mapper.selectBatchIds(List.of(2, 3, 5)) = " + mapper.selectBatchIds(List.of(2, 3, 5)));
HashMap<String, Object> columnMap = new HashMap<>();
columnMap.put("name", "Sandy");
System.out.println("mapper.selectByMap(columnMap) = " + mapper.selectByMap(columnMap));
System.out.println("mapper.exists(null) = " + mapper.exists(null));
}
IService
一个通用的Service
接口,MyBatis Plus
提供了一个实现类为:ServiceImpl<M extends BaseMapper<T>, T>
例如:
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
}
Service
简单的通用增删改查的使用,Mapper
与Service
对应关系为:
get 查询单行
remove 删除
list 查询集合
@Test
void simpleSelect() {
System.out.println("service.count() = " + service.count());
System.out.println("service.list() = " + service.list());
System.out.println("service.listMaps() = " + service.listMaps());
System.out.println("service.listObjs() = " + service.listObjs());
System.out.println("service.listByIds(List.of(1, 3, 5)) = " + service.listByIds(List.of(1, 3, 5)));
System.out.println("service.getById(1580903310957416450L) = " + service.getById(1580903310957416450L));
}
@Test
void insert() {
User user = new User();
user.setName("姓名");
user.setEmail("name@kjk.aa");
user.setAge(56);
service.save(user);
}
@Test
void insertBatch() {
System.out.println("service.list() = " + service.list());
service.saveBatch(List.of(new User(null, "ming", "a@a", 11),
new User(null, "afasd", "g@a", 152), new User(null, "cfe", "a@a", 211)));
System.out.println("service.list() = " + service.list());
}
// 第二个参数为数量,从0开始
@Test
void insertBatchCount() {
System.out.println("service.list() = " + service.list());
service.saveBatch(List.of(new User(null, "1111", "a@a", 11),
new User(null, "2222", "g@a", 152), new User(null, "3333", "a@a", 211)), 1);
System.out.println("service.list() = " + service.list());
}
// 根据主键进行插入或者更新某条记录,如果数据库中不存在这个主键,那么将会进行插入;如果存在这个主键,就进行更新这条记录
@Test
void insertUpdate() {
System.out.println("service.list() = " + service.list());
User user = new User(9999L, "9999", "g@a", 152);
service.saveOrUpdate(user);
System.out.println("service.list() = " + service.list());
}
@Test
void insertUpdateBatch() {
System.out.println("service.list() = " + service.list());
service.saveOrUpdateBatch(List.of(new User(1580903310957416450L, "姓名-更新", "name@kjk.aa", 56),
new User(1580903370369757185L, "姓名-sxx", "name@kjk.aa", 56)));
System.out.println("service.list() = " + service.list());
}
@Test
void update() {
System.out.println("service.getById(1580903310957416450L) = " + service.getById(1580903310957416450L));
service.updateById(new User(1580903310957416450L, "姓名-更新-更新", "name@kjk.aa", 56));
System.out.println("service.getById(1580903310957416450L) = " + service.getById(1580903310957416450L));
}
@Test
void updateOther() {
System.out.println("service.list() = " + service.list());
service.updateBatchById(List.of(new User(1580903310957416450L, "姓名0-更新", "name@kjk.aa", 56),
new User(1580903370369757185L, "姓名1-sxx", "name@kjk.aa", 56)));
System.out.println("service.list() = " + service.list());
}
@Test
void delete() {
System.out.println("service.list() = " + service.list());
service.removeById(1580906572498501634L);
System.out.println("service.list() = " + service.list());
service.removeById(new User(1580905782966972420L, null, null, 0));
System.out.println("service.list() = " + service.list());
service.removeByIds(List.of(1580905782966972419L, 1580905782966972418L));
System.out.println("service.list() = " + service.list());
service.removeBatchByIds(List.of(1580905523557658625L, 1580905523549270018L));
System.out.println("service.list() = " + service.list());
HashMap<String, Object> map = new HashMap<>();
map.put("id", 1580905523519909890L);
service.removeByMap(map);
System.out.println("service.list() = " + service.list());
}
配置表名前缀
如果表名的前缀都一样时,可以添加表名的前缀,例如:
mybatis-plus:
global-config:
db-config:
table-prefix: 前缀
常用注解
@TableName("表名")
在类名和表名不一致的情况下,可以在类名上添加此注解指定表名:
@TableId("id名称")
可以在属性上指定id
所在的字段,在id
和表中的id
不一致时,可以在括号中,@TableId(type = IdType.XXX)
可以指定插入主键值的生成策略,默认为雪花算法:
-
AUTO
自动递增 -
ASSIGN_ID
为雪花算法 -
NONE
为默认值,将会使用在配置文件中指定的全局配置 -
可以在
yml
中配置全局的生成策略:-
mybatis-plus: global-config: db-config: table-prefix: t_ id-type: auto
-
@TableField("属性名称")
如果类中的字段和数据库中的字段不一致时,可以通过在属性上添加这个注解并指定
逻辑删除
- 添加一个数据库字段,类型为
integer
或者Boolean
,设置好默认值为0
- 在实体类上添加这个属性,并添加
@TableLogic
注解
这时候再调用删除方法时,只是相当于把这个字段的值设置为了1
条件构造器 wrapper
wrapper
中文为包装
类的结构:
QueryWrapper
的使用:
@Test
void test() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
// 小于
// queryWrapper.lt("id", 3);
// 小于等于
// queryWrapper.le("id", 3);
// 大于
// queryWrapper.gt("id", 2);
// 大于等于
// queryWrapper.ge("id", 3);
// 等于
// queryWrapper.eq("id", 3);
// 不等于
// queryWrapper.ne("id", 2);
// 联合起来写,代表大于2且小于5,且不等于3
// queryWrapper.ge("id", 2)
// .le("id", 5)
// .ne("id", 3);
// between条件
// queryWrapper.between("id", 2, 4);
// like条件
// queryWrapper.like("name", "J%");
// not like
// queryWrapper.notLike("name", "%a%");
// 查询某几个字段
// queryWrapper.select("id", "name");
/*
也可以使用group by,select中也可以写别名
*/
// queryWrapper.groupBy("age");
// queryWrapper.select("age 年龄", "count(*) 数量");
// queryWrapper.select("age as 年龄", "count(*) as 数量");
// in的用法
// queryWrapper.in("id", 2, 3, 4);
// queryWrapper.in("id", List.of(2, 3, 4));
// not in
// queryWrapper.notIn("id", 2, 3, 5);
// queryWrapper.notIn("id", List.of(2, 3, 5));
// 排序
// 正序
// queryWrapper.orderBy(true, true, "id");
// queryWrapper.orderByAsc("id");
// 倒序
// queryWrapper.orderBy(true, true, "id");
// queryWrapper.orderByDesc("id");
// SELECT id, name, email, age, del FROM user WHERE del = 0 AND(id >= ? OR age >= ? AND age <= ?)
// queryWrapper.ge("id", 5)
// .or()
// .ge("age", 25)
// .le("age", 100);
// SELECT id,name,email,age,del FROM user WHERE del=0 AND (id >= ? OR (age >= ? AND age <= ?))
// queryWrapper.ge("id", 5)
// .or(wrapper -> wrapper.ge("age", 25).le("age", 100));
// queryWrapper.isNull("列名").isNotNull("列名")
System.out.println("mapper.selectList(queryWrapper) = " + mapper.selectList(queryWrapper));
}
queryWrapper
也可以用于删除:
@Test
void delete() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age", 100);
mapper.delete(queryWrapper);
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
}
也可以进行子查询:
@Test
void subQuery() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
// SELECT id,name,email,age,del FROM user WHERE del=0 AND (id IN (select id where id > 100))
queryWrapper.inSql("id", "select id where id > 100");
mapper.selectList(queryWrapper);
}
可以使用UpdateWrapper
进行更新:
@Test
void update() {
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("age", 888);
updateWrapper.eq("age", 999);
mapper.update(null, updateWrapper);
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
}
条件组装
例如现在有这么个需求:
- 如果名字不为空,会按照名称进行查询
- 如果最小年龄不为空,同时也会将这个条件加进去
- 如果最大年龄不会空,也会将这个条件加进去
根据这个条件,代码可以表示为:
@Test
void condition() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
String name = null;
Integer minAge = 10, maxAge = 150;
if(name != null) {
queryWrapper.eq("name", name);
}
if(minAge != null) {
queryWrapper.ge("age", minAge);
}
if(maxAge != null) {
queryWrapper.le("age", maxAge);
}
mapper.selectList(queryWrapper);
}
Mybatis plus
提供了条件组装,只有条件为true
时才会组装这条sql
:
@Test
void condition() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
String name = "名称";
Integer minAge = 10, maxAge = 1500;
queryWrapper.eq(name != null, "name", name);
queryWrapper.ge(minAge != null, "age", minAge);
queryWrapper.le(maxAge != null, "age", maxAge);
mapper.selectList(queryWrapper);
}
LambdaQueryWrapper
在之前的条件中,有许多手打的字段名,手打的字段名很容易出错,这个使用可以使用LambdaQueryWrapper<T>
,通常每个方法中都带有SFunction<T, ?> column
类型的参数,在这个参数中可以使用实体类的Lambda
表达式,例如:
@Test
void lambdaQueryWrapper() {
LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
String name = "名称";
Integer minAge = 10, maxAge = 1500;
queryWrapper.select(UserInfo::getId, UserInfo::getEmail);
queryWrapper.eq(name != null, UserInfo::getName, name);
queryWrapper.ge(minAge != null, UserInfo::getAge, minAge);
queryWrapper.le(maxAge != null, UserInfo::getAge, maxAge);
mapper.selectList(queryWrapper);
}
LambdaUpdateWrapper
同样也是使用Lambda
表达式来控制列:
@Test
void lambdaUpdateWrapper() {
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
LambdaUpdateWrapper<UserInfo> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(UserInfo::getAge, 888);
updateWrapper.set(UserInfo::getAge, 999);
mapper.update(null, updateWrapper);
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
}
分页
需要配置分页插件:
- 新建配置类,配置类中提供返回值为
MyBatisPlusInterceptor
的方法 - 方法中实例化
MyBatisPlusInterceptor
的对象 - 并调用
addInnerIntercepter
的方法添加PaginationInnerInterceptor(DbType.数据库类型)
进行添加配置插件 - 最后返回这个对象
使用:
-
通过调用
selectPage(page, queryWrapper)
等一系列的方法进行使用 -
需要提供
Page<>
实例- 在实例化时,指定页数和每页多少个元素
- 当页数为
0
或者1
时,查询出来的效果一样,两者执行的SQL
语句也都一样,都是select .. from ... limit 每页个数
-
最后查出来的数据将会放到
Page<>
实例中 -
可以通过
Spring Boot
中集成的Jackson
的在测试时查看Page
中的内容- 需要注入
ObjectMapper
,通过writeValueAsString(page)
可以查看具体的内容
- 需要注入
-
@Test void pages() throws JsonProcessingException { mapper.selectList(null); Page<UserInfo> page = new Page<>(1, 2); mapper.selectPage(page, null); System.out.println("page = " + page); // 页数 System.out.println("page.getPages() = " + page.getPages()); // 当前页 System.out.println("page.getCurrent() = " + page.getCurrent()); // 每页大小 System.out.println("page.getSize() = " + page.getSize()); // 总的元素数 System.out.println("page.getTotal() = " + page.getTotal()); // 获取查出来的数据的List System.out.println("page.getRecords() = " + page.getRecords()); // 是否有下一页 System.out.println("page.hasNext() = " + page.hasNext()); // 是否有上一页 System.out.println("page.hasPrevious() = " + page.hasPrevious()); System.out.println("objectMapper.writeValueAsString(page) = " + objectMapper.writeValueAsString(page)); }
Page
中的内容为:
-
{ "records": [ { "id": 1, "name": "Jone", "email": "test1@baomidou.com", "age": 18, "del": false }, { "id": 2, "name": "Jack", "email": "test2@baomidou.com", "age": 20, "del": false } ], "total": 11, "size": 2, "current": 1, "orders": [], "optimizeCountSql": true, "searchCount": true, "countId": null, "maxLimit": null, "pages": 6 }
自定义SQL使用分页插件
修改Mapper
接口:
- 将
Mapper
接口中的返回值设置为Page<类型>
- 第一个参数设置为
Page<类型> page
- 其他地方不变
- 需要注意的是,自己写的
sql
语句末尾一定不要添加;
,因为分页是在sql
语句末尾进行追加的sql
语句
@Mapper
public interface UserMapper extends BaseMapper<UserInfo> {
Page<UserInfo> selectByAge(Page<UserInfo> page, Integer age);
}
<select id="selectByAge" resultType="com.xiaoxu.learn.bean.UserInfo">
select id, name, age, email from user where age >= #{age} and del = 0
</select>
@Test
void consumerPage() throws JsonProcessingException {
Page<UserInfo> page = new Page<>(2, 3);
mapper.selectByAge(page, 10);
System.out.println("objectMapper.writeValueAsString(page) = " + objectMapper.writeValueAsString(page));
}
乐观锁
需要配置插件:
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
在数据库表增加一个整型的字段用来表示当前这条记录的版本号
在实体类上相关字段添加@Version
注解
例如:
-
有一件商品,价格为100元,第一个用户和第二个用户同时查询它的价格,第一个用户将其价格减少10元,第二个用户将其价格增加20元。理想情况下得到的最后的价格为
110
-
在没有锁的情况下两个用户同时查,此时两个用户得到的价格都是
100
,第一个用户减少10元后,此时价格为90
,用户2此时存储的价格仍为100
,这时候增加价格是,会从100
的基础上增加20
最后结果为120
,与预期的结果不一样 -
引入乐观锁之后,将会为每条记录带有一个版本号,更新时会附上版本号,因此不会出现这个问题
-
更新时所执行的
sql
语句为:UPDATE commodity SET name=?, price=?, version=? WHERE id=? AND version=?
-
@Test void lock() { Commodity commodity = mapper.selectById(1); System.out.println("commodity = " + commodity); Commodity commodity2 = mapper.selectById(1); // 使用commodity更新商品 commodity.setPrice(commodity.getPrice() - 10); mapper.updateById(commodity); System.out.println("mapper.selectById(1) = " + mapper.selectById(1)); // 再使用commodity2更新商品 commodity2.setPrice(commodity2.getPrice() + 20); mapper.updateById(commodity2); System.out.println("mapper.selectById(1) = " + mapper.selectById(1)); }
-
@Data @ToString public class Commodity { private Integer id; private String name; private Double price; @Version private Integer version; }
带上乐观锁之后,第二个用户修改价格时将不会更新数据库
枚举
有的属性值可能在实体类中使用的是枚举类型的,但枚举类型默认的值是一个String
的字符串,如果数据库中使用的是整型或者其他数据类型与之对应,那么,将无法正常查询或者修改
在MyBatis Plus
中提供了@EnumValue
注解,用来指定插入数据的枚举类上相关字段具体的值是哪一个:
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
@Getter
public enum Sex {
/**
*
*/
MALE(1, "男"),
FEMALE(2, "女");
@EnumValue
private final int flag;
private final String name;
Sex(int flag, String name) {
this.flag = flag;
this.name = name;
}
}
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "user")
public class UserInfo {
private Long id;
private String name, email;
private int age;
@TableLogic
private boolean del;
private Sex sex;
public UserInfo(Long id, String name, String email, int age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}
}
@Test
void enumTest() {
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
UserInfo user = new UserInfo(null, "人", "aaa@ewwe.co", 12);
user.setSex(Sex.FEMALE);
mapper.insert(user);
System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
}
多数据源
可以使用:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
作为多数据源
文档:https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611
Q.E.D.