Spring Boot整合MyBatis-Plus(萌新入门-自用总结-核心功能-热门插件)

    技术2022-07-10  134

    MyBatis-Plus之快速启动

    MyBatis-Pluis官网 在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹:图中blog具体看项目

    启动类注解:

    @MapperScan("com.example.blog.mapper")

    1. 核心功能——代码生成器

    1.1 添加依赖

    配置自动生成时的错误:mybatis-plus自动生成的时候报错 java.lang.NoClassDefFoundError

    参考 https://blog.csdn.net/wangjinb/article/details/106488308

    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>

    注意!如果您选择了非默认引擎,需要在 AutoGenerator 中 设置模板引擎。

    AutoGenerator generator = new AutoGenerator(); // set freemarker engine generator.setTemplateEngine(new FreemarkerTemplateEngine()); // set beetl engine generator.setTemplateEngine(new BeetlTemplateEngine()); // set custom engine (reference class is your custom engine class) generator.setTemplateEngine(new CustomTemplateEngine()); // other config ...

    1.2 编写生成文件:

    生成文件 CodeCreate.java

    package com.example.CodeGenerator; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.generator.InjectionConfig; import com.baomidou.mybatisplus.generator.config.*; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.mysql.cj.xdevapi.Table; //代码生成器 public class CodeCreate { public static void main(String[] args){ //DataSourceConfig 数据源配置,通过该配置,指定需要生成代码的具体数据库 //StrategyConfig 数据库表配置,通过该配置,可指定需要生成哪些表或者排除哪些表 //PackageConfig 包名配置,通过该配置,指定生成代码的包路径 //TemplateConfig 模板配置,可自定义代码生成的模板,实现个性化操作 //GlobalConfig 全局策略配置,具体请查看 //InjectionConfig 注入配置,通过该配置,可注入自定义参数等操作以实现个性化操作 AutoGenerator mpg = new AutoGenerator();//构建一个代码自动生成器对象 String projectPath = System.getProperty("user.dir"); //1.全局配置 -> GlobalConfig GlobalConfig gc = new GlobalConfig(); gc.setAuthor("lzy"); //开发人员,默认null gc.setActiveRecord(false);//开启 ActiveRecord 模式 gc.setBaseResultMap(false);//开启 BaseResultMap gc.setBaseColumnList(false);//开启 baseColumnList gc.setDateType(DateType.ONLY_DATE); //时间类型对应策略,默认值:TIME_PACK gc.setIdType(IdType.ID_WORKER);//指定生成的主键的ID类型 gc.setEnableCache(false);//是否在xml中添加二级缓存配置 gc.setFileOverride(true);//是否覆盖已有文件,默认false gc.setKotlin(false);//开启 Kotlin 模式 gc.setOpen(false); //是否打开输出目录 gc.setSwagger2(true);//开启 swagger2 模式 gc.setOutputDir(projectPath+"/src/main/java");//生成文件的输出目录 gc.setControllerName("%sController");//controller 命名方式 gc.setEntityName("%sEntity");//实体命名方式 gc.setMapperName("%sMapper");//mapper 命名方式 gc.setServiceName("%sService");//service 命名方式,%s填充实体属性,%s为占位符 gc.setServiceImplName("%sServiceImpl");//service impl 命名方式 gc.setXmlName("%sXml");//Mapper xml 命名方式 mpg.setGlobalConfig(gc); //2.配置数据源 -> dataSourceConfig DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT+8"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); //驱动名称 dsc.setUsername("root"); //数据库连接用户名 dsc.setPassword("123456"); //数据库连接密码 dsc.setDbType(DbType.MYSQL); //数据库类型,该类内置了常用的数据库类型 //dsc.setDbQuery()数据库信息查询类,默认由 dbType 类型决定选择对应数据库内置实现,实现 IDbQuery 接口自定义数据库查询 SQL 语句 定制化返回自己需要的内容 //dsc.setKeyWordsHandler(); //dsc.setSchemaName(); 数据库 schema name //dsc.setTypeConvert();类型转换,默认由 dbType 类型决定选择对应数据库内置实现 mpg.setDataSource(dsc); //4.PackageConfig -> 包的相关配置 PackageConfig pc = new PackageConfig(); pc.setParent("com.example"); //父包名。如果为空,将下面子包名必须写全部, 否则就只需写子包名 pc.setModuleName("blog"); //父包模块名 pc.setEntity("entity"); //Entity目录名 pc.setMapper("mapper"); //Mapper目录名 pc.setController("controller"); //Controller目录名 pc.setService("service"); //Service目录名 pc.setServiceImpl("serviceImpl");//Service Impl包名 pc.setXml("xml"); // Mapper XML包名 //pc.setPathInfo() 路径配置信息 mpg.setPackageInfo(pc); //3.数据库表配置 -> StrategyConfig StrategyConfig strategy = new StrategyConfig(); strategy.setInclude("User"); //设置映射表名 strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); //strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!"); strategy.setEntityLombokModel(true); //自动lombok strategy.setRestControllerStyle(true);//生成 @RestController 控制器 strategy.setLogicDeleteFieldName("deleted");//逻辑删除名字 //isCapitalMode是否大写命名,skipView是否跳过视图, mpg.setStrategy(strategy); //5.模板配置 -> TemplateConfig // TemplateConfig tc = new TemplateConfig(); // tc.setEntity(); // tc.setEntityKt(); // tc.setService(); // tc.setServiceImpl(); // tc.setMapper(); // tc.setXml(); // tc.setController(); // mpg.setPackageInfo(tc); //6.注入配置 -> injectionConfig mpg.execute(); //执行 } }

    1.3 我的目录结构:

    以数据表one为例运行CodeCreate可以生成如下图的目录结构

    简单的CRUD中service、impl、xml并未实际用处可以删除,编写一下controller层即可返回数据。复杂sql语句仍需要在xml中书写。 package com.example.blog.controller; import com.example.blog.entity.UserEntity; import com.example.blog.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * <p> * 前端控制器 * </p> * * @author lzy * @since 2020-07-01 */ @RestController public class UserController { @Autowired UserMapper userMapper; @RequestMapping(value = "/qurry") public UserEntity qurryOne(){ return userMapper.selectById(4L); } @RequestMapping(value = "/list") public List<UserEntity> queryAllUser(){ List<UserEntity> users = userMapper.selectList(null); return users; } @RequestMapping(value = "/insert/{name}/{age}/{email}") public String insertUser(@PathVariable("name")String name,@PathVariable("age")Integer age,@PathVariable("email")String email){ UserEntity user = new UserEntity(); user.setName(name); user.setAge(age); user.setEmail(email); userMapper.insert(user); return "insert is running"; } @RequestMapping(value = "/update/{id}/{name}/{age}/{email}") public String update(@PathVariable("id")Long id,@PathVariable("name")String name,@PathVariable("age")Integer age,@PathVariable("email")String email){ UserEntity user = new UserEntity(); user.setId(id); user.setName(name); user.setAge(age); user.setEmail(email); userMapper.updateById(user); return "update is running"; } @RequestMapping(value = "/delete/{id}") //删除 public String DeleteUserById(@PathVariable("id")Long id){ int r = userMapper.deleteById(id); return "Delete is running"; } }

    结果截图:如下图是访问/list得到 list里面拿到的json数据。

    2 插件扩展——逻辑删除

    2.1第一步配置.properties或者.yml文件

    application.properties文件配置如下:

    #配置逻辑删除 mybatis-plus.global-config.db-config.logic-delete-field= flag #全局逻辑删除字段值 3.3.0开始支持,详情看下面。 mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0

    application.yml文件配置如下:

    (如果你的默认值和mp默认的一样,该配置可无)

    global-config: db-config: logic-delete-field: flag #全局逻辑删除字段值 3.3.0开始支持,详情看下面。 logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

    2.2数据库表中添加字段deleted

    在数据库中书写deleted字段,默认为0,逻辑删除后变1。

    2.3 Entity层User/UserEntity文件下添加字段(@TableLogic)

    字段支持所有数据类型(推荐使用 Integer,Boolean,LocalDateTime)

    如果使用LocalDateTime,建议逻辑未删除值设置为字符串null

    逻辑删除值只支持数据库函数例如now() 效果

    使用mp自带方法删除和查找都会附带逻辑删除功能 (自己写的xml不会)

    设置逻辑删除,执行删除操作实际是更新操作,更新deleted为1,从而数据库可见,用户不可见

    example 删除 update user set deleted=1 where id =1 and deleted=0 查找 select * from user where deleted=0

    全局逻辑删除: begin 3.3.0

    如果公司代码比较规范,比如统一了全局都是flag为逻辑删除字段。

    使用此配置则不需要在实体类上添加 @TableLogic。

    但如果实体类上有 @TableLogic 则以实体上的为准,忽略全局。

    即先查找注解再查找全局,都没有则此表没有逻辑删除。

    mybatis-plus: global-config: db-config: logic-delete-field: flag #全局逻辑删除字段值

    逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。 如果你需要再

    查出来就不应使用逻辑删除,而是以一个状态去表示。 如: 员工离职,账号被锁定等都应该是一个

    状态字段,此种场景不应使用逻辑删除。

    若确需查找删除数据,如老板需要查看历史所有数据的统计汇总信息,请单独手写sql。

    3.条件构造器——( 查询、更新 )

    3.1编写测试类 WrapperTest

    test7中有每个选项的简单例子,可以参考

    package com.example; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.example.blog.mapper.UserMapper; import com.example.blog.entity.UserEntity; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; import java.util.Map; @SpringBootTest public class WrapperTest { @Autowired private UserMapper userMapper; @Test void test1(){ //查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12 QueryWrapper<UserEntity> wrapper = new QueryWrapper<>(); wrapper .isNotNull("name") .isNotNull("email") .ge("age",12); userMapper.selectList(wrapper).forEach(System.out::println); } @Test void test2(){ //查询名字风花雪月 QueryWrapper<UserEntity> wrapper = new QueryWrapper<>(); wrapper.eq("name","风花雪月"); UserEntity user = userMapper.selectOne(wrapper); System.out.println(user); } @Test void test3(){ //查询年龄在18-30 QueryWrapper<UserEntity> wrapper = new QueryWrapper<>(); wrapper.between("age",18,30); Integer count = userMapper.selectCount(wrapper); System.out.println(count); } @Test void test4(){ //查询名字没有e并且右边以t开头 QueryWrapper<UserEntity> wrapper = new QueryWrapper<>(); wrapper .notLike("name","e") .likeRight("email","t"); List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); } @Test void test5(){ //id在子查询中查询出来 QueryWrapper<UserEntity> wrapper = new QueryWrapper<>(); wrapper.inSql("id","select id from user where id>3"); List<Object> objects = userMapper.selectObjs(wrapper); objects.forEach(System.out::println); } @Test void test6(){ //通过id排序 QueryWrapper<UserEntity> wrapper = new QueryWrapper<>(); wrapper.orderByAsc("id"); List<UserEntity> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } @Test void test7() { //通过id排序 QueryWrapper<UserEntity> wrapper = new QueryWrapper<>(); //.allEq({id:1,name:"老王",age:null})->id=1 and name = '老王' and age is null wrapper .eq("id",15) //等于 .ne("id",15) //不等于 .gt("id",15)//大于 .ge("id",15)//大于等于 .lt("id",15)//小于 .le("id",15)//小于等于 .between("id",15,20)//介于15和20之间 .notBetween("id",15,20)//不介于15 和 20之间 .like("id","15")//匹配 .notLike("id","15")//不匹配 .likeLeft("","15")//左匹配 .likeRight("","15")//右匹配 .isNull("id")//为空 .isNotNull("id")//不为空 .in("age",1,2,3)//在给定的values之内 .notIn("age",1,2,3)//不在给定的values之内 .inSql("age","select id from table where id < 3")//在写的sql之内 .notInSql("age","select id from table where id < 3")//不在sql之内 .groupBy("id","name")//分组 ID和name .orderByAsc(true,"id","name") //排序 .orderByDesc(true,"name","id")//排序 .orderBy(true,true,"id","name") //排序 .having("sum(age) > 10") // sum(age) > 10 .or() //主动调用or表示紧接着下一个方法不是用and连接! //(不调用or则默认为使用and连接) .and(i -> i.eq("name", "李白").ne("status", "活着")) .nested(i -> i.eq("name", "李白").ne("status", "活着")) .apply("id = 1") //拼接 sql .last("limit 1") //无视优化规则直接拼接到 sql 的最后 .exists("select id from table where age = 1") //拼接EXISTS(sql语句) .notExists("select id from table where age = 1"); //拼接NOT EXISTS (sql语句) List<UserEntity> users = userMapper.selectList(wrapper); users.forEach(System.out::println); } //updateWrapper @Test void test8() { //将没有逻辑删除的设置名字年龄和邮箱全部更新 UpdateWrapper<UserEntity> Uwrapper = new UpdateWrapper<>(); //更新Wrapper Uwrapper.set("name","lzy") .set("age",99).set("email","145236987@qq.com"); //设置年龄为18 UserEntity entity = new UserEntity(); int count = userMapper.update(entity,Uwrapper); //返回更新的目录条数 System.out.println(count);//打印更新的目录条数 } @Test void test9(){ UpdateWrapper<UserEntity> Uwrapper = new UpdateWrapper<>(); Uwrapper.setSql("name = '老李头'"); //设置 SET 部分 SQL,内部写一条sql语句。 UserEntity entity = new UserEntity(); int count = userMapper.update(entity,Uwrapper); //返回更新的目录条数 System.out.println(count);//打印更新的目录条数 } }

    4.核心功能——分页插件

    4.1编写配置类MyBatisPlusConfig(官网拿来即用)

    package com.example.config; import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; @EnableTransactionManagement @Configuration @MapperScan("com.example.blog.mapper") //扫描文件夹 public class MyBatisPlusConfig { //分页插件 @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; } }

    4.2测试函数以及效果图

    5.插件扩展——自动填充功能

    5.1 步骤一:数据库添加字段

    5.2 步骤二:在UserEntity中添加字段,也可以在自动生成中配置

    5.3 步骤三:自定义实现类 MyMetaObjectHandler

    package com.example.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 // public class MyMetaObjectHandler implements MetaObjectHandler { //插入时的更新策略 @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill..."); this.setFieldValByName("createTime",new Date(),metaObject); this.setFieldValByName("updateTime",new Date(),metaObject); } //更新时的填充策略 @Override public void updateFill(MetaObject metaObject) { log.info("start update fill..."); this.setFieldValByName("updateTime",new Date(),metaObject); } }

    5.4 注意事项:

    字段必须声明TableField注解,属性fill选择对应策略,

    该声明告知Mybatis-Plus需要预留注入SQL字段,

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

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

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

    5.5 FieIdFill策略在5.2中的应用

    public enum FieldFill { /** * 默认不处理 */ DEFAULT, /** * 插入填充字段 */ INSERT, /** * 更新填充字段 */ UPDATE, /** * 插入和更新填充字段 */ INSERT_UPDATE }

    6:自用小插件——SQL配置日志输出

    6.1:效果图展示

    7.1乐观锁

    第一步:在数据库中添加字段version

    **第二步:**在实体类中添加字段Integer version 第三步:注册乐观锁插件 输出结果如图

    Processed: 0.013, SQL: 10