为什么要学习它呢?MyBatisPlus可以节省我们大量工作时间,所有的CRUD代码它都可以自动化完成!
愿景 我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
官网地址:https://mybatis.plus/guide/quick-start.html 使用第三方组件: 1、导入对应依赖 2、研究依赖如何配置 3、代码如何编写 4、提高扩展技术能力
1、创建数据库 mybatis_plus 2、创建user表 3、编写项目,初始化项目!使用SpringBoot初始化! 4、导入依赖
创建 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) ); 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');编写项目,初始化项目!使用SpringBoot初始化! 添加依赖
<properties> <java.version>1.8</java.version> <mybatis-plus.version>3.3.2</mybatis-plus.version> </properties> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.19</version> </dependency> <!--druid数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!--添加 代码生成器 依赖--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>说明:我们使用 mybatis-plus 可以节省我们大量的代码,尽量不要同时导入 mybatis 和 mybatis-plus!版本的差异!
5、连接数据库!这一步和 mybatis 相同!
# mysql 5 驱动不同 com.mysql.jdbc.Driver # mysql 8 驱动不同com.mysql.cj.jdbc.Driver、需要增加时区的配置 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.0.113:33608/test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8 username: root password: 1234566、传统方式pojo-dao(连接mybatis,配置mapper.xml文件)-service-controller 6、使用了mybatis-plus 之后
pojo
@Data @AllArgsConstructor @NoArgsConstructor public class User { @TableId(type = IdType.AUTO) private Long id; private String name; private Integer age; private String email; }mapper接口
@Repository public interface UserMapper extends BaseMapper<User> { }在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹:
@SpringBootApplication @MapperScan(basePackages = {"com.akieay.akieayblog.admin.*.mapper"}) public class AkieayblogApplication { public static void main(String[] args) { SpringApplication.run(AkieayblogApplication.class, args); } }测试类中测试
@SpringBootTest class AkieayblogApplicationTests { @Autowired UserMapper userMapper; @Test public void testList(){ List<User> list = userMapper.selectList(null); list.forEach(System.out::println); } }结果
我们所有的sql现在是不可见的,我们希望知道它是怎么执行的,所以我们必须要看日志!
mybatis-plus: configuration: #指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl配置完毕日志之后,后面的学习就需要注意这个自动生成的SQL,你们就会喜欢上 MyBatis-Plus!
Insert插入
@Test public void contextLoads() { User user = new User(); user.setName("测试插入"); user.setAge(3); user.setEmail("test@qq.com"); int result = userMapper.insert(user); // 帮我们自动生成id System.out.println(result); // 受影响的行数 System.out.println(user); // 发现,id会自动回填 }数据库插入的id的默认值为:全局的唯一id
默认 ID_WORKER 全局唯一id
分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html
雪花算法:
snowflflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯 一!
主键自增
我们需要配置主键自增:
1、实体类字段上 @TableId(type = IdType.AUTO) 2、数据库字段一定要是自增! 3、再次测试插入即可!
其他主键生成策略 的源码解释
public enum IdType { AUTO(0), // 数据库id自增 NONE(1), // 未设置主键 INPUT(2), // 手动输入 ID_WORKER(3), // 默认的全局唯一id UUID(4), // 全局唯一id uuid ID_WORKER_STR(5); //ID_WORKER 字符串表示法 }所有的sql都是自动帮你动态配置的!
创建时间、修改时间!这些个操作一遍都是自动化完成的,我们不希望手动更新!阿里巴巴开发手册:所有的数据库表:gmt_create、gmt_modifified几乎所有的表都要配置上!而且需要自动化!
方式一:数据库级别(工作中不允许你修改数据库)
1、在表中新增字段 gmt_create、gmt_modifified,并设置默认值为 CURRENT_TIMESTAMP,且修改时间根据时间戳更新 2、再次测试插入方法,我们需要先把实体类同步!
private Date gmtCreate; private Date gmtModifified;3、再次更新查看结果即可
方式二:代码级别
1、删除数据库的默认值、更新操作!
2、实体类字段属性上需要增加注解
/** * 自动填充 */ @TableField(fill = FieldFill.INSERT) private Date gmtCreate; @TableField(fill = FieldFill.INSERT_UPDATE) private Date gmtModifified;填充类型
public enum FieldFill { /** * 默认不处理 */ DEFAULT, /** * 插入填充字段 */ INSERT, /** * 更新填充字段 */ UPDATE, /** * 插入和更新填充字段 */ INSERT_UPDATE }3、编写处理器来处理这个注解即可!
/** * 元对象处理器接口 * @author akieay */ @Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ...."); this.strictInsertFill(metaObject, "gmtCreate", Date.class, new Date()); this.strictInsertFill(metaObject, "gmtModifified", Date.class, new Date()); } @Override public void updateFill(MetaObject metaObject) { log.info("start update fill ...."); this.strictInsertFill(metaObject, "gmtModifified", Date.class, new Date()); } }4、测试插入 5、测试更新、观察时间即可
在面试过程中,我们经常会被问道乐观锁,悲观锁!这个其实非常简单!
乐观锁:故名思意十分乐观,它总是认为不会出现问题,无论干什么不去上锁!如果出现了问题,再次更新值测试 悲观锁:故名思意十分悲观,它总是认为总是出现问题,无论干什么都会上锁!再去操作!
我们这里主要讲解 乐观锁机制!
乐观锁实现方式:
取出记录时,获取当前 version更新时,带上这个version执行更新时, set version = newVersion where version = oldVersion如果version不对,就更新失败 乐观锁:1、先查询,获得版本号 version = 1 -- A update user set name = "testA", version = version + 1 where id = 2 and version = 1 -- B 线程抢先完成,这个时候 version = 2,会导致 A 修改失败! update user set name = "testB", version = version + 1 where id = 2 and version = 1测试一下MP的乐观锁插件
1、给数据库中增加version字段! 2、我们实体类加对应的字段
/** * 乐观锁Version注解 */ @Version private Integer version;3、注册组件
/** * MyBatisPlus配置类 * @author akieay * 扫描包 启用事务管理 配置类 */ @MapperScan(basePackages = {"com.akieay.akieayblog.admin.*.mapper"}) @EnableTransactionManagement @Configuration public class MyBatisPlusConfig { /** * 注册乐观锁插件 * @return */ @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); }4、测试一下!
// 测试乐观锁成功单元 @Test public void testOptimisticLocker() { User user = userMapper.selectById(3L); user.setName("测试乐观锁"); user.setAge(10); userMapper.updateById(user); } // 测试乐观锁失败!多线程下 @Test public void testOptimisticLocker2() { User user = userMapper.selectById(3L); user.setName("线程1"); user.setAge(10); User userTwo = userMapper.selectById(3L); userTwo.setName("插入线程2"); userTwo.setEmail("111111"); userMapper.updateById(userTwo); userMapper.updateById(user); }MP 其实也内置了分页插件!
1、配置拦截器组件即可
/** * 分页插件 * @return */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false // paginationInterceptor.setOverflow(false); // 设置最大单页限制数量,默认 500 条,-1 不受限制 // paginationInterceptor.setLimit(500); // 开启 count 的 join 优化,只针对部分 left join paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true)); return paginationInterceptor; }2、直接使用Page对象即可!
@Test public void testPage() { Page<User> page = new Page<>(2, 5); userMapper.selectPage(page, null); page.getRecords().forEach(System.out::println); System.out.println(page.getTotal()); }物理删除 :从数据库中直接移除 逻辑删除 :再数据库中没有被移除,而是通过一个变量来让他失效! deleted = 0 => deleted = 1
管理员可以查看被删除的记录!防止数据的丢失,类似于回收站! 测试一下: 1、在数据表中增加一个 deleted 字段 2、实体类中增加属性
/** * 逻辑删除 */ @TableLogic private Integer deleted;3、配置!
mybatis-plus: global-config: db-config: logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)以下配置 mybatis-plus 3.3.2 已不再需要
// 逻辑删除组件! @Bean public ISqlInjector sqlInjector() { return new LogicSqlInjector(); }4、测试一下删除!
@Test public void testDelete(){ userMapper.deleteById(1278987353129500675L); }记录依旧在数据库,但是值确已经变化了!
并且执行查询语句的时候,会自动过滤掉已经逻辑删除的数据
我们在平时的开发中,会遇到一些慢sql。测试! druid,作用:性能分析拦截器,用于输出每条 SQL 语句及其执行时间,MP也提供性能分析插件,如果超过这个时间就停止运行!
该插件已于3.2.0及以上版本失效, 可以使用 "执行sql分析打印功能" 代替 1、导入插件
/** * SQL执行效率插件 3.1及以下版本有效 3.2已移除 * 设置 dev test 环境开启 */ @Bean @Profile({"dev","test"}) public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); // ms设置sql执行的最大时间,如果超过了则不 执行 performanceInterceptor.setMaxTime(100); // 是否格式化代码 performanceInterceptor.setFormat(true); return performanceInterceptor; }记住,要在SpringBoot中配置环境为dev或者 test 环境!
2、测试使用!
@Test public void testList(){ List<User> list = userMapper.selectList(null); list.forEach(System.out::println); }使用性能分析插件,可以帮助我们提高效率!
太多就不一 一列举了;可自行参考官方文档使用 1、测试一,记住查看输出的SQL进行分析
@Test public void contextLoads() { // 查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.isNotNull("name").isNotNull("email").ge("age",12); userMapper.selectList(wrapper).forEach(System.out::println); }2、测试二,记住查看输出的SQL进行分析
@Test void test2(){ // 查询名字狂神说 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("name","测试插入"); User user = userMapper.selectOne(wrapper); // 查询一个数据,出现多个结果使用List 或者 Map System.out.println(user); }3、测试三,记住查看输出的SQL进行分析
@Test void test3(){ // 查询年龄在 20 ~ 30 岁之间的用户 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.between("age",20,30); // 区间 Integer count = userMapper.selectCount(wrapper);// 查询结果数 System.out.println(count); }4、测试四,记住查看输出的SQL进行分析
// 模糊查询 @Test void test4(){ // 查询年龄在 20 ~ 30 岁之间的用户 QueryWrapper<User> wrapper = new QueryWrapper<>(); // 左和右 t% wrapper .notLike("name","e").likeRight("email","t"); List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); }5、测试五()
// 模糊查询 @Test void test5(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); // id 在子查询中查出来 wrapper.inSql("id","select id from user where id<3"); List<Object> objects = userMapper.selectObjs(wrapper); objects.forEach(System.out::println); }6、测试六
//测试六 @Test void test6(){ QueryWrapper<User> wrapper = new QueryWrapper<>(); // 通过id进行排序 wrapper.orderByAsc("id"); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }其余的测试,可以自己下去多练习!
官方文档:https://mybatis.plus/guide/generator.html#%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B
特别注意: MyBatis-Plus 从 3.0.3 之后移除了代码生成器与模板引擎的默认依赖,需要手动添加相关依赖:
添加 代码生成器 依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.3.2</version> </dependency>添加 模板引擎 依赖,MyBatis-Plus 支持 Velocity(默认)、Freemarker、Beetl,用户可以选择自己熟悉的模板引擎,如果都不满足您的要求,可以采用自定义模板引擎。
Velocity(默认): <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.2</version> </dependency> Freemarker: <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> Beetl: <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl</artifactId> <version>3.1.8.RELEASE</version> </dependency>代码生成工具类
/** * @author akieay */ public class CodeAutomaticGenerator { public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); //当前项目路径 String projectPath = System.getProperty("user.dir"); //输出目录 gc.setOutputDir(projectPath + "/src/main/java"); //作者 gc.setAuthor("akieay"); //禁止打开资源管理器 gc.setOpen(false); //是否覆盖 gc.setFileOverride(false); //去掉Service的I 前缀 gc.setServiceName("%sService"); //设置主键生成策略 gc.setIdType(IdType.AUTO); //设置日期格式 gc.setDateType(DateType.ONLY_DATE); //实体属性 Swagger2 注解 gc.setSwagger2(true); mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://192.168.0.113:33608/akieay_blog?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("blog"); pc.setParent("com.akieay.akieayblog.admin"); pc.setEntity("entity"); pc.setMapper("mapper"); pc.setService("service"); pc.setController("controller"); mpg.setPackageInfo(pc); //策略配置 StrategyConfig strategy = new StrategyConfig(); //设置映射表名 strategy.setInclude("ay_article","ay_article_comments","ay_article_reply"); //设置标名下划线转驼峰 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); mpg.setStrategy(strategy); //执行 mpg.execute(); } }