MyBatis-Plus
简介
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为 简化开发、提高效率而生。提供了通用的mapper和service,可以在不编写田可SQL语句的情况下,快速的实现对单表的CRUD
、批量、逻辑删除、分页等操作。
提供了分页、乐观锁、MyBatisX等插件,提供了代码生成器功能。并支持多数据源。
特性
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由 配置,完美解决主键问题
支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强 大的 CRUD 操作
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等 同于普通 List 查询
分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、 Postgre、SQLServer 等多种数据库
内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出 慢查询
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
框架结构
使用配置
引入
<!--mybatis-plus启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
配置数据源和日志
spring:
datasource.:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
username: xxxxxx
password: xxxxxx
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl // 配置日志,可以查看sql语句
type-aliases-package: com.xx.pojo // 配置类别名
BaseMapper<T>接口
自定义的mapper接口继承这个接口,即可拥有一些简单的增删改查方法。泛型要记得替换。
package com.baomidou.mybatisplus.core.mapper;
public interface BaseMapper<T> extends Mapper<T> {
int insert(T entity);
int deleteById(Serializable id);
int deleteById(T entity);
int deleteByMap(@Param("cm") Map<String, Object> columnMap);
int delete(@Param("ew") Wrapper<T> queryWrapper);
int deleteBatchIds(@Param("coll") Collection<?> idList);
int updateById(@Param("et") T entity);
int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
T selectById(Serializable id);
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
default T selectOne(@Param("ew") Wrapper<T> queryWrapper) {
List<T> ts = this.selectList(queryWrapper);
if (CollectionUtils.isNotEmpty(ts)) {
if (ts.size() != 1) {
throw ExceptionUtils.mpe("One record is expected, but the query result is multiple records", new Object[0]);
} else {
return ts.get(0);
}
} else {
return null;
}
}
default boolean exists(Wrapper<T> queryWrapper) {
Long count = this.selectCount(queryWrapper);
return null != count && count > 0L;
}
Long selectCount(@Param("ew") Wrapper<T> queryWrapper);
List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
<P extends IPage<T>> P selectPage(P page, @Param("ew") Wrapper<T> queryWrapper);
<P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param("ew") Wrapper<T> queryWrapper);
}
通用Service
通用 Service CRUD 封装IService
接口,进一步封装 CRUD。
get
查询单行remove
删除list
查询集合page
分页 前缀命名方式区分Mapper
层避免混淆- 泛型
T
为任意实体对象 - 建议如果存在自定义通用
Service
方法的可能,请创建自己的IBaseService
继承 Mybatis-Plus 提供的基类IService
MyBatis-Plus中有一个接口
IService
和其实现类ServiceImpl
,封装了常见的业务层逻辑
项目中一般这样实现:
添加接口:
public interface UserService extends IService<User> {
}
添加实现。这个类继承mp提供的ServiceImpl
,实现我们的接口UserService
:
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
常用注解
@Table
实体类名和表名不一致时,需要中实体类上加@Table("表名")
注解。
该问题也可以通过全局配置解决。如下方的最后一行。
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
@TableId
指定表里的主键对应的实体类里的字段。默认是id
。如果不是,需要在类里面代表id的字段上方添加此注
解。
value属性:如果表里id列名为uid
,而实体类里注解名为id
,需要这样添加:@TableId(“uid”)
type属性:指定主键生成策略。可选值如下,在com.baomidou.mybatisplus.annotation.IdType
里定义。
AUTO(0), // 使用数据库的自增策略,注意,该类型请确保数据库设置了id自增,否则无效
NONE(1),
INPUT(2),
ASSIGN_ID(3), // 默认。基于雪花算法的策略生成数据id,与数据库id是否设置自增无关
ASSIGN_UUID(4); // 使用UUID
使用示例:
@TableId(value = "uId", type = IdType.AUTO)
private Long id;
当然,也可以像下面这样全局配置:
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto
@TableField
MyBatis-Plus在执行SQL语句时,要保证实体类中的属性名和表中的字段名一致。如果实体类中的属性名和字段名不一致的情况,会出现什么问题呢?
情况1:
若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格。例如实体类属性userName
,表中字段user_name
。此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格,相当于在MyBatis中配置。
情况2:
若实体类中的属性和表中的字段不满足情况1,例如实体类属性name
,表中字段username
。此时需要在实体类属性上使用@TableField("username")
设置属性所对应的字段名。
@TableLogic
逻辑删除
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库 中仍旧能看到此条数据记录。
使用场景:可以进行数据恢复。不会真正删除数据。
实现逻辑删除
数据库中创建状态列
is_deleted
,设置默认值为0。实体类中添加逻辑删除属性。
@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 : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : 查询条件封装
- UpdateWrapper : Update 条件封装
- AbstractLambdaWrapper : 使用Lambda 语法
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper
可以组装查询、排序、删除条件,还可以设置优先级、子查询。
组装查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username", "a")
.between("age", 20, 30)
.isNotNull("email");
List<User> list = userMapper.selectList(queryWrapper);
组装排序条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.orderByDesc("age")
.orderByAsc("id");
List<User> users = userMapper.selectList(queryWrapper);
组装删除条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
//条件构造器也可以构建删除语句的条件
int result = userMapper.delete(queryWrapper);
组装修改条件
queryWrapper
.like("username", "a")
.gt("age", 20)
.or()
.isNull("email");
User user = new User();
user.setAge(18);
user.setEmail("user@atguigu.com");
// 参数1,用于设置填充的内容。参数2,用于设置更新条件
int result = userMapper.update(user, queryWrapper);
等于执行以下SQL:
UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND age > ? OR
email IS NULL)
修改更新的优先级
如果更新条件为:将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改。即sql为:
UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND (age > ? OR
email IS NULL))
此时可以使用lambda表达式,它里面的逻辑优先运算。
queryWrapper
.like("username", "a")
.and(i -> i.gt("age", 20).or().isNull("email"));
User user = new User();
user.setAge(18);
user.setEmail("user@atguigu.com");
int result = userMapper.update(user, queryWrapper);
组装Select子句
需求:查询用户信息的username
和age
字段:SELECT username,age FROM t_user
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("username", "age");
//selectMaps()返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值为null
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
子查询
需求:查询id小于等于3的用户信息。
SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (id IN (select id from t_user where id <= 3))
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("id", "select id from t_user where id <= 3");
List<User> list = userMapper.selectList(queryWrapper);
UpdateWrapper
和QueryWrapper
相比,更新时可以设置条件和字段。
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
//lambda表达式内的逻辑优先运算
updateWrapper
.set("age", 18)
.set("email", "user@atguigu.com")
.like("username", "a")
.and(i -> i.gt("age", 20).or().isNull("email"));
int result = userMapper.update(null, updateWrapper);
UPDATE user SET name=?,email=? WHERE is_deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL))
condition
在mybatis-plus中如何判断参数是否为空呢。我们可以使用带condition参数的重载方法构建查询条件,简化代码的编写。
queryWrapper.like(StringUtils.isNotBlank(name), "name", name);
如上方代码,如果name
为null或空字符串,则queryWrapper
不会拼接此条件。否则拼接。
更多示例:
queryWrapper
.like(StringUtils.isNotBlank(username), "username", "a")
.ge(ageBegin != null, "age", ageBegin)
.le(ageEnd != null, "age", ageEnd);
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (age >=? AND age <= ?)
List<User> users = userMapper.selectList(queryWrapper);
LambdaQueryWrapper
如下方代码所示,可以在LambdaQueryWrapper
里使用类似User::getAge
的语法。
这样,如果User类里的age字段改名,这里就会触发错误提示,可以在开发阶段识别风险。
@Test
public void test09() {
//定义查询条件,有可能为null(用户未输入)
String username = "a";
Integer ageBegin = 10;
Integer ageEnd = 24;
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
//避免使用字符串表示字段,防止运行时错误
queryWrapper
.like(StringUtils.isNotBlank(username), User::getName, username)
.ge(ageBegin != null, User::getAge, ageBegin)
.le(ageEnd != null, User::getAge, ageEnd);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
LambdaUpdateWrapper
@Test
public void test10() {
//组装set子句
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper
.set(User::getAge, 18)
.set(User::getEmail, "user@atguigu.com")
.like(User::getName, "a")
.and(i -> i.lt(User::getAge, 24).or().isNull(User::getEmail)); //lambda表达式内的逻辑优先运算
User user = new User();
int result = userMapper.update(user, updateWrapper);
System.out.println("受影响的行数:" + result);
}
那如果QueryWrapper也想用lambda风格的写法,如何实现呢?可以这样实现:
QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(User::getUname, "hpt");
插件
分页插件
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
添加配置类
@Configuration
@MapperScan("com.atguigu.mybatisplus.mapper") //可以将主类中的注解移到此处
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
测试
@Test
public void selectPage() {
Page<User> page = new Page<>(1, 3);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", "a");
userMapper.selectPage(page, queryWrapper);
page.getRecords().forEach(System.out::println);
System.out.println("page.getPages() = " + page.getPages());
System.out.println("page.getTotal() = " + page.getTotal());
System.out.println("page.hasNext() = " + page.hasNext());
System.out.println("page.hasPrevious() = " + page.hasPrevious());
}
xml自定义分页
如果想在自己写的sql中使用分页插件,如何配置?
先定义接口方法,然后正常调用即可。
注意:返回值必须是Page对象,Page对象也必须是方法的第一个参数。
/**
* 根据年龄分页查询用户信息
* @param page mp提供的分页对象,必须是第一个参数。
* @param age
* @return
*/
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
乐观锁
使用版本号机制实现乐观锁。数据库中添加version字段,取出记录时,获取当前version:
SELECT id, name, price, version FROM product WHERE id=1
更新时,version + 1,如果where语句中的version版本不对,则更新失败:
UPDATE product SET price=price+50, version=version + 1 WHERE id=1 AND version=1
MyBatis-Plus实现乐观锁
- 在代表版本的字段上加
@Version
注解。
@Data
public class Product {
private Long id;
private String name;
private Integer price;
@Version
private Integer version;
}
- 配置类中添加乐观锁插件
@Configuration
//@MapperScan("com.atguigu.mybatisplus.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
动态表名SQL解析器
当数据量特别大的时候,我们通常会采用分库分表。这时,可能就会有多张表,其表结构相同,但表名不同。例如order_1,order_2,order_3,查询时,我们可能需要动态设置要查的表名。
mp提供了动态表名SQL解析器。
代码生成器
引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
快速生成
具体可参考官网
public class FastAutoGeneratorTest {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus?characterEncoding=utf-8&userSSL=false", "root", "123456")
.globalConfig(builder -> {
builder.author("atguigu") // 设置作者
.fileOverride() // 覆盖已生成文件
.outputDir("D://mybatis_plus"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.atguigu") // 设置父包名
.moduleName("mybatisplus") // 设置父包模块名
// 设置mapperXml生成路径
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus"));
}).strategyConfig(builder -> {
builder.addInclude("t_user") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
}).templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
多数据源
配置
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
在service
类,或者类里的方法上加上@DS("dsName")
即可指定该类或方法使用的数据源。
注解 | 结果 |
---|---|
没有@DS | 默认数据源 |
@DS("dsName") | dsName可以为组名也可以为具体某个库的名称 |
MyBatisX插件
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
- 可以在xml和mapper接口之间跳转
- 可以根据jpa风格的方法名生成对应的sql语句
- 0很棒
- 0不错
- 0一般
- 0???