SpringBoot快速集成Mybatis-plus常用功能

    技术2022-07-14  72

    Mybatis-plus 常用功能集成

    本项目使用 使用 Spring Initializer 初始化的 Spring Boot 工程; 版本:2.3.1

    MyBatis-Plus官方地址:MyBatis-Plus 版本:3.1.0

    示例项目Github地址:scaffold-project

    文章目录

    Mybatis-plus 常用功能集成1、基本使用2、代码生成器3、CRUD接口4、分页插件5、逻辑删除6、通用枚举7、自动填充功能8、SQL性能分析9、多数据源

    1、基本使用

    添加maven依赖

    mybatis-plus 基础依赖

    <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.0</version> </dependency>

    添加 代码生成器 依赖

    <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.1.0</version> </dependency>

    添加 模板引擎 依赖 (如果需要自定义代码生成模板), MyBatis-Plus 支持 Velocity(默认)使用其他自定义模板引擎需要额外配置

    <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.2</version> </dependency>

    在application.yml配置文件中添加基础配置

    # 基础 DataSource Config spring: datasource: druid: url: jdbc:mysql://localhost:3306/scaffold?useUnicode=true&characterEncoding=utf8&useSSL=false username: mysql password: mysql driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 更多配置 # ====================MybatisPlus Config==================== mybatis-plus: # 如果是放在src/main/java目录下 classpath:/com/yourpackage/*/mapper/*Mapper.xml # 如果是放在resource目录 classpath*:/mapper/*Mapper.xml mapper-locations: classpath:/com/jasper/scaffold/**/mapper/xml/*Mapper.xml #实体扫描,多个package用逗号或者分号分隔 typeAliasesPackage: com.jasper.scaffolds.*.entity global-config: key-generator: com.baomidou.mybatisplus.incrementer.OracleKeyGenerator #主键类型 0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID"; id-type: 2 #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断" field-strategy: 2 #驼峰下划线转换 db-column-underline: true db-config: #mp2.3+ 全局表前缀 mp_ #table-prefix: mp_ #刷新mapper 调试神器 #refresh-mapper: true #数据库大写下划线转换 #capital-mode: true # 逻辑删除配置 logic-delete-field: mark # 全局逻辑删除字段 实体类上有 @TableLogic 则以实体上的为准,忽略全局 logic-delete-value: 1 logic-not-delete-value: 0 #自定义填充策略接口实现 #meta-object-handler: com.baomidou.springboot.MyMetaObjectHandler configuration: #配置返回数据库(column下划线命名&&返回java实体是驼峰命名),自动匹配无需as(没开启这个,SQL需要写as: select user_id as userId) map-underscore-to-camel-case: true cache-enabled: false #配置JdbcTypeForNull, oracle数据库必须配置 jdbc-type-for-null: 'null' #===================================================================================

    启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹

    @SpringBootApplication @MapperScan("com.jasper.scaffold.*.mapper") public class ScaffoldApplication { public static void main(String[] args) { SpringApplication.run(ScaffoldApplication.class, args); } }

    测试数据库 scaffold Table: User

    -- 数据库: scaffold DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` bigint(20) NOT NULL COMMENT '主键ID', `name` varchar(30) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年龄', `email` varchar(50) DEFAULT NULL COMMENT '邮箱', `cTime` datetime DEFAULT NULL COMMENT '创建时间', `uTime` datetime DEFAULT NULL COMMENT '修改时间', `mark` tinyint(1) DEFAULT NULL COMMENT '数据有效标示位 1无效 0有效', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表实体';

    2、代码生成器

    通过MyBatis-Plus 的代码生成器AutoGenerator,通过数据库表 直接快速生成tity、Mapper、Mapper XML、Service、Controller 等各个模块的代码

    单独创建MpGenerator生成类,执行 main 方法生成指定 table 对应的模块代码

    更多属性及配置请查看:更多配置

    // 自定义代码生成类 demo public class MpGenerator { public static void main(String[] args) { //fixme 生成的java文件路径 String javaPath = "/xxx/git-hub/Jasper/scaffold/src/main/java"; //fixme 包名 String packgeName = "com.jasper.scaffold.api"; //fixme mysql数据库配置 String dbusername = "mysql"; String dbpassword = "mysql"; String dbip = "localhost"; String dbname = "scaffold"; // 各种配置 AutoGenerator mpg = new AutoGenerator(); // 设置数据源 mpg.setDataSource(new DataSourceConfig() .setDriverName("com.mysql.jdbc.Driver") // 设置数据库类型 .setDbType(DbType.MYSQL) .setUsername(dbusername) .setPassword(dbpassword) .setUrl(String.format("jdbc:mysql://%s:3306/%s?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull", dbip, dbname)) ); // 全局配置 mpg.setGlobalConfig(new GlobalConfig() // 输出目录 .setOutputDir(javaPath) // 是否覆盖 .setFileOverride(true) // 开启AR模式 .setActiveRecord(true) // XML二级缓存 .setEnableCache(false) // 生成ResultMap .setBaseResultMap(true) // 生成 sql片段 .setBaseColumnList(true) // 自动打开生成后的文件夹 .setOpen(true) // 所有文件的生成者 .setAuthor("codegenerator") .setDateType(DateType.ONLY_DATE) // 自定义文件命名,%s会自动填充表实体类名字 .setMapperName("%sMapper") .setXmlName("%sMapper") .setServiceName("I%sService") .setServiceImplName("%sServiceImpl") .setControllerName("%sController") ); // 策略配置 mpg.setStrategy(new StrategyConfig() // fixme 添加需要生成的表 .setInclude("user") // 实体类使用Lombok .setEntityLombokModel(true) // 表名生成策略,下划线转驼峰 .setNaming(NamingStrategy.underline_to_camel) .setRestControllerStyle(true) .setSuperControllerClass("com.jasper.scaffold.common.mvc.BaseController") .setLogicDeleteFieldName("mark") ); // 包配置 mpg.setPackageInfo(new PackageConfig() // 基本包路径 .setParent(packgeName) // 设置Controller包名 .setController("web") // 设置entity包名 .setEntity("entity") // 设置Mapper包名 .setMapper("mapper") // 设置Service包名 .setService("service") // 设置Service实现类包名 .setServiceImpl("service.impl") // 设置Mapper.xml包名 .setXml("mapper.xml") ); // 如果需要定制 代码生成模板 可以 自己写代码模板 如: 自定义 VO模板 List<FileOutConfig> list = new ArrayList<>(); list.add(new FileOutConfig("/templates/codegenerator/vo.java.vm") { // 自定义vo输出路径 @Override public String outputFile(TableInfo tableInfo) { String voPath = javaPath.concat("/").concat(packgeName.replaceAll("[.]", "/")) .concat("/").concat("vo").concat("/") .concat(tableInfo.getEntityName()).concat("VO.java"); log.info("voPath:{}", voPath); return voPath; } }); // list.add(new FileOutConfig("/templates/mapper.xml.vm") { // // 自定义Mapper.xml输出路径 // @Override // public String outputFile(TableInfo tableInfo) { // return "/Users/liqingchao/work/xingyun_pmp/project_be/project_be/proj-server/src/main/java/com/jd/jacp/proj/project/mapper/xml/" + tableInfo.getEntityName() + "Mapper.xml"; // } // }); // 注入自定义配置 mpg.setCfg(new InjectionConfig() { @Override public void initMap() { // 注入自定义 Map 对象(注意需要setMap放进去) Map<String, Object> map = new HashMap<>(1); map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp"); this.getConfig().getPackageInfo().put("Vo", mpg.getPackageInfo().getParent() + ".vo"); this.setMap(map); } }.setFileOutConfigList(list)); mpg.execute(); } }

    3、CRUD接口

    Mapper层 CRUD接口简单示例: 更多接口列表详情

    通用 CRUD 封装BaseMapper接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器泛型 T 为任意实体对象参数 Serializable 为任意类型主键 Mybatis-Plus 不推荐使用复合主键约定每一张表都有自己的唯一 id 主键对象 Wrapper 为 条件构造器方法名均为这四种关键字:Insert Delete Update Select // 插入一条记录 int insert(T entity); // 根据 entity 条件,删除记录 int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); // 根据 ID 修改 int updateById(@Param(Constants.ENTITY) T entity); // 根据 ID 查询 T selectById(Serializable id); // 根据 entity 条件,查询一条记录 T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    Service层 CRUD接口简单示例:

    说明:

    通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,泛型 T 为任意实体对象建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类对象 Wrapper 为 条件构造器

    基础接口列表详情参见:

    基础接口列表详情

    链式查询 测试

    // 链式查询 普通 QueryChainWrapper<T> query(); // 链式查询 lambda 式。注意:不支持 Kotlin LambdaQueryChainWrapper<T> lambdaQuery(); @Test void testChainQuery() { // 链式查询 普通 User user = userService.query().eq("name", "jasper").one(); // 链式查询 lambda 式 User user2 = userService.lambdaQuery().eq(User::getName, "jasper").one(); System.out.println(user); System.out.println(user2); }

    链式更新 测试

    // 链式更改 普通 UpdateChainWrapper<T> update(); // 链式更改 lambda 式。注意:不支持 Kotlin LambdaUpdateChainWrapper<T> lambdaUpdate(); @Test void testChainUpdate() { // 链式更改 普通 String queryVal = "zhangsan"; userService.update().eq("name", queryVal).remove(); // 链式更改 lambda 式 User updateEntity = new User().setEmail("1111@qq.com"); userService.lambdaUpdate().eq(User::getName, "lisi").update(updateEntity); }

    4、分页插件

    在配置类中 开启分页插件, 支持额外的分页配置,如:最大单页数量限制

    @Configuration @EnableTransactionManagement public class MybatisPlusConfig { /** * 分页插件 * @return */ @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } }

    测试分页查询

    @Test void testPagination() { Page<User> page = new Page<>(1, 5); page.setDesc("age"); IPage<User> userPage = userMapper.selectPage(page, Wrappers.<User>query().gt("age", 5)); log.error("总条数 -------------> {}", userPage.getTotal()); log.error("当前页数 -------------> {}", userPage.getCurrent()); log.error("当前每页显示数 -------------> {}", userPage.getSize()); List<User> records = userPage.getRecords(); log.error("records: {}", records); }

    测试lambda分页查询

    @Test void lambdaPagination() { Page<User> page = new Page<>(1, 3); IPage<User> result = userMapper.selectPage(page, Wrappers.<User>lambdaQuery().ge(User::getAge, 1).orderByAsc(User::getAge)); log.error("总条数 -------------> {}", result.getTotal()); log.error("当前页数 -------------> {}", result.getCurrent()); log.error("当前每页显示数 -------------> {}", result.getSize()); List<User> records = result.getRecords(); log.error("records: {}", records); }

    5、逻辑删除

    ​ mybatis-plus 支持数据的逻辑删除,当数据库记录设计是通过 某个字段来标示 数据是否有效或者是否已删除,而不是真正的物理删除时, 可以通过配置,将mybatis-plus默认的删除变成逻辑删除;并且查询方法也会默认带上逻辑删除标示,即只查询 未删除的数据 (自己扩展的xml不会),当有需求 就是需要查询逻辑删除的数据时,可手写查询 (既然设计了逻辑删除,一般不会查询已删除的数据)

    application.yml加入配置

    mybatis-plus: global-config: db-config: logic-delete-field: flag #全局逻辑删除字段值3.3.0开始支持 flag可以修改为自己的 逻辑标示字段 logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

    实体类字段上加上@TableLogic注解

    /** * 数据有效标示位 0有效 1无效 */ @TableLogic private Boolean mark;

    Tip:

    字段支持所有数据类型(推荐使用 Integer,Boolean,LocalDateTime)如果使用LocalDateTime,建议逻辑未删除值设置为字符串null,逻辑删除值只支持数据库函数例如now()使用mp自带方法删除和查找都会附带逻辑删除功能 (自己写的xml不会)如果实体类上有 @TableLogic 则以实体上的为准,忽略全局。 即先查找注解再查找全局,都没有则此表没有逻辑删除

    6、通用枚举

    在一些情况下,我们希望实体的字段是固定的几个值,比如:User用户表的性别(Gender),我们希望是固定的Male,Female 我们可能在设计数据库字段时,采用了int(1):

    1: 男2: 女

    如果实体使用了Integer,那么无法保证新增时传入的参数是1/2;此种场景就可以用通用枚举来处理

    在application.yml中添加 配置枚举类扫描路径

    mybatis-plus: # 支持统配符 * 或者 ; 分割 type-enums-package: com.jasper.scaffold.api.entity.enums ...

    先定义一个性别的枚举类

    @Getter public enum GenderEnum { MALE(1, "男"), FEMALE(2, "女"); private final int code; // 数据库中 存储的为 code private final String descp; GenderEnum(int code, String descp){ this.code = code; this.descp = descp; } }

    定义用户实体时,使用枚举类型定义 性别

    /** * 性别 */ private GenderEnum gender; // 省略其他。。

    添加枚举配置

    方式1: 使用 @EnumValue 注解枚举属性

    @EnumValue //标记数据库存的值是code private final int code;

    方式2:枚举属性,实现 IEnum 接口

    public enum GenderEnum implements IEnum<Integer>{ MALE(1, "男"), FEMALE(2, "女"); private final int code; // 数据库中 存储的为 code private final String descp; @Override public Integer getValue() { return this.code; } GenderEnum(int code, String descp){ this.code = code; this.descp = descp; } }

    测试枚举使用

    @Test void testEnum() { User user = new User() .setGender(GenderEnum.MALE) .setAge(12) .setEmail("1234@11.com") .setName("Name_y") .setMark(false); // false 未删除 // 插入 userMapper.insert(user); // 查询 List<User> userList = userService.lambdaQuery().eq(User::getGender, GenderEnum.MALE).list(); log.error("用户数:{}", userList.size()); } // 用户数:2

    7、自动填充功能

    ​ 当我们在插入、修改数据库记录时,想要填充某个字段的值时:比如 User表的 cTime、uTime字段,当新增数据时,自动设置 cTime、uTime为当前时间,当更新修改数据时,自动修改uTime为当前时间,这个时候就可以使用自动填充功能

    实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler

    @Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.debug("start insert fill ...."); // 起始版本 3.3.0(推荐使用) // this.strictInsertFill(metaObject, "cTime", Date.class, new Date()); this.setFieldValByName("cTime", new Date(), metaObject); } @Override public void updateFill(MetaObject metaObject) { log.debug("start update fill ...."); // 起始版本 3.3.0(推荐使用) // this.strictUpdateFill(metaObject, "uTime", Date.class, new Date()); this.setFieldValByName("uTime", new Date(), metaObject); } }

    注解填充字段 @TableField(.. fill = FieldFill.INSERT)

    /** * 创建时间 */ @TableField(value = "cTime", fill = FieldFill.INSERT) private Date cTime; /** * 修改时间 */ @TableField(value = "uTime", fill = FieldFill.UPDATE) private Date uTime;

    Tip:

    字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段

    填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component或@Bean注入

    要想根据注解FieldFill.xxx和字段名以及字段类型来区分必须使用父类的strictInsertFill或者strictUpdateFill方法

    不需要根据任何来区分可以使用父类的fillStrategy方法

    字段类型是 LocalDateTime时, 集成druid数据源,使用3.1.0之前版本没问题,升级mp到3.1.1+后,运行时报错:java.sql.SQLFeatureNotSupportedException

    8、SQL性能分析

    ​ 可以通过自带的SQL性能分析插件,自动打印出执行的SQL以及执行时间,方便排查问题以及优化性能较慢的SQL,可以设置Profile只在 dev、test环境下启动

    在MybatisPlusConfig启动SQL性能分析插件

    /** * SQL 性能分析插件 * @return */ @Bean @Profile({"dev", "test"}) public PerformanceInterceptor performanceInterceptor() { return new PerformanceInterceptor(); }

    自动分析执行SQL

    Time:39 ms - ID:com.jasper.scaffold.api.mapper.UserMapper.selectList Execute SQL:SELECT id,name,gender,age,email,cTime,uTime,mark FROM user WHERE gender = 1

    9、多数据源

    ​ dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器,可实现快速切换多个数据源: 详情

    引入Maven包

    <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>${version}</version> </dependency>

    配置多个数据源

    spring: datasource: dynamic: primary: master #设置默认的数据源或者数据源组,默认值即为master strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源. datasource: master: url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver slave_1: url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver slave_2: url: ENC(xxxxx) # 内置加密,使用请查看详细文档 username: ENC(xxxxx) password: ENC(xxxxx) driver-class-name: com.mysql.jdbc.Driver schema: db/schema.sql # 配置则生效,自动初始化表结构 data: db/data.sql # 配置则生效,自动初始化数据 continue-on-error: true # 默认true,初始化失败是否继续 separator: ";" # sql默认分号分隔符 #......省略 #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2

    使用 @DS 切换数据源

    @DS 可以注解在方法上和类上,同时存在方法注解优先于类上注解。建议只注解在service实现上

    示例

    @Service @DS("slave") public class UserServiceImpl implements UserService { @Autowired private JdbcTemplate jdbcTemplate; public List<Map<String, Object>> selectAll() { return jdbcTemplate.queryForList("select * from user"); } @Override @DS("slave_1") public List<Map<String, Object>> selectByCondition() { return jdbcTemplate.queryForList("select * from user where age >10"); } }
    Processed: 0.016, SQL: 9