SpringBoot---MyBatisPlus简单配置与使用

    技术2024-01-09  115

    MyBatisPlus


    为什么要学习它呢?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: 123456

    6、传统方式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!

    CRUD扩展


    插入操作

    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 字符串表示法 }

    更新操作

    @Test public void contextLoads() { User user = new User(); user.setId(5L); user.setName("测试更新"); user.setAge(23); user.setEmail("test@qq.com"); int i = userMapper.updateById(user); System.out.println(i); }

    所有的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); }

    查询操作

    // 测试查询 @Test public void testSelectById(){ User user = userMapper.selectById(1L); System.out.println(user); } // 测试批量查询! @Test public void testSelectByBatchId(){ List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3)); users.forEach(System.out::println); } // 按条件查询之一使用map操作 @Test public void testSelectByBatchIds(){ HashMap<String, Object> map = new HashMap<>(); // 自定义要查询 map.put("name","测试插入"); map.put("age",3); List<User> users = userMapper.selectByMap(map); users.forEach(System.out::println); }

    分页查询

    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()); }

    删除操作

    // 测试删除 @Test public void testDeleteById(){ userMapper.deleteById(1278987353129500674L); } // 通过id批量删除 @Test public void testDeleteBatchId(){ userMapper.deleteBatchIds(Arrays.asList(1L, 2L)); } // 通过map删除 @Test public void testDeleteMap(){ HashMap<String, Object> map = new HashMap<>(); map.put("name","测试更新22"); userMapper.deleteByMap(map); }

    逻辑删除

    物理删除 :从数据库中直接移除 逻辑删除 :再数据库中没有被移除,而是通过一个变量来让他失效! 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(); } }
    Processed: 0.016, SQL: 9