-
狂神说 MyBatisPlus 学习笔记
一、快速入门
文档:https://mp.baomidou.com/
使用第三方组件:
- 导入对应依赖
- 研究依赖如何配置
- 代码如何编写
- 提高扩展技术能力
步骤:
1、创建数据库
mybatis_plus
2、创建user表
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) );
2.1、插入数据
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');
3、创建项目
4、导入依赖
<!--数据库驱动--> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.20</version> </dependency> <!--mybatis-plus--> <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version> </dependency> <!--lombok--> <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional> </dependency>
说明:我们使用mybatis-plus可以节省我们大量的代码,尽量不要同时导入mybatis和mybatis-plus!
5、连接数据库,这一步和mybatis相同
# mysql spring.datasource.username=root spring.datasource.password=root spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
6、传统方式:pojo-dao(连接mybatis,配置mapper.xml文件)- service - controller
6、使用了mybatis-plus 之后
pojo(实体类):
- extends Model : 必须存在对应的原始mapper并继承baseMapper并且可以使用的前提下才能使用此 AR 模式 !!!
文章末尾有关于AR模式的简单介绍
@Data
@TableName("user")
public class User extends Model<User> {@TableIdprivate Long id;private String name;private Integer age;private String email;private Long managerId;private LocalDateTime createTime;
}
mapper接口(dao层):
// 在对应的Mapper上面继承基本的接口 BaseMapper
@Repository // 代表持久层
public interface UserMapper extends BaseMapper<User> {// 所有的CRUD操作都已经基本完成了// 你不需要像以前的配置一大堆文件了
}
Service层:
import com.baomidou.mybatisplus.extension.service.IService;
import com.zhixi.pojo.User;public interface UserService extends IService<User> {
}
Service层实现类:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}
启动类:
- 注意点:我们需要在主启动类上去扫描我们的mapper包下的所有接口
测试类中测试
@SpringBootTest
class MybatisPlusApplicationTests {// 继承了BaseMapper,所有的方法都来自父类// 我们也可以编写自己的扩展方法@AutowiredUserMapper userMapper;@Testvoid contextLoads() {// 参数是一个 Wrapper , 条件构造器,这里我们先不用 null// 查询全部用户List<User> users = userMapper.selectList(null);users.forEach(System.out::println);}
}
思考问题:
1、SQL是谁帮我们写的?MyBatis-Plus都写好了
2、方法哪里来的?MyBatis-Plus 都写好了
二、配置日志
我们所有的sqld现在都是不可见的,我们希望知道它是怎么执行的,所以我们必须要看日志!
# 配置日志 (系统自带的,控制台输出)
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
配置完毕日志之后,后面的学习就需要注意这个自动生成的SQL,你们就会喜欢上MyBatis-Plus !
三、CRUD
1. 插入操作
Insert 插入
@Test
public void testInsert() {User user = new User();user.setName("Dainel");user.setAge(3);user.setEmail("daniel@alibaba.com");int result = userMapper.insert(user);// 帮我们自动生成idSystem.out.println(result);// 受影响的行数System.out.println(user);// 发现: id自动回填
}
数据库插入的id默认值为:全局的唯一id
2. 主键生成策略
默认 ID_WORKER 全局唯一id
分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html
雪花算法:
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!
主键自增
我们需要配置主键自增:
-
实体类字段上 @TableId(type = IdType.AUTO)
-
数据库字段上一定是自增的
-
再次测试即可
其余的源码解释
public enum IdType {AUTO(0), // 数据库id自增NONE(1), // 未设置主键INPUT(2), // 手动输入,自己写idID_WORKER(3), // 默认的全局唯一idUUID(4), // 全局唯一id uuidID_WORKER_STR(5); // ID_WORKER 字符串表示法
}
3、更新操作
@Test/*更新操作*/
void updateUser() {// 查询到要更新的用户User user = userMapper.selectById(7);// 要更新的值user.setName("demo");user.setAge(21);// 执行更新userMapper.updateById(user);
}
4. 自动填充
创建时间、修改时间!这些个操作一般都是自动化完成的,我们不希望手动更新!
阿里巴巴开发手册:所有的数据库表:gmt_create、gmt_modified几乎所有的表都要配置上!而且需要自动化!
方式一:数据库级别(工作中不允许修改数据库)
-
在表中新增字段 create_time,update_time;注意将更新的时间戳勾选
-
再次测试插入方法,我们需要先把实体类同步
private Date createTime; private Date updateTime;
-
再次查看更新结果即可
方式二:代码级别
-
删除数据库中的默认值、更新操作
-
实体类的字段属性上需要增加注解
//字段添加填充内容 @TableField(fill = FieldFill.INSERT) private Date createTime;@TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;
-
编写处理器来处理这个注解即可:handler/MyMetaObjectHandler
package com.kuang.handler;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component;import java.util.Date;@Slf4j @Component // 一定不要忘记把处理器加到IOC容器中 public class MyMetaObjectHandler implements MetaObjectHandler {// 插入时候的填充策略@Overridepublic void insertFill(MetaObject metaObject) {log.info("start insert fill ...");// setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject)this.setFieldValByName("createTime", new Date(), metaObject);this.setFieldValByName("updateTime", new Date(), metaObject);}// 更新时的填充策略@Overridepublic void updateFill(MetaObject metaObject) {log.info("start update fill ...");this.setFieldValByName("updateTime", new Date(), metaObject);}}
-
测试插入
-
测试更新,观察时间已经更新
5. 乐观锁
在面试过程中,我们经常会被问到乐观锁,悲观锁。
乐观锁:顾名思义,它总是认为不会出现问题,无论干什么都不去上锁!如果出现了问题,再次更新值测试!
悲观锁:顾名思义,它总是认为总是出现问题,无论干什么都上锁!再去操作!
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
测试一下MyBatisPlus的插件:
1、数据库中增加一个version字段,并设置默认值为1
2、实体类添加注解
@Version // 乐观锁的version注解
private Integer version;
3、编写配置
config/MybatisPlusConfig
// 配置,会被spring扫描到
@Configuration
public class MybatisPlusConfig {// 注册乐观锁@Beanpublic OptimisticLockerInterceptor optimisticLockerInnerInterceptor(){return new OptimisticLockerInterceptor();}
}
4、测试方法
测试乐观锁成功:
将2号的姓名改为lipu。看他的version会怎么变化
@Test
void OptimisticLock() {// 查询到要进行更新的用户User user = userMapper.selectById(2);// 修改用户的属性user.setName("lipu");user.setAge(28);// 提交更新userMapper.updateById(user);
}
测试乐观锁失败(多线程下)
// 测试乐观锁失败!多线程下
@Test
public void testVersionFall() {// 线程1User user1 = userMapper.selectById(9);user1.setName("fan111");user1.setAge(14);// 线程2User user2 = userMapper.selectById(9);user2.setName("fan222");user2.setAge(24);userMapper.updateById(user2);//自旋锁来多次尝试提交!userMapper.updateById(user1); //如果没有乐观锁就会覆盖插队线程的值
}
6. 查询操作
// 测试查询
@Test
public void testSelectById(){User user = userMapper.selectById(1);System.out.println(user);
}// 批量查询
@Test
public void testSelectByBatchIds(){List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));users.forEach(System.out::println);
}// 按照条件查询之一使用 map
@Test
public void testSelectByMap(){HashMap<String, Object> map = new HashMap<>();// 自定义要查询map.put("name","Dainel");map.put("age","6");List<User> users = userMapper.selectByMap(map);users.forEach(System.out::println);
}
7、分页查询
分页网站频繁使用
- 原始使用limit进行分页
- pageHelper第三方插件
- MybatisPlus内置了分页插件
如何使用:
1、配置分页拦截器:config/MybatisPlusConfig
// 分页插件@Beanpublic PaginationInterceptor paginationInterceptor() {return new PaginationInterceptor();}
2、编写查询
// 分页查询@Testvoid selectLimit() {Page<User> page = new Page<User>(2, 5);// 获取通过分页查询到的记录条数page.getRecords().forEach(System.out::println);// 查询userMapper.selectPage(page, null);// 查询总记录条数System.out.println("总计数条数:" + page.getTotal());}
8. 删除操作
// 测试删除
@Test
public void testdelete(){userMapper.deleteById(6L);
}// 测试批量删除
@Test
public void testdeleteBatchId(){userMapper.deleteBatchIds(Arrays.asList(1,14));
}//通过map删除
@Test
public void testDeleteByMap(){HashMap<String, Object> map = new HashMap<>();map.put("name","KUANG");userMapper.deleteByMap(map);
}
我们在工作中会遇到一些问题:逻辑删除!
9. 逻辑删除
物理删除:从数据库中直接移除
逻辑删除:在数据库中没有被移除,而是通过一个变量让他生效!deleted=0 --> deleted=1
管理员可以查看被删除的记录!防止数据的丢失!类似于回收站!
步骤:
1、在数据库表中增加一个deleted字段
2、修改实体类
// 表逻辑删除
@TableLogic
private int deleted;
3、配置:config/MybatisPlusConfig
注意:在新版本的MP中,不需要再写逻辑删除配置了,知道就行,只需要在实体类逻辑删除字段上加上注解即可!
// 逻辑删除组件
public ISqlInjector sqlInjector(){return new LogicSqlInjector();
}
4、properties配置(默认,可以不配置)
# 配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
测试逻辑删除:
// 逻辑删除@Testvoid deleteLogic() {userMapper.deleteById(1);}
逻辑删除成功、
我们通过查询1号用户,验证下看是否能够查询的到:
四、性能分析插件
PerformanceInterceptor在3.2.0被移除了,如果想进行性能分析,用第三方的,官方这样写的“该插件 3.2.0 以上版本移除推荐使用第三方扩展 执行 SQL 分析打印 功能
https://baomidou.com/guide/p6spy.html
我们在平时的开发中,会遇到一些慢sql。解决方案:测试,druid监控…
作用:性能分析拦截器,用于输出每条SQL语句及其执行时间
MyBatisPlus也提供性能分析插件,如果超过这个时间就停止运行!
1、导入插件
// SQL执行效率插件
@Bean
@Profile({"dev","test"})
public PerformanceInterceptor performanceInterceptor(){PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();performanceInterceptor.setMaxTime(100); //ms 设置sql执行的最大时间,如果超过了则不执行performanceInterceptor.setFormat(true); // 是否格式化return performanceInterceptor;
}
记住,要在SpringBoot中配置环境为dev或者test环境!
# 开发环境
spring.profiles.active=dev
2、测试使用
// 分页查询@Testvoid selectLimit() {Page<User> page = new Page<User>(1, 5);// 获取通过分页查询到的记录条数page.getRecords().forEach(System.out::println);// 查询userMapper.selectPage(page, null);// 查询总记录条数System.out.println("总计数条数:" + page.getTotal());}
只要超出时间就会抛出异常
使用性能分析插件可以提高效率,新版本MP已经移除该拆件了,可以使用druid
五、代码自动生成**非常NB
dao、pojo、service、controller都给我自己去编写完成!
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、
Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
只需要改实体类名字 和包名 还有 数据库配置即可
需要使用到的pom依赖:
<dependencies><!--swagger依赖--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.7.0</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.7.0</version></dependency><!-- 模板引擎 --><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.0</version></dependency><!--导入mysql驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies>
测试代码:/test/…
package com.zhixi;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;import java.util.ArrayList;// 代码自动生成器
public class zhixiCode {public static void main(String[] args) {// 需要构建一个 代码自动生成器 对象AutoGenerator mpg = new AutoGenerator();// 配置策略// 1、全局配置GlobalConfig gc = new GlobalConfig();// 代码输出到哪个目录(获取到项目目录)String projectPath = System.getProperty("user.dir");gc.setOutputDir(projectPath + "/src/main/java");// 设置作者gc.setAuthor("张志喜");// 是否打开资源管理器(生成完代码打开目录)gc.setOpen(false);// 是否覆盖原来的文件gc.setFileOverride(false);// 去掉IService的I前缀(正则)gc.setServiceName("%sService");// 设置字段gc.setIdType(IdType.ID_WORKER);gc.setDateType(DateType.ONLY_DATE);// 配置swaggergc.setSwagger2(true);mpg.setGlobalConfig(gc);//2、设置数据源DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://182.92.209.212:3306/mybatis_plus?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai");dsc.setDriverName("com.mysql.cj.jdbc.Driver");dsc.setUsername("root");dsc.setPassword("密码");// 数据库类型dsc.setDbType(DbType.MYSQL);mpg.setDataSource(dsc);//3、包的配置(只需要改实体类名字 和包名 还有 数据库配置即可)PackageConfig pc = new PackageConfig();// 模块名pc.setModuleName("blog");// 设置类放在哪个包下pc.setParent("com.kuang");// 设置实体类pc.setEntity("entity");// 设置mapperpc.setMapper("mapper");// 设置services层pc.setService("service");// 设置controller层pc.setController("controller");mpg.setPackageInfo(pc);//4、策略配置StrategyConfig strategy = new StrategyConfig();// 设置要映射的表名strategy.setInclude("blog_tags", "course", "links", "sys_settings", "user_record", " user_say");// 设置下划线转驼峰命名strategy.setNaming(NamingStrategy.underline_to_camel);strategy.setColumnNaming(NamingStrategy.underline_to_camel);// 自动lombok;strategy.setEntityLombokModel(true);// 设置逻辑删除列名strategy.setLogicDeleteFieldName("deleted");// 自动填充配置TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);/*创建时间*/TableFill gmtModified = new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);/*修改时间*/ArrayList<TableFill> tableFills = new ArrayList<>();tableFills.add(gmtCreate);tableFills.add(gmtModified);// 添加自动填充strategy.setTableFillList(tableFills);// 乐观锁strategy.setVersionFieldName("version");/*设置版本字段名称*/strategy.setRestControllerStyle(true);strategy.setControllerMappingHyphenStyle(true);// localhost:8080/hello_id_2mpg.setStrategy(strategy);mpg.execute(); //执行}
}
六、SQL注入器:实现自定义通用方法
MP在一开始就给大家提供了很多通用的方法,在DefaultSqlInjector这个类中,在MethodList这个集合当中包含的都是通用方法类,如果想要使用自定义通用方法,也需要添加到这个集合当中。
/*** SQL 默认注入器*/
public class DefaultSqlInjector extends AbstractSqlInjector {@Overridepublic List<AbstractMethod> getMethodList(Class<?> mapperClass) {return Stream.of(new Insert(),new Delete(),new DeleteByMap(),new DeleteById(),new DeleteBatchByIds(),new Update(),new UpdateById(),new SelectById(),new SelectBatchByIds(),new SelectByMap(),new SelectOne(),new SelectCount(),new SelectMaps(),new SelectMapsPage(),new SelectObjs(),new SelectList(),new SelectPage()).collect(toList());}
}
第一步:创建自定义方法的类(以创建删除所有记录的方法为例)
类名是DelFillUserMethod,这个是自定义的,但是最好是见名知意的类名。
继承AbstractMethod抽象类,AbstractMethod是抽象的注入方法类,每个通用方法也都继承了这个类,也是为了方便调用里面的方法
重写injectMappedStatement方法,injectMappedStatement内是我们完成我们这个方法具体的逻辑。
package com.zhixi.method;import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;/**
* @author zhangzhixi
* @date 2021-6-13 20:56
*/
public class DelAllMethod extends AbstractMethod {@Overridepublic MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {// 1、自定义执行的sql语句String sql = "delete from " + tableInfo.getTableName();// 2、mapper接口方法名String method = "deleteUserAll";/*** 3、添加动态SQL标签处理器* 注意只需要传入sql即可*/SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);// 4、返回statement对象return addDeleteMappedStatement(mapperClass, method, sqlSource);}
}
第二步:创建注入器,并把自定义的方法添加到集合当中
- 类名是CustomSqlInjector,自定义的
- 继承DefaultSqlInjector并重写getMethodList,SQL 默认注入器上面也有提到过,我们得把我们自定义的方法加入到通用方法的集合
methodList
当中
package com.zhixi.injector;import com.baomidou.mybatisplus.core.injector.AbstractMethod;import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;import com.zhixi.method.DelAllMethod;import org.springframework.stereotype.Component;import java.util.List;@Componentpublic class MyInjector extends DefaultSqlInjector {@Overridepublic List<AbstractMethod> getMethodList(Class<?> mapperClass) {// 1、先要通过父类方法,获取到原有的集合,不然会自带的通用方法会失效的List<AbstractMethod> methodList = super.getMethodList(mapperClass);// 2、添加自定义mapper类methodList.add(new DelAllMethod());return methodList;}}
第三步:在Mapper中添加自定义的方法
package com.zhixi.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhixi.pojo.User;
import org.springframework.stereotype.Repository;@Repository
public interface UserMapper extends BaseMapper<User> {/*** 删除所有记录** @return 影响行数*/int deleteUserAll();
}
什么是AR模式:简单来说就是通过实体类对象,直接进行表的增删改查操作。
实现AR模式在MP中有两个基本要求:
- 实体类继承Model类
//如果不加下面第二个注解,会报一个警告(自动调用equals和hashcode方法,没有调用父类的)
@Data
@EqualsAndHashCode(callSuper = false)
public class User extends Model<User> {...
}
- 必须存在原始的Mapper接口并继承BaseMapper
public interface UserMapper extends BaseMapper<User> {...
}
实战演练:
package com.mp.first;import com.mp.first.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)
@SpringBootTest
public class ARTest {/*** 这里就不用再注入userMapper了,直接操作实体类就好*/@Testpublic void insert(){User user = new User();user.setName("老刘");user.setAge(18);user.setEmail("liu@qq.com");boolean insert = user.insert();System.out.println("插入是否成功:"+insert);}@Testpublic void selectById(){User user = new User();User userSelect = user.selectById(1321469479649107970L);System.out.println("查到了:"+userSelect);}@Testpublic void selectById2(){User user = new User();user.setId(1321469479649107970L);User userSelect = user.selectById();System.out.println("查到了:"+userSelect);}@Testpublic void updateById(){User user = new User();user.setId(1321469479649107970L);user.setName("老王");boolean userSelect = user.updateById();System.out.println(userSelect);}@Testpublic void deleteById(){User user = new User();user.setId(1321469479649107970L);boolean userSelect = user.deleteById();System.out.println(userSelect);}// SELECT id,name,age,email FROM user WHERE id=?
// UPDATE user SET name=?, age=?, email=? WHERE id=?@Testpublic void insertOrUpdate(){User user = new User();//不传入id时,是做新增操作user.setName("老牛");user.setAge(58);user.setEmail("laoniu@qq.com");//传入id时,会先在数据库中找;如果没有此id,怎做新增操作,如果有此id则做修改操作user.setId(1321473206095212546L);boolean insert = user.insertOrUpdate();System.out.println(insert);}
}