MyBatis-Plus 学习笔记
学习官网: https://mp.baomidou.com/guide/
一、快速入门
1.简介
- MyBatis的增强工具,在Mybatis的基础上只做增强功能,兼容MyBatis
我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
2.快速开始
2.1 官方提供的数据库的表
创建表:
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
插入数据:
DELETE FROM user;
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
表:
2.2 创建spring boot项目
2.3 引入依赖
除了创建项目引入的那些常用的依赖外,还需要引入mybatis-plus和mysql驱动的依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
2.4 配置
yml配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/bookstore
driver-class-name: com.mysql.jdbc.Driver
username: root
password: '073838'
2.5 编写bean和mapper
User:
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
UserMapper:
@Mapper//也可以在springboot主类上标@MapperScan注解
public interface UserMapper extends BaseMapper<User> {
}
2.6 开始使用
使用测试类进行测试:
@SpringBootTest
class MpApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
System.out.println(("----- selectAll method test ------"));
List<User> userList = userMapper.selectList(null);
for (User user: userList){
System.out.println(user);
}
}
}
运行结果:
3.常用注解
@TableName
描述:表名注解,标在实体类中
常用属性:
- value:表名
- schema:对应的schema
- resultMap:sql映射文件中 resultMap 的 id
使用:
@Data
@TableName(value = "user", schema = "bookstore", resultMap = "book")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
注意:resultMap 若使用,需要编写相应的sql映射文件来映射,否则会报错
@TableId
描述:主键注解
属性:
IdType:设置自动生成主键值的策略
@TableField
描述:字段注解(非主键)
属性:详见https://mp.baomidou.com/guide/annotation.html#tablefield
- value:数据库字段名
- el:映射为原生
#{ ... }
逻辑,相当于写在 xml 里的#{ ... }
部分 - exist:是否为数据库表字段。若为false,该属性不会映射到数据库表的字段
示例:
@TableName(value = "user", schema = "bookstore")
public class User {
@TableId(value = "id")
private Long id;
@TableField(exist = true, value = "email")
private String name;
private Integer age;
private String email;
}
运行结果:
@Version注解
描述:乐观锁注解、标记,标记@Version在字段上
@EnumValue
- 描述:通枚举类注解(注解在枚举字段上)
二、CRUD接口
1.Service CRUD接口
IService提供了给service层进行CRUD的接口,需要配合Mapper层的接口一起使用。主要有save、remove、update、getOne、list等。示例:
@SpringBootTest
public class CRUDTest {
@Autowired
UserService userService;
@Test
public void saveTest(){
User user = new User();
user.setAge(18);
user.setEmail("aaaa@126.com");
user.setName("anpu");
List<User> list = new ArrayList<>();
for(int i = 0; i < 15; i++){
list.add(user);
}
userService.save(user);
userService.saveBatch(list);
}
@Test
public void getTest(){
User user = userService.getById("1");
List<User> users = userService.list();
System.out.println("one:" + user);
System.out.println("--------------------");
users.forEach(System.out::println);
}
@Test
public void updateTest(){
User user = userService.getById(6);
user.setAge(3);
user.setName("deserts");
userService.updateById(user);
}
@Test
public void deleteTest(){
userService.removeById(6);
Map<String, Object> map = new HashMap<>();
map.put("name", "anpu");
userService.removeByMap(map);
}
@Test
public void saveOrUpdateTest(){
User user = userService.getById(3);
user.setName("lala");
user.setId(6L);
userService.saveOrUpdate(user);
}
@Test
public void countTest(){
System.out.println(userService.count());
}
}
2.Mapper CRUD层接口
BaseMapper提供了Mapper层进行CRUD的接口。主要有delete、select、update、Insert接口。
3.两层CRUD接口的使用
1.Mapper层接口继承BaseMapper接口
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
2.Service层创建接口去继承IService接口
public interface UserService extends IService<User> {
}
3.创建service实现类去继承ServiceImpl,实现service接口,妻子ServiceImpl是Iservice的实现类,避免重写很多方法
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
三、条件构造器
说明:
QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类
用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件
注意: entity 生成的 where 条件与 使用各个 api 生成的 where 条件没有任何关联行为
AbstractWrapper
AbstractWrapper是抽象类,封装了基本的条件方法,提供给QueryWrapper和UpdateWrapper继承使用。
提供的条件方法:
- allEq, eq, ne:等于(map),等于(单个),不等于
- gt,ge,lt,le: 大于,大于等于,小于,小于等于
- between,notBetween: 在A和B之间,不在A和B之间
- like: 模糊查询。示例: like(“name”, “王”)
--->
name like ‘%王%’ - notLike: 模糊查询。示例: notLike(“name”, “王”)
--->
name not like ‘%王%’ - likeLeft: 模糊查询。示例:likeLeft(“name”, “王”)
--->
name like ‘%王’ - likeRight: 模糊查询。示例:likeRight(“name”, “王”)
--->
name like ‘王%’ - isNull, isNotNUll: 是否为空
- in,notIn: 取值范围
- inSql, notInSql: 子查询。示例:inSql(“id”, “select id from table where id < 3”)
--->
id in (select id from table where id < 3) - groupBy: 分组查询。示例: groupBy(“id”, “name”)
--->
group by id,name - orederByAsc,orederByDsc,orederBy: 按字段升降序排序查找
- having: 示例:having(“sum(age) > 10”)
--->
having sum(age) > 10 - or,and: 条件嵌套。示例:eq(“id”,1).or().eq(“name”,“老王”)
--->
id = 1 or name = ‘老王’ - exists,notExists: 子查询。示例:exists(“select id from table where age = 1”)
--->
exists (select id from table where age = 1)
conditonal参数
如:notExists(boolean condition, String notExistsSql);默认为true,若为false,表示该条件失效。
QueryWrapper
QueryWrapper继承AbstractWrapper。除了AbstractWrapper外,还有select()用于设置查询字段:
select(String... sqlSelect)
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
说明:
以上方法分为两类.
第二类方法为:过滤查询字段(主键除外),入参不包含 class 的调用前需要wrapper
内的entity
属性有值! 这两类方法重复调用以最后一次为准
- 例:
select("id", "name", "age")
- 例:
select(i -> i.getProperty().startsWith("test"))
UpdateWrapper
说明:
继承自
AbstractWrapper
,自身的内部属性entity
也用于生成 where 条件
及LambdaUpdateWrapper
, 可以通过new UpdateWrapper().lambda()
方法获取!
set
为某个字段的所以值设置为一个值,如set(“name”, null)--->数据库字段值变为
null
set(String column, Object val)
set(boolean condition, String column, Object val)
setSql
- 设置 SET 部分 SQL
- 例:
setSql("name = '老李头'")
四、分页功能
1.加入分页插件
@Configuration
public class MyBatisConfiguration {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInnerInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(100L);
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybatisPlusInterceptor;
}
}
2.使用service层的接口实现分页
@Test
public void pageTest(){
Page<User> page = new Page<>(1, 5);
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "anpu");
Page<User> userPage = userService.page(page, wrapper);
List<User> records = userPage.getRecords();
System.out.println("---------" + userPage);
System.out.println("-------->" + userPage.getTotal());
System.out.println("-------->" + userPage.getSize());
records.forEach(System.out::println);
}
控制台输出:
---------com.baomidou.mybatisplus.extension.plugins.pagination.Page@44114b9f
-------->16
-------->5
User(id=15, name=anpu, age=18, email=aaaa@126.com)
User(id=16, name=anpu, age=18, email=aaaa@126.com)
User(id=17, name=anpu, age=18, email=aaaa@126.com)
User(id=18, name=anpu, age=18, email=aaaa@126.com)
User(id=19, name=anpu, age=18, email=aaaa@126.com)
五、乐观锁插件
MybatisPlusInterceptor
该插件是核心插件,目前代理了 Executor#query
和 Executor#update
和 StatementHandler#prepare
方法
目前已有的功能:
- 自动分页: PaginationInnerInterceptor
- 多租户: TenantLineInnerInterceptor
- 动态表名: DynamicTableNameInnerInterceptor
- 乐观锁: OptimisticLockerInnerInterceptor
- sql性能规范: IllegalSQLInnerInterceptor
- 防止全表更新与删除: BlockAttackInnerInterceptor
使用顺序:
使用多个功能需要注意顺序关系,建议使用如下顺序
- 多租户,动态表名
- 分页,乐观锁
- sql性能规范,防止全表更新与删除
总结: 对sql进行单次改造的优先放入,不对sql进行改造的最后放入
乐观锁实现
什么是乐观锁?当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁的实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
使用方法:
1.数据库表添加字段version
2.实体类使用@version标注
@Data
@TableName(value = "user", schema = "bookstore")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
@Version
private Long version;
}
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整数类型下
newVersion = oldVersion + 1
newVersion
会回写到entity
中- 仅支持
updateById(id)
与update(entity, wrapper)
方法- 在
update(entity, wrapper)
方法下,wrapper
不能复用!!!
3.配置类加入乐观锁插件,乐观锁生效
@Configuration
public class MyBatisConfiguration {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInnerInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(100L);
//乐观锁插件
OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
mybatisPlusInterceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
return mybatisPlusInterceptor;
}
}
六、扩展功能
1.代码生成器
添加依赖:MyBatis-Plus 从 3.0.3 之后移除了代码生成器与模板引擎的默认依赖,需要手动添加相关依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
编写代码:
@Test
public void test(){
//创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setEntityName("bean");
gc.setMapperName("mapper");
gc.setServiceName("service");
gc.setControllerName("controller");
//是否覆盖之前的文件
gc.setFileOverride(true);
gc.setAuthor("deserts");
gc.setOpen(false);
//实体属性 Swagger2 注解
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/bookstore?useUnicode=true&useSSL=false&characterEncoding=utf8");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("073838");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("auto");
pc.setParent("com.deserts");
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
//包含的表名
strategy.setInclude("user");
//设置逻辑删除字段
strategy.setLogicDeleteFieldName("deleted");
//设置自动填充字段
TableFill gmt_create = new TableFill("gmt_create", FieldFill.INSERT);
TableFill gmt_update = new TableFill("gmt_update", FieldFill.INSERT_UPDATE);
strategy.setTableFillList(Arrays.asList(gmt_create, gmt_update));
//设置乐观锁version字段
strategy.setVersionFieldName("version");
mpg.setStrategy(strategy);
//执行
mpg.execute();
}
2.逻辑删除
1.数据库表中增加一个逻辑删除的字段:
2.配置yml
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
3.实体类中指定逻辑删除字段:
@TableLogic
private Integer deleted;
4.测试
@Test
public void deletedTest(){
userService.removeById(1);
}
5.实际执行的语句
UPDATE bookstore.user SET deleted=1 WHERE id=? AND deleted=0
6.加上逻辑删除后执行查找:
@Test
public void select(){
User user = userService.getById(1);
System.out.println(user);
}
此时执行的sql:
SELECT id,name,age,email,version,deleted FROM bookstore.user WHERE id=? AND deleted=0
3.自动填充功能
实现数据库表的更新有记录:gmt_create,gmt_modified
方式一:基于数据库表
创建时间不需要勾选根据时间戳更新:
效果:
执行更新操作后:
方式二:代码中设置
表中创建两个字段:
实体类注解填充字段:
@TableField(fill = FieldFill.INSERT)
private Date cmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date cmtModified;
自定义实现类MyMetaObjectHandler:
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.setFieldValByName("cmtCreate", new Date(), metaObject);
this.setFieldValByName("cmtModified", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.setFieldValByName("cmtModified", new Date(), metaObject);
}
}
更新操作后:
4.日志分析
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl